}
},
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') {
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);
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) {
* 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);
});
},
/**
- * 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()) {
})), 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();
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; }
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) {
['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;
}));
});
},
+ 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();
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) {
--- /dev/null
+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.