[IMP] work on inline search view in web client
authorGéry Debongnie <ged@odoo.com>
Mon, 19 May 2014 12:16:09 +0000 (14:16 +0200)
committerGéry Debongnie <ged@odoo.com>
Mon, 19 May 2014 12:16:09 +0000 (14:16 +0200)
incomplete work.  So far, the search view widget has been split in
two widgets: SearchView and SearchViewDrawer.  The SearchViewDrawer
has been inserted inline, and some preliminary work has been done
to improve the layout.

addons/mail/static/src/js/mail.js
addons/mail/static/src/xml/mail.xml
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
addons/web/static/src/js/search.js
addons/web/static/src/js/views.js
addons/web/static/src/xml/base.xml

index 99a7751..9cf3320 100644 (file)
@@ -1888,7 +1888,10 @@ openerp.mail = function (session) {
             var self = this;
             var ds_msg = new session.web.DataSetSearch(this, 'mail.message');
             this.searchview = new session.web.SearchView(this, ds_msg, false, defaults || {}, false);
-            this.searchview.appendTo(this.$('.oe_view_manager_view_search'))
+            this.searchview_drawer = new session.web.SearchViewDrawer(this, this.searchview);
+
+            $.when(this.searchview.appendTo(this.$('.oe_view_manager_view_search')),
+                    this.searchview_drawer.appendTo(this.$('.oe_searchview_drawer_container')))
                 .then(function () { self.searchview.on('search_data', self, self.do_searchview_search); });
             if (this.searchview.has_defaults) {
                 this.searchview.ready.then(this.searchview.do_search);
index ec7b0fa..77aeef0 100644 (file)
             </tr>
           </tbody>
         </table>
+        <div class="oe_searchview_drawer_container"/>
         <div class="oe_mail-placeholder"></div>
         <aside class="oe_mail_wall_aside"></aside>
     </div>
index f02dc3c..03066c1 100644 (file)
@@ -1,4 +1,4 @@
-@charset "utf-8";
+@charset "UTF-8";
 @font-face {
   font-family: "mnmliconsRegular";
   src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot");
   margin: 0 0 0 4px;
   padding: 0;
 }
+.openerp .oe_searchview_drawer_container {
+  border-bottom: 1px solid #afafb6;
+}
 .openerp .oe_view_nocontent {
   padding: 15px;
   margin-top: 0;
index c15c90a..e129692 100644 (file)
@@ -1280,7 +1280,7 @@ $sheet-padding: 16px
         &.oe_searchview_open_drawer
             .oe_searchview_drawer
                 display: block
-
+                
         .oe_searchview_drawer
             cursor: default
             position: absolute
@@ -1458,6 +1458,8 @@ $sheet-padding: 16px
                 margin: 0 0 0 4px
                 padding: 0
 
+    .oe_searchview_drawer_container
+        border-bottom: 1px solid $tag-border
 
     // }}}
     // Views Common {{{
index 832faa4..88cf753 100644 (file)
@@ -340,7 +340,8 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         },
         'click .oe_searchview_unfold_drawer': function (e) {
             e.stopImmediatePropagation();
-            this.$el.toggleClass('oe_searchview_open_drawer');
+            if (this.searchview_drawer) 
+                this.searchview_drawer.toggle();
         },
         'keydown .oe_searchview_input, .oe_searchview_facet': function (e) {
             switch(e.which) {
@@ -383,14 +384,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         this.defaults = defaults || {};
         this.has_defaults = !_.isEmpty(this.defaults);
 
-        this.inputs = [];
-        this.controls = [];
-
         this.headless = this.options.hidden && !this.has_defaults;
 
         this.input_subviews = [];
 
         this.ready = $.Deferred();
+        this.drawer_ready = $.Deferred();
+        this.fields_view_get = $.Deferred();
     },
     start: function() {
         var self = this;
@@ -416,20 +416,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
             });
 
             this.alive($.when(load_view)).then(function (r) {
+                self.fields_view_get.resolve(r);
                 return self.search_view_loaded(r);
             }).fail(function () {
                 self.ready.reject.apply(null, arguments);
             });
         }
 
-        instance.web.bus.on('click', this, function(ev) {
-            if ($(ev.target).parents('.oe_searchview').length === 0) {
-                self.$el.removeClass('oe_searchview_open_drawer');
-            }
-        });
-
         return $.when(p, this.ready);
     },
+
+    set_drawer: function (drawer) {
+        this.searchview_drawer = drawer;
+    },
+
     show: function () {
         this.$el.show();
     },
@@ -463,14 +463,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
     },
 
     /**
-     * Sets up thingie where all the mess is put?
-     */
-    select_for_drawer: function () {
-        return _(this.inputs).filter(function (input) {
-            return input.in_drawer();
-        });
-    },
-    /**
      * Sets up search view's view-wide auto-completion widget
      */
     setup_global_completion: function () {
@@ -630,87 +622,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
         });
     },
 
-    /**
-     * Builds a list of widget rows (each row is an array of widgets)
-     *
-     * @param {Array} items a list of nodes to convert to widgets
-     * @param {Object} fields a mapping of field names to (ORM) field attributes
-     * @param {Object} [group] group to put the new controls in
-     */
-    make_widgets: function (items, fields, group) {
-        if (!group) {
-            group = new instance.web.search.Group(
-                this, 'q', {attrs: {string: _t("Filters")}});
-        }
-        var self = this;
-        var filters = [];
-        _.each(items, function (item) {
-            if (filters.length && item.tag !== 'filter') {
-                group.push(new instance.web.search.FilterGroup(filters, group));
-                filters = [];
-            }
-
-            switch (item.tag) {
-            case 'separator': case 'newline':
-                break;
-            case 'filter':
-                filters.push(new instance.web.search.Filter(item, group));
-                break;
-            case 'group':
-                self.make_widgets(item.children, fields,
-                    new instance.web.search.Group(group, 'w', item));
-                break;
-            case 'field':
-                var field = this.make_field(
-                    item, fields[item['attrs'].name], group);
-                group.push(field);
-                // filters
-                self.make_widgets(item.children, fields, group);
-                break;
-            }
-        }, this);
-
-        if (filters.length) {
-            group.push(new instance.web.search.FilterGroup(filters, this));
-        }
-    },
-    /**
-     * Creates a field for the provided field descriptor item (which comes
-     * from fields_view_get)
-     *
-     * @param {Object} item fields_view_get node for the field
-     * @param {Object} field fields_get result for the field
-     * @param {Object} [parent]
-     * @returns instance.web.search.Field
-     */
-    make_field: function (item, field, parent) {
-        // M2O combined with selection widget is pointless and broken in search views,
-        // but has been used in the past for unsupported hacks -> ignore it
-        if (field.type === "many2one" && item.attrs.widget === "selection"){
-            item.attrs.widget = undefined;
-        }
-        var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
-        if(obj) {
-            return new (obj) (item, field, parent || this);
-        } else {
-            console.group('Unknown field type ' + field.type);
-            console.error('View node', item);
-            console.info('View field', field);
-            console.info('In view', this);
-            console.groupEnd();
-            return null;
-        }
-    },
-
-    add_common_inputs: function() {
-        // add Filters to this.inputs, need view.controls filled
-        (new instance.web.search.Filters(this));
-        // add custom filters to this.inputs
-        this.custom_filters = new instance.web.search.CustomFilters(this);
-        // add Advanced to this.inputs
-        (new instance.web.search.Advanced(this));
-    },
-
     search_view_loaded: function(data) {
         var self = this;
         this.fields_view = data;
@@ -720,41 +631,28 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
                     "Got non-search view after asking for a search view: type %s, arch root %s",
                     data.type, data.arch.tag));
         }
-        this.make_widgets(
-            data['arch'].children,
-            data.fields);
-
-        this.add_common_inputs();
-
-        // build drawer
-        var drawer_started = $.when.apply(
-            null, _(this.select_for_drawer()).invoke(
-                'appendTo', this.$('.oe_searchview_drawer')));
-
-        
-        // load defaults
-        var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
-                'facet_for_defaults', this.defaults))
-            .then(this.proxy('setup_default_query'));
 
-        return $.when(drawer_started, defaults_fetched)
+        return this.drawer_ready
+            .then(this.proxy('setup_default_query'))
             .then(function () { 
                 self.trigger("search_view_loaded", data);
                 self.ready.resolve();
             });
     },
     setup_default_query: function () {
+        this.ready.resolve();
+        return;
         // Hacky implementation of CustomFilters#facet_for_defaults ensure
         // CustomFilters will be ready (and CustomFilters#filters will be
         // correctly filled) by the time this method executes.
-        var custom_filters = this.custom_filters.filters;
+        var custom_filters = this.searchview_drawer.custom_filters.filters;
         if (!this.options.disable_custom_filters && !_(custom_filters).isEmpty()) {
             // Check for any is_default custom filter
             var personal_filter = _(custom_filters).find(function (filter) {
                 return filter.user_id && filter.is_default;
             });
             if (personal_filter) {
-                this.custom_filters.toggle_filter(personal_filter, true);
+                this.searchview_drawer.custom_filters.toggle_filter(personal_filter, true);
                 return;
             }
 
@@ -762,7 +660,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
                 return !filter.user_id && filter.is_default;
             });
             if (global_filter) {
-                this.custom_filters.toggle_filter(global_filter, true);
+                this.searchview_drawer.custom_filters.toggle_filter(global_filter, true);
                 return;
             }
         }
@@ -875,6 +773,146 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
     }
 });
 
+instance.web.SearchViewDrawer = instance.web.Widget.extend({
+    template: "SearchViewDrawer",
+
+    init: function(parent, searchview) {
+        this._super(parent);
+        this.searchview = searchview;
+        this.searchview.set_drawer(this);
+        this.ready = searchview.drawer_ready;
+        this.controls = [];
+        this.inputs = [];
+    },
+
+    toggle: function () {
+        this.$el.toggle();
+    },
+
+    start: function() {
+        var filters_ready = this.searchview.fields_view_get
+                                .then(this.proxy('prepare_filters'));
+        return $.when(this._super(), filters_ready)
+                .then(this.proxy('notify_searchview'));
+    },
+    prepare_filters: function (data) {
+        this.make_widgets(
+            data['arch'].children,
+            data.fields);
+
+        this.add_common_inputs();
+
+        // build drawer
+        var drawer_started = $.when.apply(
+            null, _(this.select_for_drawer()).invoke(
+                'appendTo', this.$el));
+
+        
+        // load defaults
+        // var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
+        //         'facet_for_defaults', this.defaults))
+        //     .then(this.proxy('setup_default_query'));
+
+
+    },
+    notify_searchview: function () {
+        this.searchview.drawer_ready.resolve();
+
+        // with args...
+        // this.searchview.drawer_ready.resolve();
+    },
+    /**
+     * Sets up thingie where all the mess is put?
+     */
+    select_for_drawer: function () {
+        return _(this.inputs).filter(function (input) {
+            return input.in_drawer();
+        });
+    },
+
+    /**
+     * Builds a list of widget rows (each row is an array of widgets)
+     *
+     * @param {Array} items a list of nodes to convert to widgets
+     * @param {Object} fields a mapping of field names to (ORM) field attributes
+     * @param {Object} [group] group to put the new controls in
+     */
+    make_widgets: function (items, fields, group) {
+        if (!group) {
+            group = new instance.web.search.Group(
+                this, 'q', {attrs: {string: _t("Filters")}});
+        }
+        var self = this;
+        var filters = [];
+        _.each(items, function (item) {
+            if (filters.length && item.tag !== 'filter') {
+                group.push(new instance.web.search.FilterGroup(filters, group));
+                filters = [];
+            }
+
+            switch (item.tag) {
+            case 'separator': case 'newline':
+                break;
+            case 'filter':
+                filters.push(new instance.web.search.Filter(item, group));
+                break;
+            case 'group':
+                self.make_widgets(item.children, fields,
+                    new instance.web.search.Group(group, 'w', item));
+                break;
+            case 'field':
+                var field = this.make_field(
+                    item, fields[item['attrs'].name], group);
+                group.push(field);
+                // filters
+                self.make_widgets(item.children, fields, group);
+                break;
+            }
+        }, this);
+
+        if (filters.length) {
+            group.push(new instance.web.search.FilterGroup(filters, this));
+        }
+    },
+    /**
+     * Creates a field for the provided field descriptor item (which comes
+     * from fields_view_get)
+     *
+     * @param {Object} item fields_view_get node for the field
+     * @param {Object} field fields_get result for the field
+     * @param {Object} [parent]
+     * @returns instance.web.search.Field
+     */
+    make_field: function (item, field, parent) {
+        // M2O combined with selection widget is pointless and broken in search views,
+        // but has been used in the past for unsupported hacks -> ignore it
+        if (field.type === "many2one" && item.attrs.widget === "selection"){
+            item.attrs.widget = undefined;
+        }
+        var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
+        if(obj) {
+            return new (obj) (item, field, parent || this);
+        } else {
+            console.group('Unknown field type ' + field.type);
+            console.error('View node', item);
+            console.info('View field', field);
+            console.info('In view', this);
+            console.groupEnd();
+            return null;
+        }
+    },
+
+    add_common_inputs: function() {
+        // add Filters to this.inputs, need view.controls filled
+        (new instance.web.search.Filters(this));
+        // add custom filters to this.inputs
+        this.custom_filters = new instance.web.search.CustomFilters(this);
+        // add Advanced to this.inputs
+        (new instance.web.search.Advanced(this));
+    },
+
+});
+
 /**
  * Registry of search fields, called by :js:class:`instance.web.SearchView` to
  * find and instantiate its field widgets.
@@ -932,9 +970,10 @@ instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web
         this._super(parent);
         var ancestor = parent;
         do {
-            this.view = ancestor;
-        } while (!(ancestor instanceof instance.web.SearchView)
+            this.drawer = ancestor;
+        } while (!(ancestor instanceof instance.web.SearchViewDrawer)
                && (ancestor = (ancestor.getParent && ancestor.getParent())));
+        this.view = this.drawer.searchview;
     }
 });
 
@@ -956,7 +995,7 @@ instance.web.search.Group = instance.web.search.Widget.extend({
         this.name = attrs.string;
         this.children = [];
 
-        this.view.controls.push(this);
+        this.drawer.controls.push(this);
     },
     push: function (input) {
         this.children.push(input);
@@ -977,7 +1016,7 @@ instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instan
     init: function (parent) {
         this._super(parent);
         this.load_attrs({});
-        this.view.inputs.push(this);
+        this.drawer.inputs.push(this);
     },
     /**
      * Fetch auto-completion values for the widget.
@@ -1814,22 +1853,13 @@ instance.web.search.Filters = instance.web.search.Input.extend({
     _in_drawer: true,
     start: function () {
         var self = this;
-        var running_count = 0;
-        // get total filters count
         var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
-        var visible_filters = _(this.view.controls).chain().reject(function (group) {
+        var visible_filters = _(this.drawer.controls).chain().reject(function (group) {
             return _(_(group.children).filter(is_group)).isEmpty()
                 || group.modifiers.invisible;
         });
-        var filters_count = visible_filters
-            .pluck('children')
-            .flatten()
-            .filter(is_group)
-            .map(function (i) { return i.filters.length; })
-            .sum()
-            .value();
 
-        var col1 = [], col2 = visible_filters.map(function (group) {
+        var groups = visible_filters.map(function (group) {
                 var filters = _(group.children).filter(is_group);
                 return {
                     name: _.str.sprintf("<span class='oe_i'>%s</span> %s",
@@ -1840,27 +1870,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({
                 };
             }).value();
 
-        while (col2.length) {
-            // col1 + group should be smaller than col2 + group
-            if ((running_count + col2[0].length) <= (filters_count - running_count)) {
-                running_count += col2[0].length;
-                col1.push(col2.shift());
-            } else {
-                break;
-            }
-        }
+        var $dl = $('<dl class="dl-horizontal">').appendTo(this.$el);
 
-        return $.when(
-            this.render_column(col1, $('<div>').appendTo(this.$el)),
-            this.render_column(col2, $('<div>').appendTo(this.$el)));
-    },
-    render_column: function (column, $el) {
-        return $.when.apply(null, _(column).map(function (group) {
-            $('<h3>').html(group.name).appendTo($el);
-            return $.when.apply(null,
-                _(group.filters).invoke('appendTo', $el));
-        }));
-    }
+        var rendered_lines = _.map(groups, function (group) {
+            $('<dt>').html(group.name).appendTo($dl);
+            var $dd = $('<dd>').appendTo($dl);
+            return $.when.apply(null, _(group.filters).invoke('appendTo', $dd));
+        });
+
+        return $.when.apply(this, rendered_lines);
+    },
 });
 
 instance.web.search.Advanced = instance.web.search.Input.extend({
index 753d52d..f954ebd 100644 (file)
@@ -553,6 +553,7 @@ instance.web.ViewManager =  instance.web.Widget.extend({
         this.model = dataset ? dataset.model : undefined;
         this.dataset = dataset;
         this.searchview = null;
+        this.searchview_drawer = null;
         this.active_view = null;
         this.views_src = _.map(views, function(x) {
             if (x instanceof Array) {
@@ -800,14 +801,20 @@ instance.web.ViewManager =  instance.web.Widget.extend({
         if (this.searchview) {
             this.searchview.destroy();
         }
+        if (this.searchview_drawer) {
+            this.searchview_drawer.destroy();
+        }
+
         var options = {
             hidden: this.flags.search_view === false,
             disable_custom_filters: this.flags.search_disable_custom_filters,
         };
         this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, options);
+        this.searchview_drawer = new instance.web.SearchViewDrawer(this, this.searchview);
 
         this.searchview.on('search_data', self, this.do_searchview_search);
-        return this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search"));
+        return $.when(this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")),
+                      this.searchview_drawer.appendTo(this.$(".oe_searchview_drawer_container")));
     },
     do_searchview_search: function(domains, contexts, groupbys) {
         var self = this,
index f801e6c..22e344f 100644 (file)
             </tr>
         </table>
 
+        <div class="oe_searchview_drawer_container"/>
+
         <div class="oe_view_manager_body">
             <t t-foreach="widget.views_src" t-as="view">
                 <div t-attf-class="oe_view_manager_view_#{view.view_type}"/>
     <div class="oe_searchview_facets"/>
     <div class="oe_searchview_clear"/>
     <div class="oe_searchview_unfold_drawer" title="Advanced Search..."/>
-    <div class="oe_searchview_drawer"/>
     <button type="button" class="oe_searchview_search"
             title="Search Again">Search</button>
 </div>
 
+<div t-name="SearchViewDrawer" class="oe_searchview_drawer" style="display:none;">
+</div>
+
 <div t-name="SearchView.InputView"
      class="oe_searchview_input"
      contenteditable="true"