[MERGE] fixes in account_followup
[odoo/odoo.git] / addons / web / static / src / js / view_list.js
index 7996356..b452dc8 100644 (file)
@@ -1,6 +1,6 @@
 openerp.web.list = function (instance) {
 var _t = instance.web._t,
-   _lt = instance.web._lt;
+    _lt = instance.web._lt;
 var QWeb = instance.web.qweb;
 instance.web.views.add('list', 'instance.web.ListView');
 instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ {
@@ -21,6 +21,12 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
         // whether the view rows can be reordered (via vertical drag & drop)
         'reorderable': true,
         'action_buttons': true,
+        //whether the editable property of the view has to be disabled
+        'disable_editable_mode': false,
+    },
+    view_type: 'tree',
+    events: {
+        'click thead th.oe_sortable[data-id]': 'sort_by_column'
     },
     /**
      * Core class for list-type displays.
@@ -83,7 +89,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
         });
 
         this.no_leaf = false;
-        this.on('view_load', self, self.load_list);
+        this.grouped = false;
+    },
+    view_loading: function(r) {
+        return this.load_list(r);
     },
     set_default_options: function (options) {
         this._super(options);
@@ -145,7 +154,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
      */
     start: function() {
         this.$el.addClass('oe_list');
-        return this.reload_view(null, null, true);
+        return this._super();
     },
     /**
      * Returns the style for the provided record in the current view (from the
@@ -219,7 +228,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
      * @param {Object} data.fields_view.arch current list view descriptor
      * @param {Boolean} grouped Is the list view grouped
      */
-    load_list: function(data, grouped) {
+    load_list: function(data) {
         var self = this;
         this.fields_view = data;
         this.name = "" + this.fields_view.arch.attrs.string;
@@ -245,16 +254,11 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
                 }).value();
         }
 
-        this.setup_columns(this.fields_view.fields, grouped);
+        this.setup_columns(this.fields_view.fields, this.grouped);
 
         this.$el.html(QWeb.render(this._template, this));
         this.$el.addClass(this.fields_view.arch.attrs['class']);
 
-        // add css classes that reflect the (absence of) access rights
-        this.$el.toggleClass('oe_list_cannot_create', !this.is_action_enabled('create'))
-                .toggleClass('oe_list_cannot_edit', !this.is_action_enabled('edit'))
-                .toggleClass('oe_list_cannot_delete', !this.is_action_enabled('delete'));
-
         // Head hook
         // Selecting records
         this.$el.find('.oe_list_record_selector').click(function(){
@@ -265,21 +269,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
                 'selected', [selection.ids, selection.records]);
         });
 
-        // Sorting columns
-        this.$el.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) {
-            e.stopPropagation();
-            var $this = $(this);
-            self.dataset.sort($this.data('id'));
-            if($this.hasClass("sortdown") || $this.hasClass("sortup"))  {
-                $this.toggleClass("sortdown").toggleClass("sortup");
-            } else {
-                $this.toggleClass("sortdown");
-            }
-            $this.siblings('.oe_sortable').removeClass("sortup sortdown");
-
-            self.reload_content();
-        });
-
         // Add button
         if (!this.$buttons) {
             this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self}));
@@ -290,7 +279,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
             }
             this.$buttons.find('.oe_list_add')
                     .click(this.proxy('do_add_record'))
-                    .prop('disabled', grouped);
+                    .prop('disabled', this.grouped);
         }
 
         // Pager
@@ -359,7 +348,28 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
             this.sidebar.add_toolbar(this.fields_view.toolbar);
             this.sidebar.$el.hide();
         }
-        this.trigger('list_view_loaded', data, grouped);
+        //Sort
+        if(this.dataset._sort.length){
+            if(this.dataset._sort[0].indexOf('-') == -1){
+                this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown");
+            }else {
+                this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup");
+            }
+        }
+        this.trigger('list_view_loaded', data, this.grouped);
+    },
+    sort_by_column: function (e) {
+        e.stopPropagation();
+        var $column = $(e.currentTarget);
+        this.dataset.sort($column.data('id'));
+        if($column.hasClass("sortdown") || $column.hasClass("sortup"))  {
+            $column.toggleClass("sortup sortdown");
+        } else {
+            $column.addClass("sortdown");
+        }
+        $column.siblings('.oe_sortable').removeClass("sortup sortdown");
+
+        this.reload_content();
     },
     /**
      * Configures the ListView pager based on the provided dataset's information
@@ -377,7 +387,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
 
         var total = dataset.size();
         var limit = this.limit() || total;
-        this.$pager.toggle(total !== 0);
+        if (total == 0)
+            this.$pager.hide();
+        else
+            this.$pager.css("display", "");
         this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
         var spager = '-';
         if (total) {
@@ -386,7 +399,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
             if (range_stop > total) {
                 range_stop = total;
             }
-            spager = _.str.sprintf('%d-%d of %d', range_start, range_stop, total);
+            spager = _.str.sprintf(_t("%d-%d of %d"), range_start, range_stop, total);
         }
 
         this.$pager.find('.oe_list_pager_state').text(spager);
@@ -464,25 +477,12 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
     /**
      * Reloads the list view based on the current settings (dataset & al)
      *
+     * @deprecated
      * @param {Boolean} [grouped] Should the list be displayed grouped
      * @param {Object} [context] context to send the server while loading the view
      */
     reload_view: function (grouped, context, initial) {
-        var self = this;
-        var callback = function (field_view_get) {
-            self.load_list(field_view_get, grouped);
-        };
-        if (this.embedded_view) {
-            return $.Deferred().then(callback).resolve(this.embedded_view);
-        } else {
-            return this.rpc('/web/view/load', {
-                model: this.model,
-                view_id: this.view_id,
-                view_type: "tree",
-                context: this.dataset.get_context(context),
-                toolbar: !!this.options.$sidebar
-            }).then(callback);
-        }
+        return this.load_view(context);
     },
     /**
      * re-renders the content of the list view
@@ -516,12 +516,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
         return this.reload_content();
     },
     reload_record: function (record) {
+        var self = this;
         return this.dataset.read_ids(
             [record.get('id')],
             _.pluck(_(this.columns).filter(function (r) {
                     return r.tag === 'field';
                 }), 'name')
-        ).then(function (records) {
+        ).done(function (records) {
+            if (!records[0]) {
+                self.records.remove(record);
+                return;
+            }
             _(records[0]).each(function (value, key) {
                 record.set(key, value, {silent: true});
             });
@@ -564,8 +569,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
             group_by = null;
         }
         this.no_leaf = !!context['group_by_no_leaf'];
+        this.grouped = !!group_by;
 
-        this.reload_view(!!group_by, context).then(
+        return this.load_view(context).then(
             this.proxy('reload_content'));
     },
     /**
@@ -578,7 +584,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
             return;
         }
         var self = this;
-        return $.when(this.dataset.unlink(ids)).then(function () {
+        return $.when(this.dataset.unlink(ids)).done(function () {
             _(ids).each(function (id) {
                 self.records.remove(self.records.get(id));
             });
@@ -604,6 +610,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
 
         this.dataset.index = _(this.dataset.ids).indexOf(ids[0]);
         if (this.sidebar) {
+            this.options.$sidebar.show();
             this.sidebar.$el.show();
         }
 
@@ -834,7 +841,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
         );
         var create_nocontent = this.$buttons;
         this.$el.find('.oe_view_nocontent').click(function() {
-            create_nocontent.effect('bounce', {distance: 18, times: 5}, 150);
+            create_nocontent.openerpBounce();
         });
     }
 });
@@ -891,8 +898,8 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
                 var $row;
                 if (attribute === 'id') {
                     if (old_value) {
-                        throw new Error("Setting 'id' attribute on existing record "
-                            + JSON.stringify(record.attributes));
+                        throw new Error(_.str.sprintf( _t("Setting 'id' attribute on existing record %s"),
+                            JSON.stringify(record.attributes) ));
                     }
                     if (!_.contains(self.dataset.ids, value)) {
                         // add record to dataset if not already in (added by
@@ -926,6 +933,18 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
         }, this);
 
         this.$current = $('<tbody>')
+            .delegate('input[readonly=readonly]', 'click', function (e) {
+                /*
+                    Against all logic and sense, as of right now @readonly
+                    apparently does nothing on checkbox and radio inputs, so
+                    the trick of using @readonly to have, well, readonly
+                    checkboxes (which still let clicks go through) does not
+                    work out of the box. We *still* need to preventDefault()
+                    on the event, otherwise the checkbox's state *will* toggle
+                    on click
+                 */
+                e.preventDefault();
+            })
             .delegate('th.oe_list_record_selector', 'click', function (e) {
                 e.stopPropagation();
                 var selection = self.get_selection();
@@ -943,12 +962,18 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
                       field = $target.closest('td').data('field'),
                        $row = $target.closest('tr'),
                   record_id = self.row_id($row);
+                
+                if ($target.attr('disabled')) {
+                    return;
+                }
+                $target.attr('disabled', 'disabled');
 
                 // note: $.data converts data to number if it's composed only
                 // of digits, nice when storing actual numbers, not nice when
                 // storing strings composed only of digits. Force the action
                 // name to be a string
                 $(self).trigger('action', [field.toString(), record_id, function (id) {
+                    $target.removeAttr('disabled');
                     return self.reload_record(self.records.get(id));
                 }]);
             })
@@ -960,7 +985,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
                 if (row_id) {
                     e.stopPropagation();
                     if (!self.dataset.select_id(row_id)) {
-                        throw new Error("Could not find id in dataset");
+                        throw new Error(_t("Could not find id in dataset"));
                     }
                     self.row_clicked(e);
                 }
@@ -982,13 +1007,13 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
             // set) a human-readable version. m2o does not have this issue
             // because the non-human-readable is just a number, where the
             // human-readable version is a pair
-            if (value && (ref_match = /([\w\.]+),(\d+)/.exec(value))) {
+            if (value && (ref_match = /^([\w\.]+),(\d+)$/.exec(value))) {
                 // reference values are in the shape "$model,$id" (as a
                 // string), we need to split and name_get this pair in order
                 // to get a correctly displayable value in the field
                 var model = ref_match[1],
                     id = parseInt(ref_match[2], 10);
-                new instance.web.DataSet(this.view, model).name_get([id]).then(function(names) {
+                new instance.web.DataSet(this.view, model).name_get([id]).done(function(names) {
                     if (!names.length) { return; }
                     record.set(column.id, names[0][1]);
                 });
@@ -1004,7 +1029,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
                 // and let the various registered events handle refreshing the
                 // row
                 new instance.web.DataSet(this.view, column.relation)
-                        .name_get([value]).then(function (names) {
+                        .name_get([value]).done(function (names) {
                     if (!names.length) { return; }
                     record.set(column.id, names[0]);
                 });
@@ -1012,14 +1037,15 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
         } else if (column.type === 'many2many') {
             value = record.get(column.id);
             // non-resolved (string) m2m values are arrays
-            if (value instanceof Array && !_.isEmpty(value)) {
+            if (value instanceof Array && !_.isEmpty(value)
+                    && !record.get(column.id + '__display')) {
                 var ids;
                 // they come in two shapes:
                 if (value[0] instanceof Array) {
                     var command = value[0];
                     // 1. an array of m2m commands (usually (6, false, ids))
                     if (command[0] !== 6) {
-                        throw new Error(_t("Unknown m2m command ") + command[0]);
+                        throw new Error(_.str.sprintf( _t("Unknown m2m command %s"), command[0]));
                     }
                     ids = command[2];
                 } else {
@@ -1027,9 +1053,14 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
                     ids = value;
                 }
                 new instance.web.Model(column.relation)
-                    .call('name_get', [ids]).then(function (names) {
-                        record.set(column.id, _(names).pluck(1).join(', '));
-                    })
+                    .call('name_get', [ids]).done(function (names) {
+                        // FIXME: nth horrible hack in this poor listview
+                        record.set(column.id + '__display',
+                                   _(names).pluck(1).join(', '));
+                        record.set(column.id, ids);
+                    });
+                // temp empty value
+                record.set(column.id, false);
             }
         }
         return column.format(record.toForm().data, {
@@ -1318,9 +1349,11 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
                         process_modifiers: false
                     });
                 } catch (e) {
-                    group_label = row_data[group_column.id].value;
+                    group_label = _.str.escapeHTML(row_data[group_column.id].value);
                 }
-                $group_column.text(_.str.sprintf("%s (%d)",
+                // group_label is html-clean (through format or explicit
+                // escaping if format failed), can inject straight into HTML
+                $group_column.html(_.str.sprintf(_t("%s (%d)"),
                     group_label, group.length));
 
                 if (group.length && group.openable) {
@@ -1395,43 +1428,45 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
         var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name');
         var options = { offset: page * limit, limit: limit, context: {bin_size: true} };
         //TODO xmo: investigate why we need to put the setTimeout
-        $.async_when().then(function() {dataset.read_slice(fields, options).then(function (records) {
-            // FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
-            if (self.records.length) {
-                self.records.reset(null, {silent: true});
-            }
-            if (!self.datagroup.openable) {
-                view.configure_pager(dataset);
-            } else {
-                if (dataset.size() == records.length) {
-                    // only one page
-                    self.$row.find('td.oe_list_group_pagination').empty();
+        $.async_when().done(function() {
+            dataset.read_slice(fields, options).done(function (records) {
+                // FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
+                if (self.records.length) {
+                    self.records.reset(null, {silent: true});
+                }
+                if (!self.datagroup.openable) {
+                    view.configure_pager(dataset);
                 } else {
-                    var pages = Math.ceil(dataset.size() / limit);
-                    self.$row
-                        .find('.oe_list_pager_state')
-                            .text(_.str.sprintf(_t("%(page)d/%(page_count)d"), {
-                                page: page + 1,
-                                page_count: pages
-                            }))
-                        .end()
-                        .find('button[data-pager-action=previous]')
-                            .css('visibility',
-                                 page === 0 ? 'hidden' : '')
-                        .end()
-                        .find('button[data-pager-action=next]')
-                            .css('visibility',
-                                 page === pages - 1 ? 'hidden' : '');
+                    if (dataset.size() == records.length) {
+                        // only one page
+                        self.$row.find('td.oe_list_group_pagination').empty();
+                    } else {
+                        var pages = Math.ceil(dataset.size() / limit);
+                        self.$row
+                            .find('.oe_list_pager_state')
+                                .text(_.str.sprintf(_t("%(page)d/%(page_count)d"), {
+                                    page: page + 1,
+                                    page_count: pages
+                                }))
+                            .end()
+                            .find('button[data-pager-action=previous]')
+                                .css('visibility',
+                                     page === 0 ? 'hidden' : '')
+                            .end()
+                            .find('button[data-pager-action=next]')
+                                .css('visibility',
+                                     page === pages - 1 ? 'hidden' : '');
+                    }
                 }
-            }
 
-            self.records.add(records, {silent: true});
-            list.render();
-            d.resolve(list);
-            if (_.isEmpty(records)) {
-                view.no_result();
-            }
-        });});
+                self.records.add(records, {silent: true});
+                list.render();
+                d.resolve(list);
+                if (_.isEmpty(records)) {
+                    view.no_result();
+                }
+            });
+        });
         return d.promise();
     },
     setup_resequence_rows: function (list, dataset) {
@@ -1487,7 +1522,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
                     //        Accounting > Taxes > Taxes, child tax accounts)
                     //        when synchronous (without setTimeout)
                     (function (dataset, id, seq) {
-                        $.async_when().then(function () {
+                        $.async_when().done(function () {
                             var attrs = {};
                             attrs[seqname] = seq;
                             dataset.write(id, attrs);
@@ -1512,7 +1547,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
                     self.render_groups(groups));
                 if (post_render) { post_render(); }
             }, function (dataset) {
-                self.render_dataset(dataset).then(function (list) {
+                self.render_dataset(dataset).done(function (list) {
                     self.children[null] = list;
                     self.elements =
                         [list.$current.replaceAll($el)[0]];
@@ -1559,9 +1594,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
     }
 });
 
-var DataGroup =  instance.web.CallbackEnabled.extend({
+var DataGroup =  instance.web.Class.extend({
    init: function(parent, model, domain, context, group_by, level) {
-       this._super(parent, null);
        this.model = new instance.web.Model(model, context, domain);
        this.group_by = group_by;
        this.context = context;
@@ -1572,7 +1606,7 @@ var DataGroup =  instance.web.CallbackEnabled.extend({
    list: function (fields, ifGroups, ifRecords) {
        var self = this;
        var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by);
-       $.when(query).then(function (querygroups) {
+       $.when(query).done(function (querygroups) {
            // leaf node
            if (!querygroups) {
                var ds = new instance.web.DataSetSearch(self, self.model.name, self.model.context(), self.model.domain());
@@ -1738,7 +1772,7 @@ var Record = instance.web.Class.extend(/** @lends Record# */{
             } else if (val instanceof Array) {
                 output[k] = val[0];
             } else {
-                throw new Error("Can't convert value " + val + " to context");
+                throw new Error(_.str.sprintf(_t("Can't convert value %s to context"), val));
             }
         }
         return output;
@@ -2022,6 +2056,8 @@ instance.web.list.columns = new instance.web.Registry({
     'field.progressbar': 'instance.web.list.ProgressBar',
     'field.handle': 'instance.web.list.Handle',
     'button': 'instance.web.list.Button',
+    'field.many2onebutton': 'instance.web.list.Many2OneButton',
+    'field.many2many': 'instance.web.list.Many2Many'
 });
 instance.web.list.columns.for_ = function (id, field, node) {
     var description = _.extend({tag: node.tag}, field, node.attrs);
@@ -2152,7 +2188,7 @@ instance.web.list.Boolean = instance.web.list.Column.extend({
      * @private
      */
     _format: function (row_data, options) {
-        return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
+        return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>',
                  row_data[this.id].value ? 'checked="checked"' : '');
     }
 });
@@ -2164,20 +2200,24 @@ instance.web.list.Binary = instance.web.list.Column.extend({
      */
     _format: function (row_data, options) {
         var text = _t("Download");
-        var download_url = _.str.sprintf(
-                '/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
-                instance.session.session_id, options.model, this.id, options.id);
-        if (this.filename) {
-            download_url += '&filename_field=' + this.filename;
-            if (row_data[this.filename]) {
-                text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
-                        row_data[this.filename].value, {type: 'char'}));
+        var value = row_data[this.id].value;
+        var download_url;
+        if (value && value.substr(0, 10).indexOf(' ') == -1) {
+            download_url = "data:application/octet-stream;base64," + value;
+        } else {
+            download_url = this.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
+            if (this.filename) {
+                download_url += '&filename_field=' + this.filename;
             }
         }
-        return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
+        if (this.filename && row_data[this.filename]) {
+            text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
+                    row_data[this.filename].value, {type: 'char'}));
+        }
+        return _.template('<a href="<%-href%>"><%-text%></a> (<%-size%>)', {
             text: text,
             href: download_url,
-            size: row_data[this.id].value
+            size: instance.web.binary_to_binsize(value),
         });
     }
 });
@@ -2209,5 +2249,25 @@ instance.web.list.Handle = instance.web.list.Column.extend({
         return '<div class="oe_list_handle">';
     }
 });
+instance.web.list.Many2OneButton = instance.web.list.Column.extend({
+    _format: function (row_data, options) {
+        this.has_value = !!row_data[this.id].value;
+        this.icon = this.has_value ? 'gtk-yes' : 'gtk-no';
+        this.string = this.has_value ? _t('View') : _t('Create');
+        return QWeb.render('Many2OneButton.cell', {
+            'widget': this,
+            'prefix': instance.session.prefix,
+        });
+    },
+});
+instance.web.list.Many2Many = instance.web.list.Column.extend({
+    _format: function (row_data, options) {
+        if (!_.isEmpty(row_data[this.id].value)) {
+            // If value, use __display version for printing
+            row_data[this.id] = row_data[this.id + '__display'];
+        }
+        return this._super(row_data, options);
+    }
+});
 };
 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: