[ADD] basic loading of default values in faceted search view
authorXavier Morel <xmo@openerp.com>
Tue, 20 Mar 2012 10:28:46 +0000 (11:28 +0100)
committerXavier Morel <xmo@openerp.com>
Tue, 20 Mar 2012 10:28:46 +0000 (11:28 +0100)
bzr revid: xmo@openerp.com-20120320102846-g2ymabvt4qc4bwzg

.bzrignore
addons/web/static/src/js/search.js
doc/index.rst
doc/search-view.rst [new file with mode: 0644]

index 62f8a05..f4d605a 100644 (file)
@@ -1,23 +1,13 @@
-*.pyc
-.*.swp
-.bzrignore
-openerp/addons/*
-openerp/filestore*
-.Python
-include
-lib
-bin/activate
-bin/activate_this.py
-bin/easy_install
-bin/easy_install-2.6
-bin/pip
-bin/python
-bin/python2.6
-*.pyc
-*.pyo
+.*
+*.egg-info
+*.orig
 build/
-bin/yolk
-bin/pil*.py
-.project
-.pydevproject
-.settings
+RE:^bin/
+RE:^dist/
+RE:^include/
+
+RE:^share/
+RE:^man/
+RE:^lib/
+
+RE:^doc/_build/
index 72c8772..162087f 100644 (file)
@@ -314,6 +314,7 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search
         }
     },
     on_loaded: function(data) {
+        var self = this;
         this.fields_view = data.fields_view;
         if (data.fields_view.type !== 'search' ||
             data.fields_view.arch.tag !== 'search') {
@@ -322,12 +323,16 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search
                     data.fields_view.type, data.fields_view.arch.tag));
         }
 
-        var self = this,
-           lines = this.make_widgets(
+        this.make_widgets(
                 data.fields_view['arch'].children,
                 data.fields_view.fields);
 
-        return this.ready.resolve().promise();
+        // load defaults
+        return $.when.apply(null, _(this.inputs).invoke('facet_for_defaults', this.defaults))
+            .then(function () {
+                self.vs.searchQuery.reset(_(arguments).compact());
+                self.ready.resolve();
+        });
 
         // for extended search view
         var ext = new openerp.web.search.ExtendedSearch(this, this.model);
@@ -730,10 +735,6 @@ openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web
     destroy: function () {
         delete this.view;
         this._super();
-    },
-    render: function (defaults) {
-        // FIXME
-        return this._super(_.extend(this, {defaults: defaults}));
     }
 });
 openerp.web.search.add_expand_listener = function($root) {
@@ -781,11 +782,29 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.
      * label, value prefixed with an object with keys type=section and label
      *
      * @param {String} value value to complete
-     * @returns {jQuery.Deferred<null|Object>}
+     * @returns {jQuery.Deferred<null|Array>}
      */
     complete: function (value) {
         return $.when(null)
     },
+    /**
+     * Returns a VS.model.SearchFacet instance for the provided defaults if
+     * they apply to this widget, or null if they don't.
+     *
+     * This default implementation will try calling
+     * :js:func:`openerp.web.search.Input#facet_for` if the widget's name
+     * matches the input key
+     *
+     * @param {Object} defaults
+     * @returns {jQuery.Deferred<null|Object>}
+     */
+    facet_for_defaults: function (defaults) {
+        if (!this.attrs ||
+            !(this.attrs.name in defaults && defaults[this.attrs.name])) {
+            return $.when(null);
+        }
+        return this.facet_for(defaults[this.attrs.name]);
+    },
     get_context: function () {
         throw new Error(
             "get_context not implemented for widget " + this.attrs.type);
@@ -880,25 +899,17 @@ openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.w
         });
     },
     /**
-     * Returns whether the filter is currently enabled (in use) or not.
-     *
-     * @returns a boolean
-     */
-    is_enabled:function () {
-        return this.$element.hasClass('enabled');
-    },
-    /**
      * If the filter is present in the defaults (and has a truthy value),
      * enable the filter.
      *
      * @param {Object} defaults the search view's default values
      */
-    render: function (defaults) {
-        if (this.attrs.name && defaults[this.attrs.name]) {
-            this.classes.push('enabled');
-            this.view.do_toggle_filter(this, true);
-        }
-        return this._super(defaults);
+    facet_for: function (value) {
+        return $.when(new VS.model.SearchFacet({
+            category: this.attrs.string || this.attrs.name,
+            value: 'true',
+            app: this.view.vs
+        }));
     },
     get_context: function () {
         if (!this.is_enabled()) {
@@ -939,6 +950,13 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
         })), view);
         this.make_id('input', field.type, this.attrs.name);
     },
+    facet_for: function (value) {
+        return $.when(new VS.model.SearchFacet({
+            category: this.attrs.name,
+            value: String(value),
+            app: this.view.vs
+        }));
+    },
     start: function () {
         this._super();
         this.filters.start();
@@ -1096,6 +1114,17 @@ openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends o
         return $.when.apply(null,
             [{type: 'section', label: this.attrs.string}].concat(results));
     },
+    facet_for: function (value) {
+        var match = _(this.attrs.selection).detect(function (sel) {
+            return sel[0] === value;
+        });
+        if (!match) { return $.when(null); }
+        return $.when(new VS.model.SearchFacet({
+            category: this.attrs.name,
+            value: match[1],
+            app: this.view.vs
+        }));
+    },
     get_value: function () {
         var index = parseInt(this.$element.val(), 10);
         if (isNaN(index)) { return null; }
@@ -1103,20 +1132,6 @@ openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends o
         if (value === false) { return null; }
         return value;
     },
-    /**
-     * The selection field needs a default ``false`` value in case none is
-     * provided, so that selector options with a ``false`` value (convention
-     * for explicitly empty options) get selected by default rather than the
-     * first (value-holding) option in the selection.
-     *
-     * @param {Object} defaults search default values
-     */
-    render: function (defaults) {
-        if (!defaults[this.attrs.name]) {
-            defaults[this.attrs.name] = false;
-        }
-        return this._super(defaults);
-    },
     clear: function () {
         var self = this, d = $.Deferred(), selection = this.attrs.selection;
         for(var index=0; index<selection.length; ++index) {
@@ -1146,23 +1161,6 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
             ['false', _t("No")]
         ];
     },
-    /**
-     * Search defaults likely to be boolean values (for a boolean field).
-     *
-     * In the HTML, we only want/get strings, and our strings here are ``true``
-     * and ``false``, so ensure we use precisely those by truth-testing the
-     * default value (iif there is one in the view's defaults).
-     *
-     * @param {Object} defaults default values for this search view
-     * @returns {String} rendered boolean field
-     */
-    render: function (defaults) {
-        var name = this.attrs.name;
-        if (name in defaults) {
-            defaults[name] = defaults[name] ? "true" : "false";
-        }
-        return this._super(defaults);
-    },
     get_value: function () {
         switch (this.$element.val()) {
             case 'false': return false;
@@ -1242,6 +1240,24 @@ openerp.web.search.ManyToOneField = openerp.web.search.CharField.extend({
                 }));
         });
     },
+    facet_for: function (value) {
+        var self = this;
+        if (value instanceof Array) {
+            return $.when(new VS.model.SearchFacet({
+                category: this.attrs.string,
+                value: value[1],
+                app: this.view.vs
+            }));
+        }
+        return new openerp.web.Model(this.attrs.relation)
+            .call('name_get', [value], {}).pipe(function (names) {
+                return new VS.model.SearchFacet({
+                category: self.attrs.string,
+                value: names[0][1],
+                app: self.view.vs
+            });
+        })
+    },
     start: function () {
         this._super();
         this.setup_autocomplete();
@@ -1250,51 +1266,6 @@ openerp.web.search.ManyToOneField = openerp.web.search.CharField.extend({
                            function () { started.resolve(); });
         return started.promise();
     },
-    setup_autocomplete: function () {
-        var self = this;
-        this.$element.autocomplete({
-            source: function (req, resp) {
-                if (self.abort_last) {
-                    self.abort_last();
-                    delete self.abort_last;
-                }
-                self.dataset.name_search(
-                    req.term, self.attrs.domain, 'ilike', 8, function (data) {
-                        resp(_.map(data, function (result) {
-                            return {id: result[0], label: result[1]}
-                        }));
-                });
-                self.abort_last = self.dataset.abort_last;
-            },
-            select: function (event, ui) {
-                self.id = ui.item.id;
-                self.name = ui.item.label;
-            },
-            delay: 0
-        })
-    },
-    on_name_get: function (name_get) {
-        if (!name_get.length) {
-            delete this.id;
-            this.got_name.reject();
-            return;
-        }
-        this.name = name_get[0][1];
-        this.got_name.resolve();
-    },
-    render: function (defaults) {
-        if (defaults[this.attrs.name]) {
-            this.id = defaults[this.attrs.name];
-            if (this.id instanceof Array)
-                this.id = this.id[0];
-            // TODO: maybe this should not be completely removed
-            delete defaults[this.attrs.name];
-            this.dataset.name_get([this.id], $.proxy(this, 'on_name_get'));
-        } else {
-            this.got_name.reject();
-        }
-        return this._super(defaults);
-    },
     make_domain: function (name, operator, value) {
         if (this.id && this.name) {
             if (value === this.name) {
index efa4d1c..815633a 100644 (file)
@@ -11,6 +11,8 @@ Contents:
 .. toctree::
    :maxdepth: 2
 
+   search-view
+
    getting-started
    production
    widgets
diff --git a/doc/search-view.rst b/doc/search-view.rst
new file mode 100644 (file)
index 0000000..1235572
--- /dev/null
@@ -0,0 +1,71 @@
+Search View
+===========
+
+Loading Defaults
+----------------
+
+After loading the view data, the SearchView will call
+:js:func:`openerp.web.search.Input.facet_for_defaults` with the ``defaults``
+mapping of key:values (where each key corresponds to an input).
+
+The default implementation is to check if there is a default value for the
+current input's name (via :js:attr:`openerp.web.search.Input.attrs.name`) and
+if there is to convert this value to a :js:class:`VS.models.SearchFacet` by
+calling :js:func:`openerp.web.search.Input.facet_for`.
+
+Both methods should return a ``jQuery.Deferred<Null|VS.model.SearchFacet>``.
+
+There is no built-in (default) implementation of
+:js:func:`openerp.web.search.Input.facet_for`.
+
+Providing auto-completion
+-------------------------
+
+An important component of the unified search view is the faceted autocompletion
+pane. In order to provide good user and developer experiences, this pane is
+pluggable (value-wise): each and every control of the search view can check for
+(and provide) categorized auto-completions for a given value being typed by
+the user.
+
+This is done by implementing :js:func:`openerp.web.search.Input.complete`: the
+method is provided with a value to complete, and the input must either return
+a ``jQuery.Deferred<Null>`` or fetch (by returning a ``jQuery.Deferred``) an
+array of completion values.
+
+.. todo:: describe the shape of "completion values"?
+
+Converting to and from facet objects
+------------------------------------
+
+Changes
+-------
+
+.. todo:: merge in changelog instead
+
+The displaying of the search view was significantly altered from OpenERP Web
+6.1 to OpenERP Web 6.2: it went form a form-like appearance (inherited from
+previous web client versions and ultimately from the GTK client) to a
+"universal" search input with facets.
+
+As a result, while the external API used to interact with the search view does
+not change the internal details — including the interaction between the search
+view and its widgets — is significantly altered:
+
+Widgets API
++++++++++++
+
+* :js:func:`openerp.web.search.Widget.render` has been removed
+* Search field objects are not openerp widgets anymore, their ``start`` is
+  not generally called
+
+Filters
++++++++
+
+* :js:func:`openerp.web.search.Filter.is_enabled` has been removed
+
+Many To One
++++++++++++
+
+* Because the autocompletion service is now provided by the search view
+  itself, :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
+  been removed.