[imp] improved sidebar, now hide it by default in list views.
[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     /**
12      * @constructs
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
17      */
18     init: function(view_manager, session, element_id, dataset, view_id) {
19         this._super(session, element_id);
20         this.view_manager = view_manager;
21         this.dataset = dataset;
22         this.model = dataset.model;
23         this.view_id = view_id;
24         this.fields_view = {};
25         this.widgets = {};
26         this.widgets_counter = 0;
27         this.fields = {};
28         this.datarecord = {};
29         this.ready = false;
30         this.show_invalid = true;
31         this.touched = false;
32     },
33     start: function() {
34         //this.log('Starting FormView '+this.model+this.view_id)
35         return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id,
36             toolbar:!!this.view_manager.sidebar}, this.on_loaded);
37     },
38     on_loaded: function(data) {
39         var self = this;
40         this.fields_view = data.fields_view;
41
42         var frame = new openerp.base.form.WidgetFrame(this, this.fields_view.arch);
43
44         this.$element.html(QWeb.render("FormView", { "frame": frame, "view": this }));
45         _.each(this.widgets, function(w) {
46             w.start();
47         });
48         this.$element.find('div.oe_form_pager button[data-pager-action]').click(function() {
49             var action = $(this).data('pager-action');
50             self.on_pager_action(action);
51         });
52
53         this.$element.find('#' + this.element_id + '_header button.oe_form_button_save').click(this.do_save);
54         this.$element.find('#' + this.element_id + '_header button.oe_form_button_save_edit').click(this.do_save_edit);
55         this.$element.find('#' + this.element_id + '_header button.oe_form_button_cancel').click(this.do_cancel);
56         this.$element.find('#' + this.element_id + '_header button.oe_form_button_new').click(this.on_button_new);
57
58         // sidebar stuff
59         if (this.view_manager.sidebar) {
60             this.view_manager.sidebar.set_toolbar(data.fields_view.toolbar);
61         }
62     },
63     do_show: function () {
64         var self = this;
65         this.do_update_pager.add(function() {
66             self.$element.show();
67         });
68         if (this.dataset.index === null) {
69             // null index means we should start a new record
70             this.on_button_new();
71         } else {
72             this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
73         }
74         if (this.view_manager && this.view_manager.sidebar) {
75             this.view_manager.sidebar.refresh(true);
76         }
77     },
78     do_hide: function () {
79         this.$element.hide();
80     },
81     on_record_loaded: function(record) {
82         this.touched = false;
83         if (record) {
84             this.datarecord = record;
85             for (var f in this.fields) {
86                 var field = this.fields[f];
87                 field.set_value(this.datarecord[f] || false);
88                 field.validate();
89                 field.touched = false;
90             }
91             if (!record.id) {
92                 // New record: Second pass in order to trigger the onchanges
93                 this.touched = true;
94                 this.show_invalid = false;
95                 for (var f in record) {
96                     this.on_form_changed(this.fields[f]);
97                 }
98             }
99             this.on_form_changed();
100             this.show_invalid = this.ready = true;
101         } else {
102             this.log("No record received");
103         }
104         this.do_update_pager(record.id == null);
105     },
106     on_form_changed: function(widget) {
107         if (widget && widget.node.attrs.on_change) {
108             this.do_onchange(widget);
109         } else {
110             for (var w in this.widgets) {
111                 w = this.widgets[w];
112                 w.process_attrs();
113                 w.update_dom();
114             }
115         }
116     },
117     on_pager_action: function(action) {
118         switch (action) {
119             case 'first':
120                 this.dataset.index = 0;
121                 break;
122             case 'previous':
123                 this.dataset.previous();
124                 break;
125             case 'next':
126                 this.dataset.next();
127                 break;
128             case 'last':
129                 this.dataset.index = this.dataset.ids.length - 1;
130                 break;
131         }
132         this.reload();
133     },
134     do_update_pager: function(hide_index) {
135         var $pager = this.$element.find('#' + this.element_id + '_header div.oe_form_pager');
136         var index = hide_index ? '-' : this.dataset.index + 1;
137         $pager.find('span.oe_pager_index').html(index);
138         $pager.find('span.oe_pager_count').html(this.dataset.count);
139     },
140     do_onchange: function(widget, processed) {
141         processed = processed || [];
142         if (widget.node.attrs.on_change) {
143             var self = this;
144             this.ready = false;
145             var onchange = _.trim(widget.node.attrs.on_change);
146             var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/);
147             if (call) {
148                 var method = call[1], args = [];
149                 var argument_replacement = {
150                     'False' : false,
151                     'True' : true,
152                     'None' : null
153                 }
154                 _.each(call[2].split(','), function(a) {
155                     var field = _.trim(a);
156                     if (field in argument_replacement) {
157                         args.push(argument_replacement[field]);
158                     } else if (self.fields[field]) {
159                         var value = self.fields[field].value;
160                         args.push(value == null ? false : value);
161                     } else {
162                         args.push(false);
163                         self.log("warning : on_change can't find field " + field, onchange);
164                     }
165                 });
166                 var ajax = {
167                     url: '/base/dataset/call',
168                     async: false
169                 };
170                 return this.rpc(ajax, {
171                     model: this.dataset.model,
172                     method: method,
173                     ids: (this.datarecord.id == null ? [] : [this.datarecord.id]),
174                     args: args
175                 }, function(response) {
176                     self.on_processed_onchange(response, processed);
177                 });
178             } else {
179                 this.log("Wrong on_change format", on_change);
180             }
181         }
182     },
183     on_processed_onchange: function(response, processed) {
184         var result = response.result;
185         if (result.value) {
186             for (var f in result.value) {
187                 var field = this.fields[f];
188                 if (field) {
189                     var value = result.value[f];
190                     processed.push(field.name);
191                     if (field.value != value) {
192                         field.set_value(value);
193                         if (_.indexOf(processed, field.name) < 0) {
194                             this.do_onchange(field, processed);
195                         }
196                     }
197                 } else {
198                     this.log("warning : on_processed_onchange can't find field " + field, result);
199                 }
200             }
201             this.on_form_changed();
202         }
203         if (result.warning) {
204             $(QWeb.render("DialogWarning", result.warning)).dialog({
205                 modal: true,
206                 buttons: {
207                     Ok: function() {
208                         $(this).dialog("close");
209                     }
210                 }
211             });
212         }
213         if (result.domain) {
214             // Will be removed ?
215         }
216         this.ready = true;
217     },
218     on_button_new: function() {
219         var self = this;
220         this.dataset.default_get(_.keys(this.fields), function(result) {
221             self.on_record_loaded(result.result);
222         });
223     },
224     do_save: function(success) {
225         var self = this;
226         if (!this.ready) {
227             return false;
228         }
229         var invalid = false;
230         var values = {};
231         for (var f in this.fields) {
232             f = this.fields[f];
233             if (f.invalid) {
234                 invalid = true;
235                 f.update_dom();
236             } else if (f.touched) {
237                 values[f.name] = f.get_value();
238             }
239         }
240         if (invalid) {
241             this.on_invalid();
242             return false;
243         } else {
244             this.log("About to save", values);
245             if (!this.datarecord.id) {
246                 this.dataset.create(values, function(r) {
247                     self.on_created(r, success);
248                 });
249             } else {
250                 this.dataset.write(this.datarecord.id, values, function(r) {
251                     self.on_saved(r, success);
252                 });
253             }
254             return true;
255         }
256     },
257     do_save_edit: function() {
258         this.do_save();
259         //this.switch_readonly(); Use promises
260     },
261     switch_readonly: function() {
262     },
263     switch_editable: function() {
264     },
265     on_invalid: function() {
266         var msg = "<ul>";
267         _.each(this.fields, function(f) {
268             if (f.invalid) {
269                 msg += "<li>" + f.string + "</li>";
270             }
271         });
272         msg += "</ul>";
273         this.notification.warn("The following fields are invalid :", msg);
274     },
275     on_saved: function(r, success) {
276         if (!r.result) {
277             this.notification.warn("Record not saved", "Problem while saving record.");
278         } else {
279             this.notification.notify("Record saved", "The record #" + this.datarecord.id + " has been saved.");
280             if (success) {
281                 success(r);
282             }
283         }
284     },
285     on_created: function(r, success) {
286         if (!r.result) {
287             this.notification.warn("Record not created", "Problem while creating record.");
288         } else {
289             this.datarecord.id = arguments[0].result;
290             this.dataset.ids.push(this.datarecord.id);
291             this.dataset.index = this.dataset.ids.length - 1;
292             this.dataset.count++;
293             this.do_update_pager();
294             this.notification.notify("Record created", "The record has been created with id #" + this.datarecord.id);
295             if (success) {
296                 success(r);
297             }
298         }
299     },
300     do_search: function (domains, contexts, groupbys) {
301         this.notification.notify("Searching form");
302     },
303     on_action: function (action) {
304         this.notification.notify('Executing action ' + action);
305     },
306     do_cancel: function () {
307         this.notification.notify("Cancelling form");
308     },
309     reload: function() {
310         if (this.datarecord.id) {
311             this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded);
312         } else {
313             this.on_button_new();
314         }
315     }
316 });
317
318 /** @namespace */
319 openerp.base.form = {};
320
321 openerp.base.form.compute_domain = function(expr, fields) {
322     var stack = [];
323     for (var i = expr.length - 1; i >= 0; i--) {
324         var ex = expr[i];
325         if (ex.length == 1) {
326             var top = stack.pop();
327             switch (ex) {
328                 case '|':
329                     stack.push(stack.pop() || top);
330                     continue;
331                 case '&':
332                     stack.push(stack.pop() && top);
333                     continue;
334                 case '!':
335                     stack.push(!top);
336                     continue;
337                 default:
338                     throw new Error('Unknown domain operator ' + ex);
339             }
340         }
341
342         var field = fields[ex[0]].value;
343         var op = ex[1];
344         var val = ex[2];
345
346         switch (op.toLowerCase()) {
347             case '=':
348             case '==':
349                 stack.push(field == val);
350                 break;
351             case '!=':
352             case '<>':
353                 stack.push(field != val);
354                 break;
355             case '<':
356                 stack.push(field < val);
357                 break;
358             case '>':
359                 stack.push(field > val);
360                 break;
361             case '<=':
362                 stack.push(field <= val);
363                 break;
364             case '>=':
365                 stack.push(field >= val);
366                 break;
367             case 'in':
368                 stack.push(_(val).contains(field));
369                 break;
370             case 'not in':
371                 stack.push(!_(val).contains(field));
372                 break;
373             default:
374                 this.log("Unsupported operator in attrs :", op);
375         }
376     }
377     return _.all(stack);
378 };
379
380 openerp.base.form.Widget = openerp.base.Controller.extend({
381     init: function(view, node) {
382         this.view = view;
383         this.node = node;
384         this.attrs = eval('(' + (this.node.attrs.attrs || '{}') + ')');
385         this.type = this.type || node.tag;
386         this.element_name = this.element_name || this.type;
387         this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
388
389         this._super(this.view.session, this.element_id);
390
391         this.view.widgets[this.element_id] = this;
392         this.children = node.children;
393         this.colspan = parseInt(node.attrs.colspan || 1);
394         this.template = "Widget";
395
396         this.string = this.string || node.attrs.string;
397         this.help = this.help || node.attrs.help;
398         this.invisible = (node.attrs.invisible == '1');
399     },
400     start: function() {
401         this.$element = $('#' + this.element_id);
402     },
403     process_attrs: function() {
404         var compute_domain = openerp.base.form.compute_domain;
405         for (var a in this.attrs) {
406             this[a] = compute_domain(this.attrs[a], this.view.fields);
407         }
408     },
409     update_dom: function() {
410         this.$element.toggle(!this.invisible);
411     },
412     render: function() {
413         var template = this.template;
414         return QWeb.render(template, { "widget": this });
415     }
416 });
417
418 openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
419     init: function(view, node) {
420         this._super(view, node);
421         this.template = "WidgetFrame";
422         this.columns = node.attrs.col || 4;
423         this.x = 0;
424         this.y = 0;
425         this.table = [];
426         this.add_row();
427         for (var i = 0; i < node.children.length; i++) {
428             var n = node.children[i];
429             if (n.tag == "newline") {
430                 this.add_row();
431             } else {
432                 this.handle_node(n);
433             }
434         }
435         this.set_row_cells_with(this.table[this.table.length - 1]);
436     },
437     add_row: function(){
438         if (this.table.length) {
439             this.set_row_cells_with(this.table[this.table.length - 1]);
440         }
441         var row = [];
442         this.table.push(row);
443         this.x = 0;
444         this.y += 1;
445         return row;
446     },
447     set_row_cells_with: function(row) {
448         for (var i = 0; i < row.length; i++) {
449             var w = row[i];
450             if (w.is_field_label) {
451                 w.width = "1%";
452                 if (row[i + 1]) {
453                     row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%';
454                 }
455             } else if (w.width === undefined) {
456                 w.width = Math.round((100 / this.columns) * w.colspan) + '%';
457             }
458         }
459     },
460     handle_node: function(node) {
461         var type = this.view.fields_view.fields[node.attrs.name] || {};
462         var widget_type = node.attrs.widget || type.type || node.tag;
463         var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, node);
464         if (node.tag == 'field' && node.attrs.nolabel != '1') {
465             var label = new (openerp.base.form.widgets.get_object('label')) (this.view, node);
466             label["for"] = widget;
467             this.add_widget(label);
468         }
469         this.add_widget(widget);
470     },
471     add_widget: function(widget) {
472         var current_row = this.table[this.table.length - 1];
473         if (current_row.length && (this.x + widget.colspan) > this.columns) {
474             current_row = this.add_row();
475         }
476         current_row.push(widget);
477         this.x += widget.colspan;
478         return widget;
479     }
480 });
481
482 openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
483     init: function(view, node) {
484         this._super(view, node);
485         this.template = "WidgetNotebook";
486         this.pages = [];
487         for (var i = 0; i < node.children.length; i++) {
488             var n = node.children[i];
489             if (n.tag == "page") {
490                 var page = new openerp.base.form.WidgetFrame(this.view, n);
491                 this.pages.push(page);
492             }
493         }
494     },
495     start: function() {
496         this._super.apply(this, arguments);
497         this.$element.tabs();
498     }
499 });
500
501 openerp.base.form.WidgetSeparator = openerp.base.form.Widget.extend({
502     init: function(view, node) {
503         this._super(view, node);
504         this.template = "WidgetSeparator";
505     }
506 });
507
508 openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
509     init: function(view, node) {
510         this._super(view, node);
511         this.template = "WidgetButton";
512     },
513     start: function() {
514         this._super.apply(this, arguments);
515         this.$element.click(this.on_click);
516     },
517     on_click: function(saved) {
518         var self = this;
519         if (!this.node.attrs.special && this.view.touched && saved !== true) {
520             this.view.do_save(function() {
521                 self.on_click(true);
522             });
523         } else {
524             if (this.node.attrs.confirm) {
525                 var dialog = $('<div>' + this.node.attrs.confirm + '</div>').dialog({
526                     title: 'Confirm',
527                     modal: true,
528                     buttons: {
529                         Ok: function() {
530                             self.on_confirmed();
531                             $(this).dialog("close");
532                         },
533                         Cancel: function() {
534                             $(this).dialog("close");
535                         }
536                     }
537                 });
538             } else {
539                 this.on_confirmed();
540             }
541         }
542     },
543     on_confirmed: function() {
544         var self = this;
545
546         this.view.execute_action(
547             this.node.attrs, this.view.dataset, this.session.action_manager,
548             this.view.datarecord.id, function (result) {
549                 self.log("Button returned", result);
550                 self.view.reload();
551             });
552     }
553 });
554
555 openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
556     init: function(view, node) {
557         this.is_field_label = true;
558         this.element_name = 'label_' + node.attrs.name;
559
560         this._super(view, node);
561
562         this.template = "WidgetLabel";
563         this.colspan = 1;
564     },
565     render: function () {
566         if (this['for'] && this.type !== 'label') {
567             return QWeb.render(this.template, {widget: this['for']});
568         }
569         // Actual label widgets should not have a false and have type label
570         return QWeb.render(this.template, {widget: this});
571     }
572 });
573
574 openerp.base.form.Field = openerp.base.form.Widget.extend({
575     init: function(view, node) {
576         this.name = node.attrs.name;
577         this.value = undefined;
578         view.fields[this.name] = this;
579         this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
580         this.element_name = "field_" + this.name + "_" + this.type;
581
582         this._super(view, node);
583
584         if (node.attrs.nolabel != '1' && this.colspan > 1) {
585             this.colspan--;
586         }
587         this.field = view.fields_view.fields[node.attrs.name] || {};
588         this.string = node.attrs.string || this.field.string;
589         this.help = node.attrs.help || this.field.help;
590         this.invisible = (this.invisible || this.field.invisible == '1');
591         this.nolabel = (this.field.nolabel || node.attrs.nolabel) == '1';
592         this.readonly = (this.field.readonly || node.attrs.readonly) == '1';
593         this.required = (this.field.required || node.attrs.required) == '1';
594         this.invalid = false;
595         this.touched = false;
596     },
597     set_value: function(value) {
598         this.value = value;
599         this.invalid = false;
600         this.update_dom();
601     },
602     set_value_from_ui: function() {
603         this.value = undefined;
604     },
605     get_value: function() {
606         return this.value;
607     },
608     update_dom: function() {
609         this._super.apply(this, arguments);
610         this.$element.toggleClass('disabled', this.readonly);
611         this.$element.toggleClass('required', this.required);
612         if (this.view.show_invalid) {
613             this.$element.toggleClass('invalid', this.invalid);
614         }
615     },
616     on_ui_change: function() {
617         this.touched = this.view.touched = true;
618         this.set_value_from_ui();
619         this.validate();
620         this.view.on_form_changed(this);
621     },
622     validate: function() {
623     }
624 });
625
626 openerp.base.form.FieldChar = openerp.base.form.Field.extend({
627     init: function(view, node) {
628         this._super(view, node);
629         this.template = "FieldChar";
630     },
631     start: function() {
632         this._super.apply(this, arguments);
633         this.$element.find('input').change(this.on_ui_change);
634     },
635     set_value: function(value) {
636         this._super.apply(this, arguments);
637         var show_value = (value != null && value !== false) ? value : '';
638         this.$element.find('input').val(show_value);
639     },
640     update_dom: function() {
641         this._super.apply(this, arguments);
642         this.$element.find('input').attr('disabled', this.readonly);
643     },
644     set_value_from_ui: function() {
645         this.value = this.$element.find('input').val();
646     },
647     validate: function() {
648         this.invalid = false;
649         if (this.value === false || this.value === "") {
650             this.invalid = this.required;
651         } else if (this.validation_regex) {
652             this.invalid = !this.validation_regex.test(this.value);
653         }
654     }
655 });
656
657 openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
658     init: function(view, node) {
659         this._super(view, node);
660         this.template = "FieldEmail";
661         this.validation_regex = /@/;
662     },
663     start: function() {
664         this._super.apply(this, arguments);
665         this.$element.find('button').click(this.on_button_clicked);
666     },
667     on_button_clicked: function() {
668         if (!this.value || this.invalid) {
669             this.notification.warn("E-mail error", "Can't send email to invalid e-mail address");
670         } else {
671             location.href = 'mailto:' + this.value;
672         }
673     },
674     set_value: function(value) {
675         this._super.apply(this, arguments);
676         var show_value = (value != null && value !== false) ? value : '';
677         this.$element.find('a').attr('href', 'mailto:' + show_value);
678     }
679 });
680
681 openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
682 });
683
684 openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
685     init: function(view, node) {
686         this._super(view, node);
687         this.validation_regex = /^-?\d+(\.\d+)?$/;
688     },
689     set_value: function(value) {
690         if (!value) {
691             // As in GTK client, floats default to 0
692             value = 0;
693         }
694         this._super.apply(this, [value]);
695         var show_value = value.toFixed(2);
696         this.$element.find('input').val(show_value);
697     },
698     set_value_from_ui: function() {
699         this.value = this.$element.find('input').val().replace(/,/g, '.');
700     },
701     validate: function() {
702         this._super.apply(this, arguments);
703         if (!this.invalid) {
704             this.value = Number(this.value);
705         }
706     }
707 });
708
709 openerp.base.form.FieldDatetime = openerp.base.form.Field.extend({
710     init: function(view, node) {
711         this._super(view, node);
712         this.template = "FieldDate";
713         this.jqueryui_object = 'datetimepicker';
714     },
715     start: function() {
716         this._super.apply(this, arguments);
717         this.$element.find('input').change(this.on_ui_change)[this.jqueryui_object]({
718             dateFormat: 'yy-mm-dd',
719             timeFormat: 'hh:mm:ss'
720         });
721     },
722     set_value: function(value) {
723         this._super.apply(this, arguments);
724         if (value == null || value == false) {
725             this.$element.find('input').val('');
726         } else {
727             this.value = this.parse(value);
728             this.$element.find('input')[this.jqueryui_object]('setDate', this.value);
729         }
730     },
731     set_value_from_ui: function() {
732         this.value = this.$element.find('input')[this.jqueryui_object]('getDate') || false;
733         if (this.value) {
734             this.value = this.format(this.value);
735         }
736     },
737     validate: function() {
738         this.invalid = this.required && this.value === false;
739     },
740     parse: openerp.base.parse_datetime,
741     format: openerp.base.format_datetime
742 });
743
744 openerp.base.form.FieldDate = openerp.base.form.FieldDatetime.extend({
745     init: function(view, node) {
746         this._super(view, node);
747         this.jqueryui_object = 'datepicker';
748     },
749     parse: openerp.base.parse_date,
750     format: openerp.base.format_date
751 });
752
753 openerp.base.form.FieldFloatTime = openerp.base.form.FieldChar.extend({
754     init: function(view, node) {
755         this._super(view, node);
756         this.validation_regex = /^\d+:\d+$/;
757     },
758     set_value: function(value) {
759         value = value || 0;
760         this._super.apply(this, [value]);
761         var show_value = _.sprintf("%02d:%02d", Math.floor(value), Math.round((value % 1) * 60));
762         this.$element.find('input').val(show_value);
763     },
764     validate: function() {
765         if (typeof(this.value) == "string") {
766             this._super.apply(this, arguments);
767             if (!this.invalid) {
768                 var time = this.value.split(':');
769                 this.value = parseInt(time[0], 10) + parseInt(time[1], 10) / 60;
770             }
771         }
772     }
773 });
774
775 openerp.base.form.FieldText = openerp.base.form.Field.extend({
776     init: function(view, node) {
777         this._super(view, node);
778         this.template = "FieldText";
779         this.validation_regex = null;
780     },
781     start: function() {
782         this._super.apply(this, arguments);
783         this.$element.find('textarea').change(this.on_ui_change);
784     },
785     set_value: function(value) {
786         this._super.apply(this, arguments);
787         var show_value = (value != null && value !== false) ? value : '';
788         this.$element.find('textarea').val(show_value);
789     },
790     update_dom: function() {
791         this._super.apply(this, arguments);
792         this.$element.find('textarea').attr('disabled', this.readonly);
793     },
794     set_value_from_ui: function() {
795         this.value = this.$element.find('textarea').val();
796     },
797     validate: function() {
798         this.invalid = false;
799         if (this.value === false || this.value === "") {
800             this.invalid = this.required;
801         } else if (this.validation_regex) {
802             this.invalid = !this.validation_regex.test(this.value);
803         }
804     }
805 });
806
807 openerp.base.form.FieldBoolean = openerp.base.form.Field.extend({
808     init: function(view, node) {
809         this._super(view, node);
810         this.template = "FieldBoolean";
811     },
812     start: function() {
813         var self = this;
814         this._super.apply(this, arguments);
815         this.$element.find('input').click(function() {
816             if ($(this).is(':checked') != self.value) {
817                 self.on_ui_change();
818             }
819         });
820     },
821     set_value: function(value) {
822         this._super.apply(this, arguments);
823         this.$element.find('input')[0].checked = value;
824     },
825     set_value_from_ui: function() {
826         this.value = this.$element.find('input').is(':checked');
827     },
828     update_dom: function() {
829         this._super.apply(this, arguments);
830         this.$element.find('input').attr('disabled', this.readonly);
831     },
832     validate: function() {
833         this.invalid = this.required && !this.value;
834     }
835 });
836
837 openerp.base.form.FieldProgressBar = openerp.base.form.Field.extend({
838     init: function(view, node) {
839         this._super(view, node);
840         this.template = "FieldProgressBar";
841     },
842     start: function() {
843         this._super.apply(this, arguments);
844         this.$element.find('div').progressbar({
845             value: this.value,
846             disabled: this.readonly
847         });
848     },
849     set_value: function(value) {
850         this._super.apply(this, arguments);
851         var show_value = Number(value);
852         if (show_value === NaN) {
853             show_value = 0;
854         }
855         this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(show_value + '%');
856     }
857 });
858
859 openerp.base.form.FieldTextXml = openerp.base.form.Field.extend({
860 // to replace view editor
861 });
862
863 openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
864     init: function(view, node) {
865         this._super(view, node);
866         this.template = "FieldSelection";
867     },
868     start: function() {
869         this._super.apply(this, arguments);
870         this.$element.find('select').change(this.on_ui_change);
871     },
872     set_value: function(value) {
873         this._super.apply(this, arguments);
874         if (value != null && value !== false) {
875             this.$element.find('select').val(value);
876         } else {
877             this.$element.find('select')[0].selectedIndex = 0;
878         }
879     },
880     set_value_from_ui: function() {
881         this.value = this.$element.find('select').val();
882     },
883     update_dom: function() {
884         this._super.apply(this, arguments);
885         this.$element.find('select').attr('disabled', this.readonly);
886     },
887     validate: function() {
888         this.invalid = this.required && this.value === "";
889     }
890 });
891
892 openerp.base.form.FieldMany2OneDatasSet = openerp.base.DataSetStatic.extend({
893     start: function() {
894     },
895     write: function (id, data, callback) {
896         this._super(id, data, callback);
897     }
898 });
899
900 openerp.base.form.FieldMany2OneViewManager = openerp.base.ViewManager.extend({
901     init: function(session, element_id, dataset, views) {
902         this._super(session, element_id, dataset, views);
903     }
904 });
905
906 openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
907     init: function(view, node) {
908         this._super(view, node);
909         this.template = "FieldMany2One";
910         this.is_field_m2o = true;
911     },
912     start: function() {
913         this.$element = $('#' + this.element_id);
914         this.dataset = new openerp.base.form.FieldMany2OneDatasSet(this.session, this.field.relation);
915         var views = [ [false,"list"], [false,"form"] ];
916         this.viewmanager = new openerp.base.form.FieldMany2OneViewManager(this.view.session, this.element_id, this.dataset, views);
917         new openerp.base.m2o(this.viewmanager, this.$element, this.field.relation, this.dataset, this.session)
918         this.$element.find('input').change(this.on_ui_change);
919     },
920     set_value: function(value) {
921         this._super.apply(this, arguments);
922         var show_value = '';
923         if (value != null && value !== false) {
924             show_value = value[1];
925             this.value = value[0];
926         }
927         this.$element.find('input').val(show_value);
928         this.$element.find('input').attr('m2o_id', this.value);
929     },
930
931     get_value: function() {
932         var val = this.$element.find('input').attr('m2o_id') || this.value
933         return val;
934     },
935
936     on_ui_change: function() {
937         this.touched = this.view.touched = true;
938     }
939 });
940
941 openerp.base.form.FieldOne2ManyDatasSet = openerp.base.DataSetStatic.extend({
942     start: function() {
943     },
944     write: function (id, data, callback) {
945         this._super(id, data, callback);
946     },
947     unlink: function() {
948         this.notification.notify('Unlinking o2m ' + this.ids);
949     }
950 });
951
952 openerp.base.form.FieldOne2ManyViewManager = openerp.base.ViewManager.extend({
953     init: function(session, element_id, dataset, views) {
954         this._super(session, element_id, dataset, views);
955     }
956 });
957
958 openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
959     init: function(view, node) {
960         this._super(view, node);
961         this.template = "FieldOne2Many";
962         this.operations = [];
963     },
964     start: function() {
965         this._super.apply(this, arguments);
966         this.log("o2m.start");
967         var views = [ [false,"list"], [false,"form"] ];
968         this.dataset = new openerp.base.form.FieldOne2ManyDatasSet(this.session, this.field.relation);
969         this.viewmanager = new openerp.base.form.FieldOne2ManyViewManager(this.view.session, this.element_id, this.dataset, views);
970         this.viewmanager.start();
971     },
972     set_value: function(value) {
973         this.value = value;
974         if (value != false) {
975             this.log("o2m.set_value",value);
976             this.viewmanager.dataset.ids = value;
977             this.viewmanager.dataset.count = value.length;
978             this.viewmanager.views.list.controller.do_update();
979         }
980     },
981     get_value: function(value) {
982         return this.operations;
983     },
984     update_dom: function() {
985         this._super.apply(this, arguments);
986         this.$element.toggleClass('disabled', this.readonly);
987         this.$element.toggleClass('required', this.required);
988     },
989     on_ui_change: function() {
990     }
991 });
992
993 openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
994     init: function(view, node) {
995         this._super(view, node);
996         this.template = "FieldMany2Many";
997         this.list_id = _.uniqueId("many2many");
998         this.is_started = false;
999         this.is_setted = false;
1000     },
1001     start: function() {
1002         this._super.apply(this, arguments);
1003         this.dataset = new openerp.base.DataSetMany2Many(this.session, this.field.relation);
1004         this.list_view = new openerp.base.form.Many2ManyListView(undefined, this.view.session,
1005                 this.list_id, this.dataset, false, {'selected': false, 'addable': 'Add'});
1006         var self = this;
1007         this.list_view.m2m_field = this;
1008         this.list_view.start();
1009         var hack = {loaded: false};
1010         this.list_view.on_loaded.add_last(function() {
1011             if (! hack.loaded) {
1012                 self.is_started = true;
1013                 self.check_load();
1014                 hack.loaded = true;
1015             }
1016         });
1017     },
1018     set_value: function(value) {
1019         if (value != false) {
1020             this.dataset.ids = value;
1021             this.dataset.count = value.length;
1022             this.is_setted = true;
1023             this.check_load();
1024         }
1025     },
1026     get_value: function() {
1027         return [[6,false,this.dataset.ids]];
1028     },
1029     check_load: function() {
1030         if(this.is_started && this.is_setted) {
1031             this.list_view.do_reload();
1032         }
1033     }
1034 });
1035
1036 openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({
1037     do_delete: function (ids) {
1038         this.dataset.ids = _.without.apply(null, [this.dataset.ids].concat(ids));
1039         this.dataset.count = this.dataset.ids.length;
1040         // there may be a faster way
1041         this.do_reload();
1042         
1043         this.m2m_field.on_ui_change();
1044     },
1045     do_reload: function () {
1046         /* Dear xmo, according to your comments, this method's implementation in list view seems
1047          * to be a little bit bullshit.
1048          * I assume the list view will be changed later, so I hope it will support static datasets.
1049          */
1050         return this.rpc('/base/listview/fill', {
1051             'model': this.dataset.model,
1052             'id': this.view_id,
1053             'domain': [["id", "in", this.dataset.ids]],
1054             'context': this.dataset.context
1055         }, this.do_fill_table);
1056     },
1057     do_add_record: function (e) {
1058         e.stopImmediatePropagation();
1059         var pop = new openerp.base.form.Many2XSelectPopup(null, this.m2m_field.view.session);
1060         pop.select_element(this.model);
1061         var self = this;
1062         pop.on_select_element.add(function(element_id) {
1063             if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
1064                 self.dataset.ids.push(element_id);
1065                 self.dataset.count = self.dataset.ids.length;
1066                 self.do_reload();
1067             }
1068             pop.stop();
1069         });
1070     },
1071     select_record: function(index) {
1072         var id = this.rows[index].data.id.value;
1073         if(! id) {
1074             return;
1075         }
1076         var action_manager = this.m2m_field.view.session.action_manager;
1077         action_manager.do_action({
1078             "res_model": this.dataset.model,
1079             "views":[[false,"form"]],
1080             "res_id": id,
1081             "type":"ir.actions.act_window",
1082             "view_type":"form",
1083             "view_mode":"form",
1084             "target":"new"
1085         });
1086     }
1087 });
1088
1089 openerp.base.form.Many2XSelectPopup = openerp.base.BaseWidget.extend({
1090     identifier_prefix: "many2xselectpopup",
1091     template: "Many2XSelectPopup",
1092     select_element: function(model, dataset) {
1093         this.model = model;
1094         this.dataset = dataset
1095         var html = this.render();
1096         jQuery(html).dialog({title: '',
1097                     modal: true,
1098                     minWidth: 800});
1099         this.start();
1100     },
1101     start: function() {
1102         this._super();
1103         if (!this.dataset) {
1104             this.dataset = new openerp.base.DataSetSearch(this.session, this.model);
1105         }
1106         this.setup_search_view();
1107     },
1108     setup_search_view: function() {
1109         var self = this;
1110         if (this.searchview) {
1111             this.searchview.stop();
1112         }
1113         this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search",
1114                 this.dataset, false, {});
1115         this.searchview.on_search.add(function(domains, contexts, groupbys) {
1116             self.view_list.do_search.call(
1117                 self, domains, contexts, groupbys);
1118         });
1119         this.searchview.on_loaded.add_last(function () {
1120             var $buttons = self.searchview.$element.find(".oe_search-view-buttons");
1121             $buttons.append(QWeb.render("Many2XSelectPopup.search.buttons"));
1122             var $nbutton = $buttons.find(".oe_many2xselectpopup-search-new");
1123             $nbutton.click(function() {
1124                 self.new_object();
1125             });
1126             var $cbutton = $buttons.find(".oe_many2xselectpopup-search-close");
1127             $cbutton.click(function() {
1128                 self.stop();
1129             });
1130             self.view_list = new openerp.base.form.Many2XPopupListView( null, self.session,
1131                     self.element_id + "_view_list", self.dataset, false);
1132             self.view_list.popup = self;
1133             self.view_list.do_show();
1134             self.view_list.start();
1135             var tmphack = {"loaded": false};
1136             self.view_list.on_loaded.add_last(function() {
1137                 if ( !tmphack.loaded ) {
1138                     self.view_list.do_reload();
1139                     tmphack.loaded = true;
1140                 };
1141             });
1142         });
1143         this.searchview.start();
1144     },
1145     on_select_element: function(element_id) {
1146     },
1147     new_object: function() {
1148         var self = this;
1149         this.searchview.hide();
1150         this.view_list.$element.hide();
1151         this.dataset.index = null;
1152         this.view_form = new openerp.base.FormView({}, this.session,
1153                 this.element_id + "_view_form", this.dataset, false);
1154         this.view_form.start();
1155         this.view_form.on_loaded.add_last(function() {
1156             var $buttons = self.view_form.$element.find(".oe_form_buttons");
1157             $buttons.html(QWeb.render("Many2XSelectPopup.form.buttons"));
1158             var $nbutton = $buttons.find(".oe_many2xselectpopup-form-save");
1159             $nbutton.click(function() {
1160                 self.view_form.do_save();
1161             });
1162             var $cbutton = $buttons.find(".oe_many2xselectpopup-form-close");
1163             $cbutton.click(function() {
1164                 self.stop();
1165             });
1166         });
1167         this.view_form.on_created.add_last(function(r, success) {
1168             if (r.result) {
1169                 var id = arguments[0].result;
1170                 self.on_select_element(id);
1171             }
1172         });
1173         this.view_form.do_show();
1174     }
1175 });
1176
1177 openerp.base.form.Many2XPopupListView = openerp.base.ListView.extend({
1178     switch_to_record: function(index) {
1179         this.popup.on_select_element(this.dataset.ids[index]);
1180     }
1181 });
1182
1183 openerp.base.form.FieldReference = openerp.base.form.Field.extend({
1184     init: function(view, node) {
1185         this._super(view, node);
1186         this.template = "FieldReference";
1187     }
1188 });
1189
1190 openerp.base.form.FieldImage = openerp.base.form.Field.extend({
1191     init: function(view, node) {
1192         this._super(view, node);
1193         this.template = "FieldImage";
1194     },
1195     set_value: function(value) {
1196         this._super.apply(this, arguments);
1197         var url = '/base/formview/image?session_id=' + this.session.session_id + '&model=' +
1198             this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name
1199         this.$element.find('img').show().attr('src', url);
1200     }
1201 });
1202
1203 /**
1204  * Registry of form widgets, called by :js:`openerp.base.FormView`
1205  */
1206 openerp.base.form.widgets = new openerp.base.Registry({
1207     'group' : 'openerp.base.form.WidgetFrame',
1208     'notebook' : 'openerp.base.form.WidgetNotebook',
1209     'separator' : 'openerp.base.form.WidgetSeparator',
1210     'label' : 'openerp.base.form.WidgetLabel',
1211     'button' : 'openerp.base.form.WidgetButton',
1212     'char' : 'openerp.base.form.FieldChar',
1213     'email' : 'openerp.base.form.FieldEmail',
1214     'url' : 'openerp.base.form.FieldUrl',
1215     'text' : 'openerp.base.form.FieldText',
1216     'text_wiki' : 'openerp.base.form.FieldText',
1217     'date' : 'openerp.base.form.FieldDate',
1218     'datetime' : 'openerp.base.form.FieldDatetime',
1219     'selection' : 'openerp.base.form.FieldSelection',
1220     'many2one' : 'openerp.base.form.FieldMany2One',
1221     'many2many' : 'openerp.base.form.FieldMany2Many',
1222     'one2many' : 'openerp.base.form.FieldOne2Many',
1223     'one2many_list' : 'openerp.base.form.FieldOne2Many',
1224     'reference' : 'openerp.base.form.FieldReference',
1225     'boolean' : 'openerp.base.form.FieldBoolean',
1226     'float' : 'openerp.base.form.FieldFloat',
1227     'integer': 'openerp.base.form.FieldFloat',
1228     'progressbar': 'openerp.base.form.FieldProgressBar',
1229     'float_time': 'openerp.base.form.FieldFloatTime',
1230     'image': 'openerp.base.form.FieldImage'
1231 });
1232
1233 };
1234
1235 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: