4 OpenERP Web 6.2 implements a unified facets-based search view instead
5 of the previous form-like search view (composed of buttons and
6 multiple fields). The goal for this change is twofold:
8 * Avoid the common issue of users confusing the search view with a
9 form view and trying to create their records through it (or entering
10 all their data, hitting the ``Create`` button expecting their record
11 to be created and losing everything).
13 * Improve the looks and behaviors of the view, and the fit within
14 OpenERP Web's new design.
16 The internal structure of the faceted search is inspired by
17 `VisualSearch <http://documentcloud.github.com/visualsearch/>`_
20 As does VisualSearch, the new search view is based on `Backbone`_ and
21 makes significant use of Backbone's models and collections (OpenERP
22 Web's widgets make a good replacement for Backbone's own views). As a
23 result, understanding the implementation details of the OpenERP Web 7
24 search view also requires a basic understanding of Backbone's models,
25 collections and events.
29 This document may mention *fetching* data. This is a shortcut for
30 "returning a :js:class:`Deferred` to [whatever is being
31 fetched]". Unless further noted, the function or method may opt to
32 return nothing by fetching ``null`` (which can easily be done by
33 returning ``$.when(null)``, which simply wraps the ``null`` in a
36 Working with the search view: creating new inputs
37 -------------------------------------------------
39 The primary component of search views, as with all other OpenERP
40 views, are inputs. The search view has two types of inputs — filters
41 and fields — but only one is easly customizable: fields.
43 The mapping from OpenERP field types (and widgets) to search view
44 objects is stored in the ``openerp.web.search.fields``
45 :js:class:`~openerp.web.Registry` where new field types and widgets
48 Search view inputs have four main roles:
53 Once the search view has initialized all its inputs, it will call
54 :js:func:`~openerp.web.search.Input.facet_for_defaults` on each input,
55 passing it a mapping (a javascript object) of ``name:value`` extracted
56 from the action's context.
58 This method should fetch a :js:class:`~openerp.web.search.Facet` (or
59 an equivalent object) for the field's default value if applicable (if
60 a default value for the field is found in the ``defaults`` mapping).
62 A default implementation is provided which checks if ``defaults``
63 contains a non-falsy value for the field's ``@name`` and calls
64 :js:func:`openerp.web.search.Input.facet_for` with that value.
66 There is no default implementation of
67 :js:func:`openerp.web.search.Input.facet_for` [#no_impl]_, but
68 :js:class:`openerp.web.search.Field` provides one, which uses the
69 value as-is to fetch a :js:class:`~openerp.web.search.Facet`.
74 An important component of the new search view is the auto-completion
75 pane, and the task of providing completion items is delegated to
76 inputs through the :js:func:`~openerp.web.search.Input.complete`
79 This method should take a single argument (the string being typed by
80 the user) and should fetch an ``Array`` of possible completions
83 A default implementation is provided which fetches nothing.
85 A completion item is a javascript object with two keys (technically it
86 can have any number of keys, but only these two will be used by the
91 The string which will be displayed in the completion pane. It may
92 be formatted using HTML (inline only), as a result if ``value`` is
93 interpolated into it it *must* be escaped. ``_.escape`` can be
98 Either a :js:class:`~openerp.web.search.Facet` object or (more
99 commonly) the corresponding attributes object. This is the facet
100 which will be inserted into the search query if the completion
101 item is selected by the user.
103 If the ``facet`` is not provided (not present, ``null``, ``undefined``
104 or any other falsy value), the completion item will not be selectable
105 and will act as a section title of sort (the ``label`` will be
106 formatted differently). If an input *may* fetch multiple completion
107 items, it *should* prefix those with a section title using its own
108 name. This has no technical consequence but is clearer for users.
110 Providing drawer/supplementary UI
111 +++++++++++++++++++++++++++++++++
113 For some inputs (fields or not), interaction via autocompletion may be
114 awkward or even impossible.
116 These may opt to being rendered in a "drawer" as well or instead. In
117 that case, they will undergo the normal widget lifecycle and be
118 rendered inside the drawer.
120 .. Found no good type-based way to handle this, since there is no MI
121 (so no type-tagging) and it's possible for both Field and non-Field
122 input to be put into the drawer, for whatever reason (e.g. some
123 sort of auto-detector completion item for date widgets, but a
124 second more usual calendar widget in the drawer for more
125 obvious/precise interactions)
127 Any input can note its desire to be rendered in the drawer by
128 returning a truthy value from
129 :js:func:`~openerp.web.search.Input.in_drawer`.
131 By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the
132 value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is
133 ``false``. The behavior can be toggled either by redefining the
134 attribute to ``true`` (either on the class or on the input), or by
135 overriding :js:func:`~openerp.web.search.Input.in_drawer` itself.
137 The input will be rendered in the full width of the drawer, it will be
138 started only once (per view).
140 .. todo:: drawer API (if a widget wants to close the drawer in some
141 way), part of the low-level SearchView API/interactions?
144 .. todo:: handle filters and filter groups via a "driver" input which
145 dynamically collects, lays out and renders filters? =>
146 exercises drawer thingies
148 Programmatic interactions: internal model
149 -----------------------------------------
151 This new searchview is built around an instance of
152 :js:class:`~openerp.web.search.SearchQuery` available as
153 :js:attr:`openerp.web.SearchView.query`.
155 The query is a `backbone collection`_ of
156 :js:class:`~openerp.web.search.Facet` objects, which can be interacted
157 with directly by external objects or search view controls
158 (e.g. widgets displayed in the drawer).
160 .. js:class:: openerp.web.search.SearchQuery
162 The current search query of the search view, provides convenience
163 behaviors for manipulating :js:class:`~openerp.web.search.Facet`
164 on top of the usual `backbone collection`_ methods.
166 The query ensures all of its facets contain at least one
167 :js:class:`~openerp.web.search.FacetValue` instance. Otherwise,
168 the facet is automatically removed from the query.
170 .. js:function:: openerp.web.search.SearchQuery.add(values, options)
172 Overridden from the base ``add`` method so that adding a facet
173 which is *already* in the collection will merge the value of
174 the new facet into the old one rather than add a second facet
175 with different values.
177 :param values: facet, facet attributes or array thereof
178 :returns: the collection itself
180 .. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
182 Convenience method for toggling facet values in a query:
183 removes the values (through the facet itself) if they are
184 present, adds them if they are not. If the facet itself is not
185 in the collection, adds it automatically.
187 A toggling is atomic: only one change event will be triggered
188 on the facet regardless of the number of values added to or
189 removed from the facet (if the facet already exists), and the
190 facet is only removed from the query if it has no value *at
191 the end* of the toggling.
193 :param value: facet or facet attributes
194 :returns: the collection
196 .. js:class:: openerp.web.search.Facet
198 A `backbone model`_ representing a single facet of the current
199 research. May map to a search field, or to a more complex or
200 fuzzier input (e.g. a custom filter or an advanced search).
202 .. js:attribute:: category
204 The displayed name of the facet, as a ``String``. This is a
205 backbone model attribute.
207 .. js:attribute:: field
209 The :js:class:`~openerp.web.search.Input` instance which
210 originally created the facet [#facet-field]_, used to delegate
211 some operations (such as serializing the facet's values to
212 domains and contexts). This is a backbone model attribute.
214 .. js:attribute:: values
216 :js:class:`~openerp.web.search.FacetValues` as a javascript
217 attribute, stores all the values for the facet and helps
218 propagate their events to the facet. Is also available as a
219 backbone attribute (via ``#get`` and ``#set``) in which cases
220 it serializes to and deserializes from javascript arrays (via
221 ``Collection#toJSON`` and ``Collection#reset``).
223 .. js:attribute:: [icon]
225 optional, a single ASCII letter (a-z or A-Z) mapping to the
226 bundled mnmliconsRegular icon font.
228 When a facet with an ``icon`` attribute is rendered, the icon
229 is displayed (in the icon font) in the first section of the
230 facet instead of the ``category``.
232 By default, only filters make use of this facility.
234 .. js:class:: openerp.web.search.FacetValues
236 `Backbone collection`_ of
237 :js:class:`~openerp.web.search.FacetValue` instances.
239 .. js:class:: openerp.web.search.FacetValue
241 `Backbone model`_ representing a single value within a facet,
242 represents a pair of (displayed name, logical value).
244 .. js:attribute:: label
246 Backbone model attribute storing the "displayable"
247 representation of the value, visually output to the
248 user. Must be a string.
250 .. js:attribute:: value
252 Backbone model attribute storing the logical/internal value
253 (of itself), will be used by
254 :js:class:`~openerp.web.search.Input` to serialize to domains
259 Converting from facet objects
260 -----------------------------
262 Ultimately, the point of the search view is to allow searching. In
263 OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
264 the other hand, the OpenERP Web 7 search view's state is modelled
265 after a collection of :js:class:`~openerp.web.search.Facet`, and each
266 field of a search view may have special requirements when it comes to
267 the domains it produces [#special]_.
269 So there needs to be some way of mapping
270 :js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
272 This is done via an input's
273 :js:func:`~openerp.web.search.Input.get_domain` and
274 :js:func:`~openerp.web.search.Input.get_context`. Each takes a
275 :js:class:`~openerp.web.search.Facet` and returns whatever it's
276 supposed to generate (a domain or a context, respectively). Either can
277 return ``null`` if the current value does not map to a domain or
278 context, and can throw an :js:class:`~openerp.web.search.Invalid`
279 exception if the value is not valid at all for the field.
283 The :js:class:`~openerp.web.search.Facet` object can have any
284 number of values (from 1 upwards)
288 There is a third conversion method,
289 :js:func:`~openerp.web.search.Input.get_groupby`, which returns an
290 ``Array`` of groupby domains rather than a single context. At this
291 point, it is only implemented on (and used by) filters.
296 :js:class:`~openerp.web.search.Field` provides a default
297 implementation of :js:func:`~openerp.web.search.Input.get_domain` and
298 :js:func:`~openerp.web.search.Input.get_context` taking care of most
299 of the peculiarities pertaining to OpenERP's handling of fields in
300 search views. It also provides finer hooks to let developers of new
301 fields and widgets customize the behavior they want without
302 necessarily having to reimplement all of
303 :js:func:`~openerp.web.search.Input.get_domain` or
304 :js:func:`~openerp.web.search.Input.get_context`:
306 .. js:function:: openerp.web.search.Field.get_context(facet)
308 If the field has no ``@context``, simply returns
309 ``null``. Otherwise, calls
310 :js:func:`~openerp.web.search.Field.value_from` once for each
311 :js:class:`~openerp.web.search.FacetValue` of the current
312 :js:class:`~openerp.web.search.Facet` (in order to extract the
313 basic javascript object from the
314 :js:class:`~openerp.web.search.FacetValue` then evaluates
315 ``@context`` with each of these values set as ``self``, and
316 returns the union of all these contexts.
319 :type facet: openerp.web.search.Facet
320 :returns: a context (literal or compound)
322 .. js:function:: openerp.web.search.Field.get_domain(facet)
324 If the field has no ``@filter_domain``, calls
325 :js:func:`~openerp.web.search.Field.make_domain` once with each
326 :js:class:`~openerp.web.search.FacetValue` of the current
327 :js:class:`~openerp.web.search.Facet` as well as the field's
328 ``@name`` and either its ``@operator`` or
329 :js:attr:`~openerp.web.search.Field.default_operator`.
331 If the field has an ``@filter_value``, calls
332 :js:func:`~openerp.web.search.Field.value_from` once per
333 :js:class:`~openerp.web.search.FacetValue` and evaluates
334 ``@filter_value`` with each of these values set as ``self``.
336 In either case, "ors" all of the resulting domains (using ``|``)
337 if there is more than one
338 :js:class:`~openerp.web.search.FacetValue` and returns the union
342 :type facet: openerp.web.search.Facet
343 :returns: a domain (literal or compound)
345 .. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
347 Builds a literal domain from the provided data. Calls
348 :js:func:`~openerp.web.search.Field.value_from` on the
349 :js:class:`~openerp.web.search.FacetValue` and evaluates and sets
350 it as the domain's third value, uses the other two parameters as
351 the first two values.
353 Can be overridden to build more complex default domains.
355 :param String name: the field's name
356 :param String operator: the operator to use in the field's domain
358 :type facetValue: openerp.web.search.FacetValue
359 :returns: Array<(String, String, Object)>
361 .. js:function:: openerp.web.search.Field.value_from(facetValue)
363 Extracts a "bare" javascript value from the provided
364 :js:class:`~openerp.web.search.FacetValue`, and returns it.
366 The default implementation will simply return the ``value``
367 backbone property of the argument.
370 :type facetValue: openerp.web.search.FacetValue
373 .. js:attribute:: openerp.web.search.Field.default_operator
375 Operator used to build a domain when a field has no ``@operator``
376 or ``@filter_domain``. ``"="`` for
377 :js:class:`~openerp.web.search.Field`
379 Arbitrary data storage
380 ++++++++++++++++++++++
382 :js:class:`~openerp.web.search.Facet` and
383 :js:class:`~openerp.web.search.FacetValue` objects (and structures)
384 provided by your widgets should never be altered by the search view
385 (or an other widget). This means you are free to add arbitrary fields
386 in these structures if you need to (because you have more complex
387 needs than the attributes described in this document).
389 Ideally this should be avoided, but the possibility remains.
394 .. todo:: merge in changelog instead?
396 The displaying of the search view was significantly altered from
397 OpenERP Web 6.1 to OpenERP Web 7.
399 As a result, while the external API used to interact with the search
400 view does not change many internal details — including the interaction
401 between the search view and its widgets — were significantly altered:
406 * :js:func:`openerp.web.SearchView.do_clear` has been removed
407 * :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
412 * :js:func:`openerp.web.search.Widget.render` has been removed
414 * :js:func:`openerp.web.search.Widget.make_id` has been removed
416 * Search field objects are not openerp widgets anymore, their
417 ``start`` is not generally called
419 * :js:func:`~openerp.web.search.Input.clear` has been removed since
420 clearing the search view now simply consists of removing all search
423 * :js:func:`~openerp.web.search.Input.get_domain` and
424 :js:func:`~openerp.web.search.Input.get_context` now take a
425 :js:class:`~openerp.web.search.Facet` as parameter, from which it's
426 their job to get whatever value they want
428 * :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
429 an :js:class:`Array` of context-like constructs. By default, it does not do
430 anything in :js:class:`~openerp.web.search.Field` and it returns the various
431 contexts of its enabled filters in
432 :js:class:`~openerp.web.search.FilterGroup`.
437 * :js:func:`openerp.web.search.Filter.is_enabled` has been removed
439 * :js:class:`~openerp.web.search.FilterGroup` instances are still
440 rendered (and started) in the "advanced search" drawer.
445 * ``get_value`` has been replaced by
446 :js:func:`~openerp.web.search.Field.value_from` as it now takes a
447 :js:class:`~openerp.web.search.FacetValue` argument (instead of no
448 argument). It provides a default implementation returning the
449 ``value`` property of its argument.
451 * The third argument to
452 :js:func:`~openerp.web.search.Field.make_domain` is now a
453 :js:class:`~openerp.web.search.FacetValue` so child classes have all
454 the information they need to derive the "right" resulting domain.
459 Instead of being an intrinsic part of the search view, custom filters
460 are now a special case of filter groups. They are treated specially
461 still, but much less so than they used to be.
466 * Because the autocompletion service is now provided by the search
468 :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
474 * The advanced search is now a more standard
475 :js:class:`~openerp.web.search.Input` configured to be rendered in
478 * :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` are
479 now standard widgets, with the "right" behaviors (they don't rebind
480 their ``$element`` in ``start()``)
482 * The ad-hoc optional setting of the openerp field descriptor on a
483 :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` has
484 been removed, the field descriptor is now passed as second argument
486 :js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
487 constructor, and bound to its
488 :js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
490 * Instead of its former domain triplet ``(field, operator, value)``,
491 :js:func:`~openerp.web.search.ExtendedSearchProposition.get_proposition`
492 now returns an object with two fields ``label`` and ``value``,
493 respectively a human-readable version of the proposition and the
494 corresponding domain triplet for the proposition.
498 the original view was implemented on top of a monkey-patched
499 VisualSearch, but as our needs diverged from VisualSearch's goal
500 this made less and less sense ultimately leading to a clean-room
505 In case you are extending the search view with a brand new type of
510 Ideally this array should not hold more than about 10 items, but
511 the search view does not put any constraint on this at the
512 moment. Note that this may change.
516 ``field`` does not actually need to be an instance of
517 :js:class:`~openerp.web.search.Input`, nor does it need to be what
518 created the facet, it just needs to provide the three
519 facet-serialization methods
520 :js:func:`~openerp.web.search.Input.get_domain`,
521 :js:func:`~openerp.web.search.Input.get_context` and
522 :js:func:`~openerp.web.search.Input.get_gropuby`, existing
523 :js:class:`~openerp.web.search.Input` subtypes merely provide
524 convenient base implementation for those methods.
526 Complex search view inputs (especially those living in the drawer)
527 may prefer using object literals with the right slots returning
528 closed-over values or some other scheme un-bound to an actual
529 :js:class:`~openerp.web.search.Input`, as
530 :js:class:`~openerp.web.search.CustomFilters` and
531 :js:class:`~openerp.web.search.Advanced` do.
535 search view fields may also bundle context data to add to the
539 http://documentcloud.github.com/backbone/
541 .. _Backbone.Collection:
542 .. _Backbone collection:
543 http://documentcloud.github.com/backbone/#Collection
546 http://documentcloud.github.com/backbone/#Model
548 .. _commit 3fca87101d:
549 https://github.com/documentcloud/visualsearch/commit/3fca87101d