[ADD] links for integer, progressbar and float_time form widgets, just use FieldFloat
[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.Controller.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     },
31     start: function() {
32         //this.log('Starting FormView '+this.model+this.view_id)
33         return this.rpc("/base/formview/load", {"model": this.model, "view_id": this.view_id}, this.on_loaded);
34     },
35     on_loaded: function(data) {
36         var self = this;
37         this.fields_view = data.fields_view;
38
39         var frame = new openerp.base.form.WidgetFrame(this, this.fields_view.arch);
40
41         this.$element.html(QWeb.render("FormView", { "frame": frame, "view": this }));
42         _.each(this.widgets, function(w) {
43             w.start();
44         });
45         this.$element.find('div.oe_form_pager button[data-pager-action]').click(function() {
46             var action = $(this).data('pager-action');
47             self.on_pager_action(action);
48         });
49
50         this.$element.find('div.oe_form_buttons button.oe_form_button_save').click(this.do_save);
51         this.$element.find('div.oe_form_buttons button.oe_form_button_save_edit').click(this.do_save_edit);
52         this.$element.find('div.oe_form_buttons button.oe_form_button_cancel').click(this.do_cancel);
53
54         // sidebar stuff
55         if (this.view_manager.sidebar) {
56             this.view_manager.sidebar.load_multi_actions();
57         }
58     },
59     on_record_loaded: function(record) {
60         if (record) {
61             this.datarecord = record;
62             for (var f in this.fields) {
63                 this.fields[f].set_value(this.datarecord[f]);
64             }
65             this.on_form_changed();
66             this.ready = true;
67         } else {
68             this.log("No record received");
69         }
70         this.do_update_pager();
71     },
72     on_form_changed: function(widget) {
73         for (var w in this.widgets) {
74             w = this.widgets[w];
75             w.process_attrs();
76             w.update_dom();
77         }
78         if (widget) {
79             // TODO: check if  and trigger
80             // if (onchange for this field) {
81             //      this.ready = false;
82             //      rpc - process.callback ( this.ready = true; )
83             // }
84         }
85     },
86     do_save: function() {
87         if (!this.ready) {
88             return false;
89         }
90         var invalid = false;
91         var values = {};
92         for (var f in this.fields) {
93             f = this.fields[f];
94             if (f.invalid) {
95                 invalid = true;
96             } else if (f.touched) {
97                 values[f.name] = f.get_value();
98             }
99         }
100         if (invalid) {
101             this.on_invalid();
102         } else {
103             this.log("About to save", values)
104             this.dataset.write(this.datarecord.id, values, this.on_saved);
105         }
106     },
107     do_save_edit: function() {
108         if (this.do_save()) {
109             this.switch_readonly();
110         }
111     },
112     do_show: function () {
113         this.dataset.fetch_index(this.fields_view.fields, this.on_record_loaded);
114         this.$element.show();
115     },
116     do_hide: function () {
117         this.$element.hide();
118     },
119     do_update_pager: function() {
120         var $pager = this.$element.find('div.oe_form_pager');
121         $pager.find("button[data-pager-action='first'], button[data-pager-action='previous']").attr('disabled', this.dataset.index == 0);
122         $pager.find("button[data-pager-action='next'], button[data-pager-action='last']").attr('disabled', this.dataset.index == this.dataset.ids.length - 1);
123         this.$element.find('span.oe_pager_index').html(this.dataset.index + 1);
124         this.$element.find('span.oe_pager_count').html(this.dataset.count);
125     },
126     on_pager_action: function(action) {
127         switch (action) {
128             case 'first':
129                 this.dataset.index = 0;
130                 break;
131             case 'previous':
132                 this.dataset.previous();
133                 break;
134             case 'next':
135                 this.dataset.next();
136                 break;
137             case 'last':
138                 this.dataset.index = this.dataset.ids.length - 1;
139                 break;
140         }
141         this.dataset.fetch_index(this.fields_view.fields, this.on_record_loaded);
142     },
143     switch_readonly: function() {
144     },
145     switch_editable: function() {
146     },
147     on_invalid: function() {
148         var msg = "<ul>";
149         _.each(this.fields, function(f) {
150             if (f.invalid) {
151                 msg += "<li>" + f.string + "</li>";
152             }
153         });
154         msg += "</ul>";
155         this.notification.alert("The following fields are invalid :", msg);
156     },
157     on_saved: function(r) {
158         if (!r.result) {
159             this.log("Record was not saved");
160         } else {
161             // Check response for exceptions, display error
162             this.notification['default']("Record saved", "The record #" + this.datarecord.id + " has been saved.");
163         }
164     },
165     do_search: function (domains, contexts, groupbys) {
166     },
167     on_action: function (action) {
168     }
169 });
170
171 /** @namespace */
172 openerp.base.form = {};
173
174 openerp.base.form.Widget = openerp.base.Controller.extend({
175     init: function(view, node) {
176         this.view = view;
177         this.node = node;
178         this.attrs = eval('(' + (this.node.attrs.attrs || '{}') + ')');
179         this.type = this.type || node.tag;
180         this.element_name = this.element_name || this.type;
181         this.element_id = [this.view.element_id, this.element_name, this.view.widgets_counter++].join("_");
182
183         this._super(this.view.session, this.element_id);
184
185         this.view.widgets[this.element_id] = this;
186         this.children = node.children;
187         this.colspan = parseInt(node.attrs.colspan || 1);
188         this.template = "Widget";
189
190         this.string = this.string || node.attrs.string;
191         this.help = this.help || node.attrs.help;
192         this.invisible = (node.attrs.invisible == '1');
193     },
194     start: function() {
195         this.$element = $('#' + this.element_id);
196     },
197     process_attrs: function() {
198         for (var a in this.attrs) {
199             this[a] = this.eval_attrs(this.attrs[a]);
200         }
201     },
202     eval_attrs: function(expr) {
203         var stack = [];
204         for (var i = 0; i < expr.length; i++) {
205             var ex = expr[i];
206             if (ex.length == 1) {
207                 stack.push(ex[0]);
208                 continue;
209             }
210
211             var field = this.view.fields[ex[0]].value;
212             var op = ex[1];
213             var val = ex[2];
214
215             switch (op.toLowerCase()) {
216                 case '=':
217                 case '==':
218                     stack.push(field == val);
219                     break;
220                 case '!=':
221                 case '<>':
222                     stack.push(field != val);
223                     break;
224                 case '<':
225                     stack.push(field < val);
226                     break;
227                 case '>':
228                     stack.push(field > val);
229                     break;
230                 case '<=':
231                     stack.push(field <= val);
232                     break;
233                 case '>=':
234                     stack.push(field >= val);
235                     break;
236                 case 'in':
237                     stack.push(_.indexOf(val, field) > -1);
238                     break;
239                 case 'not in':
240                     stack.push(_.indexOf(val, field) == -1);
241                     break;
242                 default:
243                     this.log("Unsupported operator in attrs :", op);
244             }
245         }
246
247         for (var j = stack.length-1; j >- 1; j--) {
248             switch (stack[j]) {
249                 case '|':
250                     var result = stack[j + 1] || stack[j + 2];
251                     stack.splice(j, 3, result);
252                     break;
253                 case '&':
254                     var result = stack[j + 1] && stack[j + 2];
255                     stack.splice(j, 3, result);
256                     break;
257             }
258         }
259         return _.indexOf(stack, false) == -1;
260     },
261     update_dom: function() {
262         this.$element.toggle(!this.invisible);
263     },
264     render: function() {
265         var template = this.template;
266         return QWeb.render(template, { "widget": this });
267     }
268 });
269
270 openerp.base.form.WidgetFrame = openerp.base.form.Widget.extend({
271     init: function(view, node) {
272         this._super(view, node);
273         this.template = "WidgetFrame";
274         this.columns = node.attrs.col || 4;
275         this.x = 0;
276         this.y = 0;
277         this.table = [];
278         this.add_row();
279         for (var i = 0; i < node.children.length; i++) {
280             var n = node.children[i];
281             if (n.tag == "newline") {
282                 this.add_row();
283             } else {
284                 this.handle_node(n);
285             }
286         }
287         this.set_row_cells_with(this.table[this.table.length - 1]);
288     },
289     add_row: function(){
290         if (this.table.length) {
291             this.set_row_cells_with(this.table[this.table.length - 1]);
292         }
293         var row = [];
294         this.table.push(row);
295         this.x = 0;
296         this.y += 1;
297         return row;
298     },
299     set_row_cells_with: function(row) {
300         for (var i = 0; i < row.length; i++) {
301             var w = row[i];
302             if (w.is_field_label) {
303                 w.width = "1%";
304                 if (row[i + 1]) {
305                     row[i + 1].width = Math.round((100 / this.columns) * (w.colspan + 1) - 1) + '%';
306                 }
307             } else if (w.width === undefined) {
308                 w.width = Math.round((100 / this.columns) * w.colspan) + '%';
309             }
310         }
311     },
312     handle_node: function(n) {
313         var type = this.view.fields_view.fields[n.attrs.name] || {};
314         var widget_type = n.attrs.widget || type.type || n.tag;
315         var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, n);
316         if (n.tag == 'field' && n.attrs.nolabel != '1') {
317             var label = new (openerp.base.form.widgets.get_object('label')) (this.view, n);
318             label["for"] = widget;
319             this.add_widget(label);
320         }
321         this.add_widget(widget);
322     },
323     add_widget: function(w) {
324         if (!w.invisible) {
325             var current_row = this.table[this.table.length - 1];
326             if (current_row.length && (this.x + w.colspan) > this.columns) {
327                 current_row = this.add_row();
328             }
329             current_row.push(w);
330             this.x += w.colspan;
331         }
332         return w;
333     }
334 });
335
336 openerp.base.form.WidgetNotebook = openerp.base.form.Widget.extend({
337     init: function(view, node) {
338         this._super(view, node);
339         this.template = "WidgetNotebook";
340         this.pages = [];
341         for (var i = 0; i < node.children.length; i++) {
342             var n = node.children[i];
343             if (n.tag == "page") {
344                 var page = new openerp.base.form.WidgetFrame(this.view, n);
345                 this.pages.push(page);
346             }
347         }
348     },
349     start: function() {
350         this._super.apply(this, arguments);
351         this.$element.tabs();
352     }
353 });
354
355 openerp.base.form.WidgetSeparator = openerp.base.form.Widget.extend({
356     init: function(view, node) {
357         this._super(view, node);
358         this.template = "WidgetSeparator";
359     }
360 });
361
362 openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({
363     init: function(view, node) {
364         this._super(view, node);
365         this.template = "WidgetButton";
366     }
367 });
368
369 openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({
370     init: function(view, node) {
371         this.is_field_label = true;
372         this.element_name = 'label_' + node.attrs.name;
373
374         this._super(view, node);
375
376         this.template = "WidgetLabel";
377         this.colspan = 1;
378     },
379     render: function () {
380         if (this['for'] && this.type !== 'label') {
381             return QWeb.render(this.template, {widget: this['for']});
382         }
383         // Actual label widgets should not have a false and have type label
384         return QWeb.render(this.template, {widget: this});
385     }
386 });
387
388 openerp.base.form.Field = openerp.base.form.Widget.extend({
389     init: function(view, node) {
390         this.name = node.attrs.name;
391         this.value = undefined;
392         view.fields[this.name] = this;
393         this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
394         this.element_name = "field_" + this.name + "_" + this.type;
395
396         this._super(view, node);
397
398         if (node.attrs.nolabel != '1' && this.colspan > 1) {
399             this.colspan--;
400         }
401         this.field = view.fields_view.fields[node.attrs.name];
402         this.string = node.attrs.string || this.field.string;
403         this.help = node.attrs.help || this.field.help;
404         this.nolabel = (node.attrs.nolabel == '1');
405         this.readonly = (node.attrs.readonly == '1');
406         this.required = (node.attrs.required == '1');
407         this.invalid = false;
408         this.touched = false;
409     },
410     set_value: function(value) {
411         this.value = value;
412         this.invalid = false;
413     },
414     get_value: function() {
415         return this.value;
416     },
417     update_dom: function() {
418         this._super.apply(this, arguments);
419         this.$element.toggleClass('disabled', this.readonly);
420         this.$element.toggleClass('required', this.required);
421         this.$element.toggleClass('invalid', this.invalid);
422     },
423     on_ui_change: function() {
424         this.touched = true;
425     }
426 });
427
428 openerp.base.form.FieldChar = openerp.base.form.Field.extend({
429     init: function(view, node) {
430         this._super(view, node);
431         this.template = "FieldChar";
432     },
433     start: function() {
434         this._super.apply(this, arguments);
435         this.$element.find('input').change(this.on_ui_change);
436     },
437     set_value: function(value) {
438         this._super.apply(this, arguments);
439         var show_value = (value != null && value !== false) ? value : '';
440         this.$element.find('input').val(show_value);
441     },
442     update_dom: function() {
443         this._super.apply(this, arguments);
444         this.$element.find('input').attr('disabled', this.readonly);
445     },
446     on_ui_change: function() {
447         this._super.apply(this, arguments);
448         this.value = this.$element.find('input').val();
449         this.invalid = this.required && this.value == "";
450         this.view.on_form_changed(this);
451     }
452 });
453
454 openerp.base.form.FieldEmail = openerp.base.form.FieldChar.extend({
455 });
456
457 openerp.base.form.FieldUrl = openerp.base.form.FieldChar.extend({
458 });
459
460 openerp.base.form.FieldFloat = openerp.base.form.FieldChar.extend({
461     set_value: function(value) {
462         this._super.apply(this, arguments);
463         var show_value = (value != null && value !== false) ? value.toFixed(2) : '';
464         this.$element.find('input').val(value);
465     },
466     on_ui_change: function() {
467         this._super.apply(this, arguments);
468         this.value = this.$element.find('input').val().replace(/,/g, '.');
469         this.invalid = (this.required && this.value == "") || !this.value.match(/^\d(\.\d)?$/) ;
470         this.view.on_form_changed(this);
471     }
472 });
473
474 openerp.base.form.FieldDate = openerp.base.form.FieldChar.extend({
475     init: function(view, node) {
476         this._super(view, node);
477         this.template = "FieldDate";
478     },
479     start: function() {
480         this._super.apply(this, arguments);
481         this.$element.find('input').change(this.on_ui_change).datepicker({
482             dateFormat: 'yy-mm-dd'
483         });
484     },
485     set_value: function(value) {
486         this._super.apply(this, arguments);
487         var show_value = (value != null && value !== false) ? value : '';
488         this.$element.find('input').val(show_value);
489     },
490     on_ui_change: function() {
491         this._super.apply(this, arguments);
492         this.value = this.$element.find('input').val();
493         this.invalid = this.required && this.value == "";
494         this.view.on_form_changed(this);
495     }
496 });
497
498 openerp.base.form.FieldDatetime = openerp.base.form.FieldChar.extend({
499     init: function(view, node) {
500         this._super(view, node);
501         this.template = "FieldDatetime";
502     },
503     start: function() {
504         this._super.apply(this, arguments);
505         this.$element.find('input').change(this.on_ui_change).datetimepicker({
506             dateFormat: 'yy-mm-dd',
507             timeFormat: 'hh:mm:ss'
508         });
509     },
510     set_value: function(value) {
511         this._super.apply(this, arguments);
512         var show_value = (value != null && value !== false) ? value : '';
513         this.$element.find('input').val(show_value);
514     },
515     on_ui_change: function() {
516         this._super.apply(this, arguments);
517         this.value = this.$element.find('input').val();
518         this.invalid = this.required && this.value == "";
519         this.view.on_form_changed(this);
520     }
521 });
522
523 openerp.base.form.FieldText = openerp.base.form.Field.extend({
524     init: function(view, node) {
525         this._super(view, node);
526         this.template = "FieldText";
527     },
528     start: function() {
529         this._super.apply(this, arguments);
530         this.$element.find('textarea').change(this.on_ui_change);
531     },
532     set_value: function(value) {
533         this._super.apply(this, arguments);
534         var show_value = (value != null && value !== false) ? value : '';
535         this.$element.find('textarea').val(show_value);
536     },
537     update_dom: function() {
538         this._super.apply(this, arguments);
539         this.$element.find('textarea').attr('disabled', this.readonly);
540     },
541     on_ui_change: function() {
542         this._super.apply(this, arguments);
543         this.value = this.$element.find('textarea').val();
544         this.invalid = this.required && this.value == "";
545         this.view.on_form_changed(this);
546     }
547 });
548
549 openerp.base.form.FieldBoolean = openerp.base.form.Field.extend({
550     init: function(view, node) {
551         this._super(view, node);
552         this.template = "FieldBoolean";
553     },
554     start: function() {
555         var self = this;
556         this._super.apply(this, arguments);
557         this.$element.find('input').click(function() {
558             if ($(this)[0].checked != this.value) {
559                 self.on_ui_change();
560             }
561         });
562     },
563     set_value: function(value) {
564         this._super.apply(this, arguments);
565         this.$element.find('input')[0].checked = value;
566     },
567     update_dom: function() {
568         this._super.apply(this, arguments);
569         this.$element.find('textarea').attr('disabled', this.readonly);
570     },
571     on_ui_change: function() {
572         this._super.apply(this, arguments);
573         this.value = this.$element.find('input').is(':checked');
574         this.invalid = this.required && !this.value;
575         this.view.on_form_changed(this);
576     }
577 });
578
579 openerp.base.form.FieldTextXml = openerp.base.form.Field.extend({
580 // to replace view editor
581 });
582
583 openerp.base.form.FieldSelection = openerp.base.form.Field.extend({
584     init: function(view, node) {
585         this._super(view, node);
586         this.template = "FieldSelection";
587     },
588     start: function() {
589         this._super.apply(this, arguments);
590         this.$element.find('select').change(this.on_ui_change);
591     },
592     set_value: function(value) {
593         this._super.apply(this, arguments);
594         if (value != null && value !== false) {
595             this.$element.find('select').val(value);
596         } else {
597             this.$element.find('select')[0].selectedIndex = 0;
598         }
599     },
600     update_dom: function() {
601         this._super.apply(this, arguments);
602         this.$element.find('select').attr('disabled', this.readonly);
603     },
604     on_ui_change: function() {
605         this._super.apply(this, arguments);
606         this.value = this.$element.find('select').val();
607         this.invalid = this.required && this.value == "";
608         this.view.on_form_changed(this);
609     }
610 });
611
612 openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({
613     init: function(view, node) {
614         this._super(view, node);
615         this.template = "FieldMany2One";
616     },
617     set_value: function(value) {
618         this._super.apply(this, arguments);
619         var show_value = ''
620         if (value != null && value !== false) {
621             show_value = value[1];
622             this.value = value[0];
623         }
624         this.$element.find('input').val(show_value);
625     }
626 });
627
628 openerp.base.form.FieldOne2ManyDatasSet = openerp.base.DataSet.extend({
629 // Extends view manager
630 });
631
632 openerp.base.form.FieldOne2ManyViewManager = openerp.base.ViewManager.extend({
633 // Extends view manager
634 });
635
636 openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({
637     init: function(view, node) {
638         this._super(view, node);
639         this.template = "FieldOne2Many";
640         this.viewmanager = null;
641         this.operations = [];
642
643     },
644     start: function() {
645         this._super.apply(this, arguments);
646         this.log("o2m.start");
647         var action = { res_model: this.field.relation, views: [ [false,"list"] ] };
648         this.viewmanager = new openerp.base.ViewManagerAction(this.view.session, this.element_id, action);
649     },
650     set_value: function(value) {
651         this.value = value;
652         this.log("o2m.set_value",value);
653         this.viewmanager.dataset.ids = value;
654         // this.viewmanager.views.list.controller.do_update();
655     },
656     get_value: function(value) {
657         return this.operations;
658     },
659     update_dom: function() {
660         this._super.apply(this, arguments);
661         this.$element.toggleClass('disabled', this.readonly);
662         this.$element.toggleClass('required', this.required);
663     },
664     on_ui_change: function() {
665     }
666 });
667
668 openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({
669     init: function(view, node) {
670         this._super(view, node);
671         this.template = "FieldMany2Many";
672     }
673 });
674
675 openerp.base.form.FieldReference = openerp.base.form.Field.extend({
676     init: function(view, node) {
677         this._super(view, node);
678         this.template = "FieldReference";
679     }
680 });
681
682 /**
683  * Registry of form widgets, called by :js:`openerp.base.FormView`
684  */
685 openerp.base.form.widgets = new openerp.base.Registry({
686     'group' : 'openerp.base.form.WidgetFrame',
687     'notebook' : 'openerp.base.form.WidgetNotebook',
688     'separator' : 'openerp.base.form.WidgetSeparator',
689     'label' : 'openerp.base.form.WidgetLabel',
690     'button' : 'openerp.base.form.WidgetButton',
691     'char' : 'openerp.base.form.FieldChar',
692     'email' : 'openerp.base.form.FieldEmail',
693     'url' : 'openerp.base.form.FieldUrl',
694     'text' : 'openerp.base.form.FieldText',
695     'date' : 'openerp.base.form.FieldDate',
696     'datetime' : 'openerp.base.form.FieldDatetime',
697     'selection' : 'openerp.base.form.FieldSelection',
698     'many2one' : 'openerp.base.form.FieldMany2One',
699     'many2many' : 'openerp.base.form.FieldMany2Many',
700     'one2many' : 'openerp.base.form.FieldOne2Many',
701     'one2many_list' : 'openerp.base.form.FieldOne2Many',
702     'reference' : 'openerp.base.form.FieldReference',
703     'boolean' : 'openerp.base.form.FieldBoolean',
704     'float' : 'openerp.base.form.FieldFloat',
705     'integer': 'openerp.base.form.FieldFloat',
706     'progressbar': 'openerp.base.form.FieldFloat',
707     'float_time': 'openerp.base.form.FieldFloat'
708 });
709
710 };
711
712 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: