[FIX] web: correctly init searchview when no action_id
[odoo/odoo.git] / addons / web / static / src / js / search.js
index 69bd4c1..312432d 100644 (file)
@@ -333,9 +333,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         },
         'click .oe_searchview_unfold_drawer': function (e) {
             e.stopImmediatePropagation();
-            $(e.target).toggleClass('fa-chevron-right')
-                       .toggleClass('fa-chevron-down');
-            localStorage.visible_search_menu = !(localStorage.visible_search_menu === 'true');
+            $(e.target).toggleClass('fa-caret-down fa-caret-up');
+            localStorage.visible_search_menu = (localStorage.visible_search_menu !== 'true');
             this.toggle_buttons();
         },
         'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
@@ -380,7 +379,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         this.search_fields = [];
         this.filters = [];
         this.groupbys = [];
-        this.visible_filters = !!(localStorage.visible_search_menu === 'true');
+        this.visible_filters = (localStorage.visible_search_menu === 'true');
         this.input_subviews = []; // for user input in searchbar
         this.defaults = defaults || {};
         this.headless = this.options.hidden &&  _.isEmpty(this.defaults);
@@ -388,7 +387,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
 
         this.filter_menu = undefined;
         this.groupby_menu = undefined;
-        this.favorite_menu = undefined
+        this.favorite_menu = undefined;
+        this.action_id = this.options && this.options.action && this.options.action.id;
     },    
     start: function() {
         if (this.headless) {
@@ -408,14 +408,14 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
             context: this.dataset.get_context(),
         });
         this.$('.oe_searchview_unfold_drawer')
-            .toggleClass('fa-chevron-right', !this.visible_filters)
-            .toggleClass('fa-chevron-down', this.visible_filters);
-        return $.when(this._super(), this.alive($.when(load_view))
-            .then(this.view_loaded.bind(this)));
+            .toggleClass('fa-caret-down', !this.visible_filters)
+            .toggleClass('fa-caret-up', this.visible_filters);
+        return this.alive($.when(this._super(), load_view.then(this.view_loaded.bind(this))));
     },
     view_loaded: function (r) {
         var self = this;
         this.fields_view_get = r;
+        this.view_id = this.view_id || r.view_id;
         this.prepare_search_inputs();
         if (this.$buttons) {
 
@@ -425,7 +425,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
 
             this.groupby_menu = new my.GroupByMenu(this, this.groupbys, fields_def);
             this.filter_menu = new my.FilterMenu(this, this.filters, fields_def);
-            this.favorite_menu = new my.FavoriteMenu(this, this.query, this.dataset.model);
+            this.favorite_menu = new my.FavoriteMenu(this, this.query, this.dataset.model, this.action_id);
 
             this.filter_menu.appendTo(this.$buttons);
             this.groupby_menu.appendTo(this.$buttons);
@@ -433,7 +433,61 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         }
         return $.when(custom_filters_ready).then(this.proxy('set_default_filters'));
     },
-    set_default_filters: function (a, b) {
+    // it should parse the arch field of the view, instantiate the corresponding 
+    // filters/fields, and put them in the correct variables:
+    // * this.search_fields is a list of all the fields,
+    // * this.filters: groups of filters
+    // * this.group_by: group_bys
+    prepare_search_inputs: function () {
+        var self = this,
+            arch = this.fields_view_get.arch;
+
+        var filters = [].concat.apply([], _.map(arch.children, function (item) {
+            return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item);
+        }));
+        function eval_item (item) {
+            var category = 'filters';
+            if (item.attrs.context) {
+                try {
+                    var context = instance.web.pyeval.eval('context', item.attrs.context);
+                    if (context.group_by) {
+                        category = 'group_by';
+                    }                    
+                } catch (e) {}
+            }
+            return {
+                item: item,
+                category: category,
+            }
+        }
+        var current_group = [],
+            current_category = 'filters',
+            categories = {filters: this.filters, group_by: this.groupbys};
+
+        _.each(filters.concat({category:'filters', item: 'separator'}), function (filter) {
+            if (filter.item.tag === 'filter' && filter.category === current_category) {
+                return current_group.push(new my.Filter(filter.item, self));
+            }
+            if (current_group.length) {
+                var group = new my.FilterGroup(current_group, self);
+                categories[current_category].push(group);
+                current_group = [];
+            }
+            if (filter.item.tag === 'field') {
+                var attrs = filter.item.attrs,
+                    field = self.fields_view_get.fields[attrs.name],
+                    Obj = my.fields.get_any([attrs.widget, field.type]);
+                if (Obj) {
+                    self.search_fields.push(new (Obj) (filter.item, field, self));
+                }
+            }
+            if (filter.item.tag === 'filter') {
+                current_group.push(new my.Filter(filter.item, self));
+            }
+            current_category = filter.category;
+        });
+    },
+    set_default_filters: function () {
         var self = this,
             default_custom_filter = this.$buttons && this.favorite_menu.get_default_filter();
         if (default_custom_filter) {
@@ -523,8 +577,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
             source: this.proxy('complete_global_search'),
             select: this.proxy('select_completion'),
             delay: 0,
-            get_search_string: function () { 
-                return self.$('div.oe_searchview_input').text(); 
+            get_search_string: function () {
+                return self.$('div.oe_searchview_input').text();
             },
         });
         this.autocomplete.appendTo(this.$el);
@@ -632,10 +686,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         this.$el.addClass('active');
     },
     childBlurred: function () {
-        var val = this.$el.val();
-        this.$el.val('');
-        this.$el.removeClass('active')
-                     .trigger('blur');
+        this.$el.val('').removeClass('active').trigger('blur');
         this.autocomplete.close();
     },
     /**
@@ -647,60 +698,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
     renderChangedFacets: function (model, options) {
         this.renderFacets(undefined, model, options);
     },
-    // it should parse the arch field of the view, instantiate the corresponding 
-    // filters/fields, and put them in the correct variables:
-    // * this.search_fields is a list of all the fields,
-    // * this.filters: groups of filters
-    // * this.group_by: group_bys
-    prepare_search_inputs: function () {
-        var self = this,
-            arch = this.fields_view_get.arch;
-
-        var filters = [].concat.apply([], _.map(arch.children, function (item) {
-            return item.tag !== 'group' ? eval_item(item) : item.children.map(eval_item);
-        }));
-        function eval_item (item) {
-            var category = 'filters';
-            if (item.attrs.context) {
-                try {
-                    var context = instance.web.pyeval.eval('context', item.attrs.context);
-                    if (context.group_by) {
-                        category = 'group_by';
-                    }                    
-                } catch (e) {}
-            }
-            return {
-                item: item,
-                category: category,
-            }
-        }
-        var current_group = [],
-            current_category = 'filters',
-            categories = {filters: this.filters, group_by: this.groupbys};
-
-        _.each(filters.concat({category:'filters', item: 'separator'}), function (filter) {
-            if (filter.item.tag === 'filter' && filter.category === current_category) {
-                return current_group.push(new my.Filter(filter.item, self));
-            }
-            if (current_group.length) {
-                var group = new my.FilterGroup(current_group, self);
-                categories[current_category].push(group);
-                current_group = [];
-            }
-            if (filter.item.tag === 'field') {
-                var attrs = filter.item.attrs,
-                    field = self.fields_view_get.fields[attrs.name],
-                    Obj = my.fields.get_any([attrs.widget, field.type]);
-                if (Obj) {
-                    self.search_fields.push(new (Obj) (filter.item, field, self));
-                }
-            }
-            if (filter.item.tag === 'filter') {
-                current_group.push(new my.Filter(filter.item, self));
-            }
-            current_category = filter.category;
-        });
-    },
 });
 
 /**
@@ -789,16 +786,6 @@ instance.web.search.Input = instance.web.Widget.extend( /** @lends instance.web.
      */
     visible: function () {
         return !this.attrs.modifiers.invisible;
-
-        // var parent = this;
-        // while ((parent = parent.getParent()) &&
-        //        (   (parent instanceof instance.web.search.Group)
-        //         || (parent instanceof instance.web.search.Input))) {
-        //     if (!parent.visible()) {
-        //         return false;
-        //     }
-        // }
-        // return true;
     },
 });
 instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
@@ -944,6 +931,11 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in
     toggle: function (filter, options) {
         this.searchview.query.toggle(this.make_facet([this.make_value(filter)]), options);
     },
+    is_visible: function () {
+        return _.some(this.filters, function (filter) {
+            return !filter.attrs.invisible;
+        });
+    },
     complete: function (item) {
         var self = this;
         item = item.toLowerCase();
@@ -1282,8 +1274,9 @@ instance.web.search.DateField = instance.web.search.Field.extend(/** @lends inst
         return instance.web.date_to_str(facetValue.get('value'));
     },
     complete: function (needle) {
-        var d = Date.parse(needle);
-        if (!d) { return $.when(null); }
+        var m = moment(needle);
+        if (!m.isValid()) { return $.when(null); }
+        var d = m.toDate();
         var date_string = instance.web.format_value(d, this.attrs);
         var label = _.str.sprintf(_.str.escapeHTML(
             _t("Search %(field)s at: %(value)s")), {
@@ -1397,7 +1390,10 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
         var values = facet.values;
         if (_.isEmpty(this.attrs.context) && values.length === 1) {
             var c = {};
-            c['default_' + this.attrs.name] = values.at(0).get('value');
+            var v = values.at(0);
+            if (v.get('operator') !== 'ilike') {
+                c['default_' + this.attrs.name] = v.get('value');
+            }
             return c;
         }
         return this._super(facet);
@@ -1442,13 +1438,19 @@ instance.web.search.FilterMenu = instance.web.Widget.extend({
         this.$apply_filter = this.$('.oe-apply-filter');
         this.$add_filter_menu = this.$('.oe-add-filter-menu');
         _.each(this.filters, function (group) {
-            group.insertBefore(self.$add_filter);
-            $('<li>').addClass('divider').insertBefore(self.$add_filter);
+            if (group.is_visible()) {
+                group.insertBefore(self.$add_filter);
+                $('<li class="divider">').insertBefore(self.$add_filter);
+            }
         });
         this.append_proposition().then(function (prop) {
             prop.$el.hide();
         });
     },
+    update_max_height: function () {
+        var max_height = $(window).height() - this.$menu[0].getBoundingClientRect().top - 10;
+        this.$menu.css('max-height', max_height);
+    },
     toggle_custom_filter_menu: function (is_open) {
         this.$add_filter
             .toggleClass('closed-menu', !is_open)
@@ -1458,6 +1460,7 @@ instance.web.search.FilterMenu = instance.web.Widget.extend({
             this.append_proposition();
         }
         this.$('.oe-filter-condition').toggle(is_open);
+        this.update_max_height();
     },
     append_proposition: function () {
         var self = this;
@@ -1466,29 +1469,29 @@ instance.web.search.FilterMenu = instance.web.Widget.extend({
             self.propositions.push(prop);
             prop.insertBefore(self.$add_filter_menu);
             self.$apply_filter.prop('disabled', false);
+            self.update_max_height();
             return prop;
         });
     },
     remove_proposition: function (prop) {
-        this.propositions = _.reject(this.propositions, function (p) { return p === prop});
+        this.propositions = _.without(this.propositions, prop);
         if (!this.propositions.length) {
             this.$apply_filter.prop('disabled', true);
         }
         prop.destroy();
     },
     commit_search: function () {
-        var self = this,
-            filters = _.invoke(this.propositions, 'get_filter'),
+        var filters = _.invoke(this.propositions, 'get_filter'),
             filters_widgets = _.map(filters, function (filter) {
-            return new my.Filter(filter, self);
-        });
-        var filter_group = new my.FilterGroup(filters_widgets, this.searchview);
+                return new my.Filter(filter, this);
+            }),
+            filter_group = new my.FilterGroup(filters_widgets, this.searchview),
+            facets = filters_widgets.map(function (filter) {
+                return filter_group.make_facet([filter_group.make_value(filter)]);
+            });
         filter_group.insertBefore(this.$add_filter);
-        $('<li>').addClass('divider').insertBefore(self.$add_filter);
-        
-        filters_widgets.forEach(function (filter) {
-            self.searchview.query.add(filter_group.make_facet([filter_group.make_value(filter)]), {silent: true});
-        });
+        $('<li class="divider">').insertBefore(this.$add_filter);
+        this.searchview.query.add(facets, {silent: true});
         this.searchview.query.trigger('reset');
 
         _.invoke(this.propositions, 'destroy');
@@ -1583,7 +1586,7 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
             this.close_menus();
         },
     },
-    init: function (parent, query, target_model) {
+    init: function (parent, query, target_model, action_id) {
         this._super.apply(this,arguments);
         this.searchview = parent;
         this.query = query;
@@ -1591,8 +1594,7 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
         this.model = new instance.web.Model('ir.filters');
         this.filters = {};
         this.$filters = {};
-        var action = instance.client.action_manager.inner_action;
-        this.action_id = action && action.id;
+        this.action_id = action_id;
     },
     start: function () {
         var self = this;
@@ -1600,9 +1602,9 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
         this.$save_name = this.$('.oe-save-name');
         this.$inputs = this.$save_name.find('input');
         this.$divider = this.$('.divider');
-
-        var $shared_filter = $(this.$inputs[1]),
-            $default_filter = $(this.$inputs[2]);
+        this.$inputs.eq(0).val(this.searchview.getParent().title);
+        var $shared_filter = this.$inputs.eq(1),
+            $default_filter = this.$inputs.eq(2);
         $shared_filter.click(function () {$default_filter.prop('checked', false)});
         $default_filter.click(function () {$shared_filter.prop('checked', false)});
 
@@ -1613,6 +1615,10 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
                 }
             })
             .on('reset', this.proxy('clear_selection'));
+        if (!this.action_id) {
+            this.prepare_dropdown_menu([]);
+            return $.when();
+        }
         return this.model.call('get_filters', [this.target_model, this.action_id])
             .done(this.proxy('prepare_dropdown_menu'));
     },
@@ -1634,8 +1640,8 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
     save_favorite: function () {
         var self = this,
             filter_name = this.$inputs[0].value,
-            shared_filter = this.$inputs[1].checked,
-            default_filter = this.$inputs[2].checked;
+            default_filter = this.$inputs[1].checked,
+            shared_filter = this.$inputs[2].checked;
         if (!filter_name.length){
             this.do_warn(_t("Error"), _t("Filter name is required."));
             this.$inputs.first().focus();
@@ -1734,8 +1740,7 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
     append_filter: function (filter) {
         var self = this,
             key = this.key_for(filter),
-            $filter,
-            warning = _t("This filter is global and will be removed for everybody if you continue.");
+            $filter;
 
         this.$divider.show();
         if (key in this.$filters) {
@@ -1751,15 +1756,14 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
             this.$filters[key].addClass(filter.user_id ? 'oe_searchview_custom_private'
                                          : 'oe_searchview_custom_public')
             $('<span>')
-                .addClass('fa fa-trash-o')
+                .addClass('fa fa-trash-o remove-filter')
                 .click(function (event) {
                     event.stopImmediatePropagation(); 
                     self.remove_filter(filter, $filter, key);
                 })
                 .appendTo($filter);
         }
-        this.$filters[key].unbind('click').click(function (event) {
-            event.stopPropagation();
+        this.$filters[key].unbind('click').click(function () {
             self.toggle_filter(filter);
         });
     },
@@ -1778,8 +1782,9 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
     },
     remove_filter: function (filter, $filter, key) {
         var self = this;
-        var warning = _t("This filter is global and will be removed for everybody if you continue.");
-        if (!(filter.user_id || confirm(warning))) {
+        var global_warning = _t("This filter is global and will be removed for everybody if you continue."),
+            warning = _t("Are you sure that you want to remove this filter?");
+        if (!confirm(filter.user_id ? warning : global_warning)) {
             return;
         }
         this.model.call('unlink', [filter.id]).done(function () {
@@ -1787,7 +1792,7 @@ instance.web.search.FavoriteMenu = instance.web.Widget.extend({
             delete self.$filters[key];
             delete self.filters[key];
             if (_.isEmpty(self.filters)) {
-                self.$('li.divider').remove();
+                self.$divider.hide();
             }
         });        
     },
@@ -2127,6 +2132,14 @@ instance.web.search.AutoComplete = instance.web.Widget.extend({
                 ev.preventDefault();
                 return;
             }
+            // ENTER is caugth at KeyUp rather than KeyDown to avoid firing
+            // before all regular keystrokes have been processed
+            if (ev.which === $.ui.keyCode.ENTER) {
+                if (self.current_result && self.get_search_string().length) {
+                    self.select_item(ev);
+                }
+                return;
+            }
             if (!self.searching) {
                 self.searching = true;
                 return;
@@ -2141,8 +2154,10 @@ instance.web.search.AutoComplete = instance.web.Widget.extend({
         });
         this.$input.on('keydown', function (ev) {
             switch (ev.which) {
+                // TAB and direction keys are handled at KeyDown because KeyUp
+                // is not guaranteed to fire.
+                // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13
                 case $.ui.keyCode.TAB:
-                case $.ui.keyCode.ENTER:
                     if (self.current_result && self.get_search_string().length) {
                         self.select_item(ev);
                     }