[FIX] domain and context generation from fields, also doc
bzr revid: xmo@openerp.com-20120503154311-4pg762pcgt83416c
This commit is contained in:
parent
6d12c155a4
commit
0c99ac1333
|
@ -1009,20 +1009,26 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
|
|||
values: [{label: String(value), value: value}]
|
||||
});
|
||||
},
|
||||
get_value: function (facet) {
|
||||
return facet.value();
|
||||
value_from: function (facetValue) {
|
||||
return facetValue.get('value');
|
||||
},
|
||||
get_context: function (facet) {
|
||||
// A field needs a value to be "active", and a context to send when
|
||||
// active
|
||||
var self = this;
|
||||
// A field needs a context to send when active
|
||||
var context = this.attrs.context;
|
||||
if (!context) {
|
||||
if (!context || !facet.values.length) {
|
||||
return;
|
||||
}
|
||||
var val = this.get_value(facet);
|
||||
var has_value = (val !== null && val !== '');
|
||||
return new instance.web.CompoundContext(context)
|
||||
.set_eval_context({self: val});
|
||||
var contexts = facet.values.map(function (facetValue) {
|
||||
return new instance.web.CompoundContext(context)
|
||||
.set_eval_context({self: self.value_from(facetValue)});
|
||||
});
|
||||
|
||||
if (contexts.length === 1) { return contexts[0]; }
|
||||
|
||||
return _.extend(instance.web.CompoundContext, {
|
||||
__contexts: contexts
|
||||
});
|
||||
},
|
||||
get_groupby: function () { },
|
||||
/**
|
||||
|
@ -1037,23 +1043,37 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc
|
|||
* @returns {Array<Array>} domain to include in the resulting search
|
||||
*/
|
||||
make_domain: function (name, operator, facet) {
|
||||
return [[name, operator, this.get_value(facet)]];
|
||||
return [[name, operator, this.value_from(facet)]];
|
||||
},
|
||||
get_domain: function (facet) {
|
||||
var val = this.get_value(facet);
|
||||
if (val === null || val === '') {
|
||||
return;
|
||||
if (!facet.values.length) { return; }
|
||||
|
||||
var value_to_domain;
|
||||
var self = this;
|
||||
var domain = this.attrs['filter_domain'];
|
||||
if (domain) {
|
||||
value_to_domain = function (facetValue) {
|
||||
return new instance.web.CompoundDomain(domain)
|
||||
.set_eval_context({self: self.value_from(facetValue)});
|
||||
};
|
||||
} else {
|
||||
value_to_domain = function (facetValue) {
|
||||
return self.make_domain(
|
||||
self.attrs.name,
|
||||
self.attrs.operator || self.default_operator,
|
||||
facetValue);
|
||||
};
|
||||
}
|
||||
var domains = facet.values.map(value_to_domain);
|
||||
|
||||
if (domains.length === 1) { return domains[0]; }
|
||||
for (var i = domains.length; --i;) {
|
||||
domains.unshift(['|']);
|
||||
}
|
||||
|
||||
var domain = this.attrs['filter_domain'];
|
||||
if (!domain) {
|
||||
return this.make_domain(
|
||||
this.attrs.name,
|
||||
this.attrs.operator || this.default_operator,
|
||||
facet);
|
||||
}
|
||||
return new instance.web.CompoundDomain(domain)
|
||||
.set_eval_context({self: val});
|
||||
return _.extend(new instance.web.CompoundDomain, {
|
||||
__domains: domains
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
|
@ -1085,7 +1105,7 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins
|
|||
}
|
||||
});
|
||||
instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{
|
||||
get_value: function () {
|
||||
value_from: function () {
|
||||
if (!this.$element.val()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1193,7 +1213,7 @@ instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends
|
|||
if (!match) { return $.when(null); }
|
||||
return $.when(facet_from(this, match));
|
||||
},
|
||||
get_value: function (facet) {
|
||||
value_from: function (facet) {
|
||||
return facet.get('values');
|
||||
}
|
||||
});
|
||||
|
@ -1209,7 +1229,7 @@ instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/**
|
|||
['false', _t("No")]
|
||||
];
|
||||
},
|
||||
get_value: function (facet) {
|
||||
value_from: function (facet) {
|
||||
switch (this._super(facet)) {
|
||||
case 'false': return false;
|
||||
case 'true': return true;
|
||||
|
@ -1222,7 +1242,7 @@ instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/**
|
|||
* @extends instance.web.search.DateField
|
||||
*/
|
||||
instance.web.search.DateField = instance.web.search.Field.extend(/** @lends instance.web.search.DateField# */{
|
||||
get_value: function (facet) {
|
||||
value_from: function (facet) {
|
||||
return openerp.web.date_to_str(facet.get('values'));
|
||||
},
|
||||
complete: function (needle) {
|
||||
|
@ -1255,7 +1275,7 @@ instance.web.search.DateField = instance.web.search.Field.extend(/** @lends inst
|
|||
* @extends instance.web.DateField
|
||||
*/
|
||||
instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @lends instance.web.search.DateTimeField# */{
|
||||
get_value: function (facet) {
|
||||
value_from: function (facet) {
|
||||
return openerp.web.datetime_to_str(facet.get('values'));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -770,6 +770,70 @@ $(document).ready(function () {
|
|||
});
|
||||
});
|
||||
|
||||
test('Field single value, default domain & context', function () {
|
||||
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
|
||||
var facet = new instance.web.search.Facet({
|
||||
field: f,
|
||||
values: [{value: 42}]
|
||||
});
|
||||
|
||||
deepEqual(f.get_domain(facet), [['foo', '=', 42]],
|
||||
"default field domain is a strict equality of name to facet's value");
|
||||
equal(f.get_context(facet), null,
|
||||
"default field context is null");
|
||||
});
|
||||
test('Field multiple values, default domain & context', function () {
|
||||
var f = new instance.web.search.Field({}, {name: 'foo'}, {inputs: []});
|
||||
var facet = new instance.web.search.Facet({
|
||||
field: f,
|
||||
values: [{value: 42}, {value: 68}, {value: 999}]
|
||||
});
|
||||
|
||||
var actual_domain = f.get_domain(facet);
|
||||
equal(actual_domain.__ref, "compound_domain",
|
||||
"multiple value should yield compound domain");
|
||||
deepEqual(actual_domain.__domains, [
|
||||
['|'],
|
||||
['|'],
|
||||
[['foo', '=', 42]],
|
||||
[['foo', '=', 68]],
|
||||
[['foo', '=', 999]]
|
||||
],
|
||||
"domain should OR a default domain for each value");
|
||||
equal(f.get_context(facet), null,
|
||||
"default field context is null");
|
||||
});
|
||||
test('Field single value, custom domain & context', function () {
|
||||
var f = new instance.web.search.Field({attrs:{
|
||||
context: "{'bob': self}",
|
||||
filter_domain: "[['edmund', 'is', self]]"
|
||||
}}, {name: 'foo'}, {inputs: []});
|
||||
var facet = new instance.web.search.Facet({
|
||||
field: f,
|
||||
values: [{value: "great"}]
|
||||
});
|
||||
|
||||
var actual_domain = f.get_domain(facet);
|
||||
equal(actual_domain.__ref, "compound_domain",
|
||||
"@filter_domain should yield compound domain");
|
||||
deepEqual(actual_domain.__domains, [
|
||||
"[['edmund', 'is', self]]"
|
||||
], 'should hold unevaluated custom domain');
|
||||
deepEqual(actual_domain.get_eval_context(), {
|
||||
self: "great"
|
||||
}, "evaluation context should hold facet value as self");
|
||||
|
||||
var actual_context = f.get_context(facet);
|
||||
equal(actual_context.__ref, "compound_context",
|
||||
"@context should yield compound context");
|
||||
deepEqual(actual_context.__contexts, [
|
||||
"{'bob': self}"
|
||||
], 'should hold unevaluated custom context');
|
||||
deepEqual(actual_context.get_eval_context(), {
|
||||
self: "great"
|
||||
}, "evaluation context should hold facet value as self");
|
||||
});
|
||||
|
||||
module('drawer', {
|
||||
setup: function () {
|
||||
instance = window.openerp.init([]);
|
||||
|
|
|
@ -277,6 +277,92 @@ exception if the value is not valid at all for the field.
|
|||
``Array`` of groupby domains rather than a single context. At this
|
||||
point, it is only implemented on (and used by) filters.
|
||||
|
||||
Field services
|
||||
++++++++++++++
|
||||
|
||||
:js:class:`~openerp.web.search.Field` provides a default
|
||||
implementation of :js:func:`~openerp.web.search.Input.get_domain` and
|
||||
:js:func:`~openerp.web.search.Input.get_context` taking care of most
|
||||
of the peculiarities pertaining to OpenERP's handling of fields in
|
||||
search views. It also provides finer hooks to let developers of new
|
||||
fields and widgets customize the behavior they want without
|
||||
necessarily having to reimplement all of
|
||||
:js:func:`~openerp.web.search.Input.get_domain` or
|
||||
:js:func:`~openerp.web.search.Input.get_context`:
|
||||
|
||||
.. js:function:: openerp.web.search.Field.get_context(facet)
|
||||
|
||||
If the field has no ``@context``, simply returns
|
||||
``null``. Otherwise, calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` once for each
|
||||
:js:class:`~openerp.web.search.FacetValue` of the current
|
||||
:js:class:`~openerp.web.search.Facet` (in order to extract the
|
||||
basic javascript object from the
|
||||
:js:class:`~openerp.web.search.FacetValue` then evaluates
|
||||
``@context`` with each of these values set as ``self``, and
|
||||
returns the union of all these contexts.
|
||||
|
||||
:param facet:
|
||||
:type facet: openerp.web.search.Facet
|
||||
:returns: a context (literal or compound)
|
||||
|
||||
.. js:function:: openerp.web.search.Field.get_domain(facet)
|
||||
|
||||
If the field has no ``@filter_domain``, calls
|
||||
:js:func:`~openerp.web.search.Field.make_domain` once with each
|
||||
:js:class:`~openerp.web.search.FacetValue` of the current
|
||||
:js:class:`~openerp.web.search.Facet` as well as the field's
|
||||
``@name`` and either its ``@operator`` or
|
||||
:js:attr:`~openerp.web.search.Field.default_operator`.
|
||||
|
||||
If the field has an ``@filter_value``, calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` once per
|
||||
:js:class:`~openerp.web.search.FacetValue` and evaluates
|
||||
``@filter_value`` with each of these values set as ``self``.
|
||||
|
||||
In either case, "ors" all of the resulting domains (using ``|``)
|
||||
if there is more than one
|
||||
:js:class:`~openerp.web.search.FacetValue` and returns the union
|
||||
of the result.
|
||||
|
||||
:param facet:
|
||||
:type facet: openerp.web.search.Facet
|
||||
:returns: a domain (literal or compound)
|
||||
|
||||
.. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
|
||||
|
||||
Builds a literal domain from the provided data. Calls
|
||||
:js:func:`~openerp.web.search.Field.value_from` on the
|
||||
:js:class:`~openerp.web.search.FacetValue` and evaluates and sets
|
||||
it as the domain's third value, uses the other two parameters as
|
||||
the first two values.
|
||||
|
||||
Can be overridden to build more complex default domains.
|
||||
|
||||
:param String name: the field's name
|
||||
:param String operator: the operator to use in the field's domain
|
||||
:param facetValue:
|
||||
:type facetValue: openerp.web.search.FacetValue
|
||||
:returns: Array<(String, String, Object)>
|
||||
|
||||
.. js:function:: openerp.web.search.Field.value_from(facetValue)
|
||||
|
||||
Extracts a "bare" javascript value from the provided
|
||||
:js:class:`~openerp.web.search.FacetValue`, and returns it.
|
||||
|
||||
The default implementation will simply return the ``value``
|
||||
backbone property of the argument.
|
||||
|
||||
:param facetValue:
|
||||
:type facetValue: openerp.web.search.FacetValue
|
||||
:returns: Object
|
||||
|
||||
.. js:attribute:: openerp.web.search.Field.default_operator
|
||||
|
||||
Operator used to build a domain when a field has no ``@operator``
|
||||
or ``@filter_domain``. ``"="`` for
|
||||
:js:class:`~openerp.web.search.Field`
|
||||
|
||||
Converting to facet objects
|
||||
---------------------------
|
||||
|
||||
|
@ -314,7 +400,7 @@ Widgets API
|
|||
|
||||
* :js:func:`~openerp.web.search.Input.get_domain` and
|
||||
:js:func:`~openerp.web.search.Input.get_context` now take a
|
||||
:js:class:`~VS.model.SearchFacet` as parameter, from which it's
|
||||
:js:class:`~openerp.web.search.Facet` as parameter, from which it's
|
||||
their job to get whatever value they want
|
||||
|
||||
* :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
|
||||
|
@ -334,19 +420,16 @@ Filters
|
|||
Fields
|
||||
++++++
|
||||
|
||||
* ``get_value`` now takes a :js:class:`~VS.model.SearchFacet` (instead
|
||||
of taking no argument).
|
||||
|
||||
A default implementation is provided as
|
||||
:js:func:`openerp.web.search.Field.get_value` and simply calls
|
||||
:js:func:`VS.model.SearchFacet.value`.
|
||||
* ``get_value`` has been replaced by
|
||||
:js:func:`~openerp.web.search.Field.value_from` as it now takes a
|
||||
:js:class:`~openerp.web.search.FacetValue` argument (instead of no
|
||||
argument). It provides a default implementation returning the
|
||||
``value`` property of its argument.
|
||||
|
||||
* The third argument to
|
||||
:js:func:`~openerp.web.search.Field.make_domain` is now the
|
||||
:js:class:`~VS.model.SearchFacet` received by
|
||||
:js:func:`~openerp.web.search.Field.get_domain`, so child classes
|
||||
have all the information they need to derive the "right" resulting
|
||||
domain.
|
||||
:js:func:`~openerp.web.search.Field.make_domain` is now a
|
||||
:js:class:`~openerp.web.search.FacetValue` so child classes have all
|
||||
the information they need to derive the "right" resulting domain.
|
||||
|
||||
Custom filters
|
||||
++++++++++++++
|
||||
|
@ -363,6 +446,13 @@ Many To One
|
|||
:js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
|
||||
been removed.
|
||||
|
||||
Advanced Search
|
||||
+++++++++++++++
|
||||
|
||||
The advanced search is now a more standard
|
||||
:js:class:`~openerp.web.search.Input` configured to be rendered in the
|
||||
drawer.
|
||||
|
||||
.. [#] the original view was implemented on top of a monkey-patched
|
||||
VisualSearch, but as our needs diverged from VisualSearch's goal this
|
||||
made less and less sense ultimately leading to a clean-room
|
||||
|
|
Loading…
Reference in New Issue