4 OpenERP Web 7.0 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.
112 If a field is :js:func:`invisible
113 <openerp.web.search.Input.visible>`, its completion function will
116 Providing drawer/supplementary UI
117 +++++++++++++++++++++++++++++++++
119 For some inputs (fields or not), interaction via autocompletion may be
120 awkward or even impossible.
122 These may opt to being rendered in a "drawer" as well or instead. In
123 that case, they will undergo the normal widget lifecycle and be
124 rendered inside the drawer.
126 .. Found no good type-based way to handle this, since there is no MI
127 (so no type-tagging) and it's possible for both Field and non-Field
128 input to be put into the drawer, for whatever reason (e.g. some
129 sort of auto-detector completion item for date widgets, but a
130 second more usual calendar widget in the drawer for more
131 obvious/precise interactions)
133 Any input can note its desire to be rendered in the drawer by
134 returning a truthy value from
135 :js:func:`~openerp.web.search.Input.in_drawer`.
137 By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the
138 value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is
139 ``false``. The behavior can be toggled either by redefining the
140 attribute to ``true`` (either on the class or on the input), or by
141 overriding :js:func:`~openerp.web.search.Input.in_drawer` itself.
143 The input will be rendered in the full width of the drawer, it will be
144 started only once (per view).
146 .. todo:: drawer API (if a widget wants to close the drawer in some
147 way), part of the low-level SearchView API/interactions?
150 .. todo:: handle filters and filter groups via a "driver" input which
151 dynamically collects, lays out and renders filters? =>
152 exercises drawer thingies
156 An :js:func:`invisible <openerp.web.search.Input.visible>` input
157 will not be inserted into the drawer.
159 Converting from facet objects
160 +++++++++++++++++++++++++++++
162 Ultimately, the point of the search view is to allow searching. In
163 OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
164 the other hand, the OpenERP Web 7 search view's state is modelled
165 after a collection of :js:class:`~openerp.web.search.Facet`, and each
166 field of a search view may have special requirements when it comes to
167 the domains it produces [#special]_.
169 So there needs to be some way of mapping
170 :js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
172 This is done via an input's
173 :js:func:`~openerp.web.search.Input.get_domain` and
174 :js:func:`~openerp.web.search.Input.get_context`. Each takes a
175 :js:class:`~openerp.web.search.Facet` and returns whatever it's
176 supposed to generate (a domain or a context, respectively). Either can
177 return ``null`` if the current value does not map to a domain or
178 context, and can throw an :js:class:`~openerp.web.search.Invalid`
179 exception if the value is not valid at all for the field.
183 The :js:class:`~openerp.web.search.Facet` object can have any
184 number of values (from 1 upwards)
188 There is a third conversion method,
189 :js:func:`~openerp.web.search.Input.get_groupby`, which returns an
190 ``Array`` of groupby domains rather than a single context. At this
191 point, it is only implemented on (and used by) filters.
193 Programmatic interactions: internal model
194 -----------------------------------------
196 This new searchview is built around an instance of
197 :js:class:`~openerp.web.search.SearchQuery` available as
198 :js:attr:`openerp.web.SearchView.query`.
200 The query is a `backbone collection`_ of
201 :js:class:`~openerp.web.search.Facet` objects, which can be interacted
202 with directly by external objects or search view controls
203 (e.g. widgets displayed in the drawer).
205 .. js:class:: openerp.web.search.SearchQuery
207 The current search query of the search view, provides convenience
208 behaviors for manipulating :js:class:`~openerp.web.search.Facet`
209 on top of the usual `backbone collection`_ methods.
211 The query ensures all of its facets contain at least one
212 :js:class:`~openerp.web.search.FacetValue` instance. Otherwise,
213 the facet is automatically removed from the query.
215 .. js:function:: openerp.web.search.SearchQuery.add(values, options)
217 Overridden from the base ``add`` method so that adding a facet
218 which is *already* in the collection will merge the value of
219 the new facet into the old one rather than add a second facet
220 with different values.
222 :param values: facet, facet attributes or array thereof
223 :returns: the collection itself
225 .. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
227 Convenience method for toggling facet values in a query:
228 removes the values (through the facet itself) if they are
229 present, adds them if they are not. If the facet itself is not
230 in the collection, adds it automatically.
232 A toggling is atomic: only one change event will be triggered
233 on the facet regardless of the number of values added to or
234 removed from the facet (if the facet already exists), and the
235 facet is only removed from the query if it has no value *at
236 the end* of the toggling.
238 :param value: facet or facet attributes
239 :returns: the collection
241 .. js:class:: openerp.web.search.Facet
243 A `backbone model`_ representing a single facet of the current
244 research. May map to a search field, or to a more complex or
245 fuzzier input (e.g. a custom filter or an advanced search).
247 .. js:attribute:: category
249 The displayed name of the facet, as a ``String``. This is a
250 backbone model attribute.
252 .. js:attribute:: field
254 The :js:class:`~openerp.web.search.Input` instance which
255 originally created the facet [#facet-field]_, used to delegate
256 some operations (such as serializing the facet's values to
257 domains and contexts). This is a backbone model attribute.
259 .. js:attribute:: values
261 :js:class:`~openerp.web.search.FacetValues` as a javascript
262 attribute, stores all the values for the facet and helps
263 propagate their events to the facet. Is also available as a
264 backbone attribute (via ``#get`` and ``#set``) in which cases
265 it serializes to and deserializes from javascript arrays (via
266 ``Collection#toJSON`` and ``Collection#reset``).
268 .. js:attribute:: [icon]
270 optional, a single ASCII letter (a-z or A-Z) mapping to the
271 bundled mnmliconsRegular icon font.
273 When a facet with an ``icon`` attribute is rendered, the icon
274 is displayed (in the icon font) in the first section of the
275 facet instead of the ``category``.
277 By default, only filters make use of this facility.
279 .. js:class:: openerp.web.search.FacetValues
281 `Backbone collection`_ of
282 :js:class:`~openerp.web.search.FacetValue` instances.
284 .. js:class:: openerp.web.search.FacetValue
286 `Backbone model`_ representing a single value within a facet,
287 represents a pair of (displayed name, logical value).
289 .. js:attribute:: label
291 Backbone model attribute storing the "displayable"
292 representation of the value, visually output to the
293 user. Must be a string.
295 .. js:attribute:: value
297 Backbone model attribute storing the logical/internal value
298 (of itself), will be used by
299 :js:class:`~openerp.web.search.Input` to serialize to domains
307 :js:class:`~openerp.web.search.Field` provides a default
308 implementation of :js:func:`~openerp.web.search.Input.get_domain` and
309 :js:func:`~openerp.web.search.Input.get_context` taking care of most
310 of the peculiarities pertaining to OpenERP's handling of fields in
311 search views. It also provides finer hooks to let developers of new
312 fields and widgets customize the behavior they want without
313 necessarily having to reimplement all of
314 :js:func:`~openerp.web.search.Input.get_domain` or
315 :js:func:`~openerp.web.search.Input.get_context`:
317 .. js:function:: openerp.web.search.Field.get_context(facet)
319 If the field has no ``@context``, simply returns
320 ``null``. Otherwise, calls
321 :js:func:`~openerp.web.search.Field.value_from` once for each
322 :js:class:`~openerp.web.search.FacetValue` of the current
323 :js:class:`~openerp.web.search.Facet` (in order to extract the
324 basic javascript object from the
325 :js:class:`~openerp.web.search.FacetValue` then evaluates
326 ``@context`` with each of these values set as ``self``, and
327 returns the union of all these contexts.
330 :type facet: openerp.web.search.Facet
331 :returns: a context (literal or compound)
333 .. js:function:: openerp.web.search.Field.get_domain(facet)
335 If the field has no ``@filter_domain``, calls
336 :js:func:`~openerp.web.search.Field.make_domain` once with each
337 :js:class:`~openerp.web.search.FacetValue` of the current
338 :js:class:`~openerp.web.search.Facet` as well as the field's
339 ``@name`` and either its ``@operator`` or
340 :js:attr:`~openerp.web.search.Field.default_operator`.
342 If the field has an ``@filter_value``, calls
343 :js:func:`~openerp.web.search.Field.value_from` once per
344 :js:class:`~openerp.web.search.FacetValue` and evaluates
345 ``@filter_value`` with each of these values set as ``self``.
347 In either case, "ors" all of the resulting domains (using ``|``)
348 if there is more than one
349 :js:class:`~openerp.web.search.FacetValue` and returns the union
353 :type facet: openerp.web.search.Facet
354 :returns: a domain (literal or compound)
356 .. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
358 Builds a literal domain from the provided data. Calls
359 :js:func:`~openerp.web.search.Field.value_from` on the
360 :js:class:`~openerp.web.search.FacetValue` and evaluates and sets
361 it as the domain's third value, uses the other two parameters as
362 the first two values.
364 Can be overridden to build more complex default domains.
366 :param String name: the field's name
367 :param String operator: the operator to use in the field's domain
369 :type facetValue: openerp.web.search.FacetValue
370 :returns: Array<(String, String, Object)>
372 .. js:function:: openerp.web.search.Field.value_from(facetValue)
374 Extracts a "bare" javascript value from the provided
375 :js:class:`~openerp.web.search.FacetValue`, and returns it.
377 The default implementation will simply return the ``value``
378 backbone property of the argument.
381 :type facetValue: openerp.web.search.FacetValue
384 .. js:attribute:: openerp.web.search.Field.default_operator
386 Operator used to build a domain when a field has no ``@operator``
387 or ``@filter_domain``. ``"="`` for
388 :js:class:`~openerp.web.search.Field`
390 Arbitrary data storage
391 ----------------------
393 :js:class:`~openerp.web.search.Facet` and
394 :js:class:`~openerp.web.search.FacetValue` objects (and structures)
395 provided by your widgets should never be altered by the search view
396 (or an other widget). This means you are free to add arbitrary fields
397 in these structures if you need to (because you have more complex
398 needs than the attributes described in this document).
400 Ideally this should be avoided, but the possibility remains.
405 .. todo:: merge in changelog instead?
407 The displaying of the search view was significantly altered from
408 OpenERP Web 6.1 to OpenERP Web 7.
410 As a result, while the external API used to interact with the search
411 view does not change many internal details — including the interaction
412 between the search view and its widgets — were significantly altered:
417 * :js:func:`openerp.web.SearchView.do_clear` has been removed
418 * :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
423 * :js:func:`openerp.web.search.Widget.render` has been removed
425 * :js:func:`openerp.web.search.Widget.make_id` has been removed
427 * Search field objects are not openerp widgets anymore, their
428 ``start`` is not generally called
430 * :js:func:`~openerp.web.search.Input.clear` has been removed since
431 clearing the search view now simply consists of removing all search
434 * :js:func:`~openerp.web.search.Input.get_domain` and
435 :js:func:`~openerp.web.search.Input.get_context` now take a
436 :js:class:`~openerp.web.search.Facet` as parameter, from which it's
437 their job to get whatever value they want
439 * :js:func:`~openerp.web.search.Input.get_groupby` has been added. It returns
440 an :js:class:`Array` of context-like constructs. By default, it does not do
441 anything in :js:class:`~openerp.web.search.Field` and it returns the various
442 contexts of its enabled filters in
443 :js:class:`~openerp.web.search.FilterGroup`.
448 * :js:func:`openerp.web.search.Filter.is_enabled` has been removed
450 * :js:class:`~openerp.web.search.FilterGroup` instances are still
451 rendered (and started) in the "advanced search" drawer.
456 * ``get_value`` has been replaced by
457 :js:func:`~openerp.web.search.Field.value_from` as it now takes a
458 :js:class:`~openerp.web.search.FacetValue` argument (instead of no
459 argument). It provides a default implementation returning the
460 ``value`` property of its argument.
462 * The third argument to
463 :js:func:`~openerp.web.search.Field.make_domain` is now a
464 :js:class:`~openerp.web.search.FacetValue` so child classes have all
465 the information they need to derive the "right" resulting domain.
470 Instead of being an intrinsic part of the search view, custom filters
471 are now a special case of filter groups. They are treated specially
472 still, but much less so than they used to be.
477 * Because the autocompletion service is now provided by the search
479 :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
485 * The advanced search is now a more standard
486 :js:class:`~openerp.web.search.Input` configured to be rendered in
489 * :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` are
490 now standard widgets, with the "right" behaviors (they don't rebind
491 their ``$element`` in ``start()``)
493 * The ad-hoc optional setting of the openerp field descriptor on a
494 :js:class:`~openerp.web.search.ExtendedSearchProposition.Field` has
495 been removed, the field descriptor is now passed as second argument
497 :js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
498 constructor, and bound to its
499 :js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
501 * Instead of its former domain triplet ``(field, operator, value)``,
502 :js:func:`~openerp.web.search.ExtendedSearchProposition.get_proposition`
503 now returns an object with two fields ``label`` and ``value``,
504 respectively a human-readable version of the proposition and the
505 corresponding domain triplet for the proposition.
509 the original view was implemented on top of a monkey-patched
510 VisualSearch, but as our needs diverged from VisualSearch's goal
511 this made less and less sense ultimately leading to a clean-room
516 In case you are extending the search view with a brand new type of
521 Ideally this array should not hold more than about 10 items, but
522 the search view does not put any constraint on this at the
523 moment. Note that this may change.
527 ``field`` does not actually need to be an instance of
528 :js:class:`~openerp.web.search.Input`, nor does it need to be what
529 created the facet, it just needs to provide the three
530 facet-serialization methods
531 :js:func:`~openerp.web.search.Input.get_domain`,
532 :js:func:`~openerp.web.search.Input.get_context` and
533 :js:func:`~openerp.web.search.Input.get_gropuby`, existing
534 :js:class:`~openerp.web.search.Input` subtypes merely provide
535 convenient base implementation for those methods.
537 Complex search view inputs (especially those living in the drawer)
538 may prefer using object literals with the right slots returning
539 closed-over values or some other scheme un-bound to an actual
540 :js:class:`~openerp.web.search.Input`, as
541 :js:class:`~openerp.web.search.CustomFilters` and
542 :js:class:`~openerp.web.search.Advanced` do.
546 search view fields may also bundle context data to add to the
550 http://documentcloud.github.com/backbone/
552 .. _Backbone.Collection:
553 .. _Backbone collection:
554 http://documentcloud.github.com/backbone/#Collection
557 http://documentcloud.github.com/backbone/#Model
559 .. _commit 3fca87101d:
560 https://github.com/documentcloud/visualsearch/commit/3fca87101d