[MERGE] Forward-port latest saas-3 bugfixes, up to 30f43da
[odoo/odoo.git] / addons / web / static / src / js / view_form.js
index 38eb78e..dee6856 100644 (file)
@@ -586,13 +586,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
             this._internal_set_values(result.value, processed);
         }
         if (!_.isEmpty(result.warning)) {
-            instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), {
+            new instance.web.Dialog(this, {
+                size: 'medium',
                 title:result.warning.title,
-                modal: true,
                 buttons: [
-                    {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+                    {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
                 ]
-            });
+            }, QWeb.render("CrashManager.warning", result.warning)).open();
         }
 
         return $.Deferred().resolve();
@@ -1086,7 +1086,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
                 };
             })
             .value();
-
         var d = new instance.web.Dialog(this, {
             title: _t("Set Default"),
             args: {
@@ -1286,6 +1285,9 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
         var defs = [];
         _.each(this.to_replace, function(el) {
             defs.push(el[0].replace(el[1]));
+            if (el[1].children().length) {
+                el[0].$el.append(el[1].children());
+            }
         });
         this.to_replace = [];
         return $.when.apply($, defs);
@@ -1313,7 +1315,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
         var tagname = $tag[0].nodeName.toLowerCase();
         if (this.tags_registry.contains(tagname)) {
             this.tags_to_init.push($tag);
-            return $tag;
+            return (tagname === 'button') ? this.process_button($tag) : $tag;
         }
         var fn = self['process_' + tagname];
         if (fn) {
@@ -1330,6 +1332,13 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
             return $tag;
         }
     },
+    process_button: function ($button) {
+        var self = this;
+        $button.children().each(function() {
+            self.process($(this));
+        });
+        return $button;
+    },
     process_widget: function($widget) {
         this.widgets_to_init.push($widget);
         return $widget;
@@ -1915,6 +1924,8 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
     template: 'WidgetButton',
     init: function(field_manager, node) {
         node.attrs.type = node.attrs['data-button-type'];
+        this.is_stat_button = /\boe_stat_button\b/.test(node.attrs['class']);
+        this.icon = node.attrs.icon && "<span class=\"fa " + node.attrs.icon + " fa-fw\"></span>";
         this._super(field_manager, node);
         this.force_disabled = false;
         this.string = (this.node.attrs.string || '').replace(/_/g, '');
@@ -1950,26 +1961,23 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
         var exec_action = function() {
             if (self.node.attrs.confirm) {
                 var def = $.Deferred();
-                var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), {
+                var dialog = new instance.web.Dialog(this, {
                     title: _t('Confirm'),
-                    modal: true,
                     buttons: [
                         {text: _t("Cancel"), click: function() {
-                                $(this).dialog("close");
+                                this.parents('.modal').modal('hide');
                             }
                         },
                         {text: _t("Ok"), click: function() {
                                 var self2 = this;
                                 self.on_confirmed().always(function() {
-                                    $(self2).dialog("close");
+                                    self2.parents('.modal').modal('hide');
                                 });
                             }
                         }
                     ],
-                    beforeClose: function() {
-                        def.resolve();
-                    },
-                });
+                }, $('<div/>').text(self.node.attrs.confirm)).open();
+                dialog.on("closing", null, function() {def.resolve();});
                 return def.promise();
             } else {
                 return self.on_confirmed();
@@ -1985,7 +1993,6 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
         var self = this;
 
         var context = this.build_context();
-
         return this.view.do_execute_action(
             _.extend({}, this.node.attrs, {context: context}),
             this.view.dataset, this.view.datarecord.id, function (reason) {
@@ -2434,6 +2441,76 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
     }
 });
 
+instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+    init: function(field_manager, node) {
+        this._super.apply(this, arguments);
+    },
+    start: function() {
+        var self = this;
+        this._super.apply(this, arguments);
+        this.on("change:effective_readonly", this, function () {
+            this.display_field();
+            this.render_value();
+        });
+        this.display_field();
+        return this._super();
+    },
+    render_value: function() {
+        this.$('button.select_records').css('visibility', this.get('effective_readonly') ? 'hidden': '');
+    },
+    set_value: function(value_) {
+        var self = this;
+        this.set('value', value_ || false);
+        this.display_field();
+     },
+    display_field: function() {
+        var self = this;
+        this.$el.html(instance.web.qweb.render("FieldCharDomain", {widget: this}));
+        if (this.get('value')) {
+            var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
+            var domain = instance.web.pyeval.eval('domain', this.get('value'));
+            var ds = new instance.web.DataSetStatic(self, model, self.build_context());
+            ds.call('search_count', [domain]).then(function (results) {
+                $('.oe_domain_count', self.$el).text(results + ' records selected');
+                $('button span', self.$el).text(' Change selection');
+            });
+        } else {
+            $('.oe_domain_count', this.$el).text('0 record selected');
+            $('button span', this.$el).text(' Select records');
+        };
+        this.$('.select_records').on('click', self.on_click);
+    },
+    on_click: function(ev) {
+        event.preventDefault();
+        var self = this;
+        var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
+        this.pop = new instance.web.form.SelectCreatePopup(this);
+        this.pop.select_element(
+            model, {title: 'Select records...'},
+            [], this.build_context());
+        this.pop.on("elements_selected", self, function(element_ids) {
+            if (this.pop.$('input.oe_list_record_selector').prop('checked')) {
+                var search_data = this.pop.searchview.build_search_data();
+                var domain_done = instance.web.pyeval.eval_domains_and_contexts({
+                    domains: search_data.domains,
+                    contexts: search_data.contexts,
+                    group_by_seq: search_data.groupbys || []
+                }).then(function (results) {
+                    return results.domain;
+                });
+            }
+            else {
+                var domain = [["id", "in", element_ids]];
+                var domain_done = $.Deferred().resolve(domain);
+            }
+            $.when(domain_done).then(function (domain) {
+                var domain = self.pop.dataset.domain.concat(domain || []);
+                self.set_value(domain);
+            });
+        });
+    },
+});
+
 instance.web.DateTimeWidget = instance.web.Widget.extend({
     template: "web.datepicker",
     jqueryui_object: 'datetimepicker',
@@ -2816,6 +2893,87 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
     }
 });
 
+/**
+    The PercentPie field expect a float from 0 to 100.
+*/
+instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
+    template: 'FieldPercentPie',
+
+    render_value: function() {
+        var value = this.get('value'),
+            formatted_value = Math.round(value || 0) + '%',
+            svg = this.$('svg')[0];
+
+        svg.innerHTML = "";
+        nv.addGraph(function() {
+            var width = 42, height = 42;
+            var chart = nv.models.pieChart()
+                .width(width)
+                .height(height)
+                .margin({top: 0, right: 0, bottom: 0, left: 0})
+                .donut(true) 
+                .showLegend(false)
+                .showLabels(false)
+                .tooltips(false)
+                .color(['#7C7BAD','#DDD'])
+                .donutRatio(0.62);
+   
+            d3.select(svg)
+                .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
+                .transition()
+                .call(chart)
+                .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
+
+            d3.select(svg)
+                .append("text")
+                .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
+                .style({"font-size": "10px", "font-weight": "bold"})
+                .text(formatted_value);
+
+            return chart;
+        });
+   
+    }
+});
+
+/**
+    The FieldBarChart expectsa list of values (indeed)
+*/
+instance.web.form.FieldBarChart = instance.web.form.AbstractField.extend({
+    template: 'FieldBarChart',
+
+    render_value: function() {
+        var value = JSON.parse(this.get('value'));
+        var svg = this.$('svg')[0];
+        svg.innerHTML = "";
+        nv.addGraph(function() {
+            var width = 34, height = 34;
+            var chart = nv.models.discreteBarChart()
+                .x(function (d) { return d.tooltip })
+                .y(function (d) { return d.value })
+                .width(width)
+                .height(height)
+                .margin({top: 0, right: 0, bottom: 0, left: 0})
+                .tooltips(false)
+                .showValues(false)
+                .transitionDuration(350)
+                .showXAxis(false)
+                .showYAxis(false);
+   
+            d3.select(svg)
+                .datum([{key: 'values', values: value}])
+                .transition()
+                .call(chart)
+                .attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;');
+
+            nv.utils.windowResize(chart.update);
+
+            return chart;
+        });
+   
+    }
+});
+
 
 instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
     template: 'FieldSelection',
@@ -3185,7 +3343,7 @@ instance.web.form.M2ODialog = instance.web.Dialog.extend({
     init: function(parent) {
         this._super(parent, {
             title: _.str.sprintf(_t("Add %s"), parent.string),
-            width: 312,
+            size: 'medium',
         });
     },
     start: function() {
@@ -3253,7 +3411,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
             delete this.$drop_down;
         }
         if (this.$input) {
-            this.$input.closest(".ui-dialog .ui-dialog-content").off('scroll');
+            this.$input.closest(".modal .modal-content").off('scroll');
             this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
                             'focus focusout change keydown');
             delete this.$input;
@@ -3296,20 +3454,25 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
                 return;
             }
             var pop = new instance.web.form.FormOpenPopup(self);
-            pop.show_element(
-                self.field.relation,
-                self.get("value"),
-                self.build_context(),
-                {
-                    title: _t("Open: ") + self.string
-                }
-            );
-            pop.on('write_completed', self, function(){
-                self.display_value = {};
-                self.display_value_backup = {};
-                self.render_value();
-                self.focus();
-                self.trigger('changed_value');
+            var context = self.build_context().eval();
+            var model_obj = new instance.web.Model(self.field.relation);
+            model_obj.call('get_formview_id', [self.get("value"), context]).then(function(view_id){
+                pop.show_element(
+                    self.field.relation,
+                    self.get("value"),
+                    self.build_context(),
+                    {
+                        title: _t("Open: ") + self.string,
+                        view_id: view_id
+                    }
+                );
+                pop.on('write_completed', self, function(){
+                    self.display_value = {};
+                    self.display_value_backup = {};
+                    self.render_value();
+                    self.focus();
+                    self.trigger('changed_value');
+                });
             });
         });
 
@@ -3346,7 +3509,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
                 self.$input.autocomplete("close");
             }
         }, 50);
-        this.$input.closest(".ui-dialog .ui-dialog-content").on('scroll', this, close_autocomplete);
+        this.$input.closest(".modal .modal-content").on('scroll', this, close_autocomplete);
 
         self.ed_def = $.Deferred();
         self.uned_def = $.Deferred();
@@ -3371,7 +3534,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
                 }
                 self.floating = false;
             }
-            if (used && self.get("value") === false && ! self.no_ed) {
+            if (used && self.get("value") === false && ! self.no_ed && (self.options.no_create === false || self.options.no_create === undefined)) {
                 self.ed_def.reject();
                 self.uned_def.reject();
                 self.ed_def = $.Deferred();
@@ -3505,13 +3668,10 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
                  .html(link);
             if (! this.options.no_open)
                 $link.click(function () {
-                    self.do_action({
-                        type: 'ir.actions.act_window',
-                        res_model: self.field.relation,
-                        res_id: self.get("value"),
-                        views: [[false, 'form']],
-                        target: 'current',
-                        context: self.build_context().eval(),
+                    var context = self.build_context().eval();
+                    var model_obj = new instance.web.Model(self.field.relation);
+                    model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){
+                        self.do_action(action);
                     });
                     return false;
                  });
@@ -4876,13 +5036,12 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
         var self = this;
         this.renderElement();
         var dialog = new instance.web.Dialog(this, {
-            min_width: '800px',
             dialogClass: 'oe_act_window',
-            close: function() {
-                self.check_exit(true);
-            },
             title: this.options.title || "",
         }, this.$el).open();
+        dialog.on('closing', this, function (e){
+            self.check_exit(true);
+        });
         this.$buttonpane = dialog.$buttons;
         this.start();
     },
@@ -4949,8 +5108,8 @@ instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
     },
     destroy: function () {
         this.trigger('closed');
-        if (this.$el.is(":data(dialog)")) {
-            this.$el.dialog('close');
+        if (this.$el.is(":data(bs.modal)")) {
+            this.$el.parents('.modal').modal('hide');
         }
         this._super();
     },
@@ -5412,7 +5571,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
  * Options on attribute ; "blockui" {Boolean} block the UI or not
  * during the file is uploading
  */
-instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
     template: "FieldBinaryFileUploader",
     init: function(field_manager, node) {
         this._super(field_manager, node);
@@ -5430,9 +5589,6 @@ instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractFie
     start: function() {
         this._super(this);
         this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
-        this.on("change:effective_readonly", this, function () {
-            this.render_value();
-        });
     },
     set_value: function(value_) {
         value_ = value_ || [];
@@ -5469,7 +5625,6 @@ instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractFie
     },
     render_value: function () {
         var self = this;
-        this.$('.oe_add').css('visibility', this.get('effective_readonly') ? 'hidden': '');
         this.read_name_values().then(function (ids) {
             var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
             render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
@@ -5875,6 +6030,36 @@ instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instanc
 });
 
 /**
+    This widget is intended to be used on stat button numeric fields.  It will display
+    the value   many2many and one2many. It is a read-only field that will 
+    display a simple string "<value of field> <label of the field>"
+*/
+instance.web.form.StatInfo = instance.web.form.AbstractField.extend({
+    is_field_number: true,
+    init: function() {
+        this._super.apply(this, arguments);
+        this.internal_set_value(0);
+    },
+    set_value: function(value_) {
+        if (value_ === false || value_ === undefined) {
+            value_ = 0;
+        }
+        this._super.apply(this, [value_]);
+    },
+    render_value: function() {
+        var options = {
+            value: this.get("value") || 0,
+        };
+        if (! this.node.attrs.nolabel) {
+            options.text = this.string
+        }
+        this.$el.html(QWeb.render("StatInfo", options));
+    },
+
+});
+
+
+/**
  * Registry of form fields, called by :js:`instance.web.FormView`.
  *
  * All referenced classes must implement FieldInterface. Those represent the classes whose instances
@@ -5887,6 +6072,7 @@ instance.web.form.widgets = new instance.web.Registry({
     'url' : 'instance.web.form.FieldUrl',
     'text' : 'instance.web.form.FieldText',
     'html' : 'instance.web.form.FieldTextHtml',
+    'char_domain': 'instance.web.form.FieldCharDomain',
     'date' : 'instance.web.form.FieldDate',
     'datetime' : 'instance.web.form.FieldDatetime',
     'selection' : 'instance.web.form.FieldSelection',
@@ -5901,6 +6087,8 @@ instance.web.form.widgets = new instance.web.Registry({
     'reference' : 'instance.web.form.FieldReference',
     'boolean' : 'instance.web.form.FieldBoolean',
     'float' : 'instance.web.form.FieldFloat',
+    'percentpie': 'instance.web.form.FieldPercentPie',
+    'barchart': 'instance.web.form.FieldBarChart',
     'integer': 'instance.web.form.FieldFloat',
     'float_time': 'instance.web.form.FieldFloat',
     'progressbar': 'instance.web.form.FieldProgressBar',
@@ -5911,6 +6099,7 @@ instance.web.form.widgets = new instance.web.Registry({
     'monetary': 'instance.web.form.FieldMonetary',
     'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
     'x2many_counter': 'instance.web.form.X2ManyCounter',
+    'statinfo': 'instance.web.form.StatInfo',
 });
 
 /**