1Search View
2===========
3
4OpenERP Web 7.0 implements a unified facets-based search view instead
5of the previous form-like search view (composed of buttons and
6multiple fields). The goal for this change is twofold:
7
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).
12
13* Improve the looks and behaviors of the view, and the fit within
14  OpenERP Web's new design.
15
16The internal structure of the faceted search is inspired by
17`VisualSearch <http://documentcloud.github.com/visualsearch/>`_
18[#previous]_.
19
20As does VisualSearch, the new search view is based on `Backbone`_ and
21makes significant use of Backbone's models and collections (OpenERP
22Web's widgets make a good replacement for Backbone's own views). As a
23result, understanding the implementation details of the OpenERP Web 7
24search view also requires a basic understanding of Backbone's models,
25collections and events.
26
27.. note::
28
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
34    Deferred).
35
36Working with the search view: creating new inputs
37-------------------------------------------------
38
39The primary component of search views, as with all other OpenERP
40views, are inputs. The search view has two types of inputs — filters
41and fields — but only one is easly customizable: fields.
42
43The mapping from OpenERP field types (and widgets) to search view
44objects is stored in the ``openerp.web.search.fields``
45:js:class:`~openerp.web.Registry` where new field types and widgets
46can be added.
47
48Search view inputs have four main roles:
49
50Loading defaults
51++++++++++++++++
52
53Once the search view has initialized all its inputs, it will call
54:js:func:`~openerp.web.search.Input.facet_for_defaults` on each input,
55passing it a mapping (a javascript object) of ``name:value`` extracted
56from the action's context.
57
58This method should fetch a :js:class:`~openerp.web.search.Facet` (or
59an equivalent object) for the field's default value if applicable (if
60a default value for the field is found in the ``defaults`` mapping).
61
62A default implementation is provided which checks if ``defaults``
63contains a non-falsy value for the field's ``@name`` and calls
64:js:func:`openerp.web.search.Input.facet_for` with that value.
65
66There 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
69value as-is to fetch a :js:class:`~openerp.web.search.Facet`.
70
71Providing completions
72+++++++++++++++++++++
73
74An important component of the new search view is the auto-completion
75pane, and the task of providing completion items is delegated to
76inputs through the :js:func:`~openerp.web.search.Input.complete`
77method.
78
79This method should take a single argument (the string being typed by
80the user) and should fetch an ``Array`` of possible completions
81[#completion]_.
82
83A default implementation is provided which fetches nothing.
84
85A completion item is a javascript object with two keys (technically it
86can have any number of keys, but only these two will be used by the
87search view):
88
89``label``
90
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
94    used for this.
95
96``facet``
97
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.
102
103If the ``facet`` is not provided (not present, ``null``, ``undefined``
104or any other falsy value), the completion item will not be selectable
105and will act as a section title of sort (the ``label`` will be
106formatted differently). If an input *may* fetch multiple completion
107items, it *should* prefix those with a section title using its own
108name. This has no technical consequence but is clearer for users.
109
110.. note::
111
112    If a field is :js:func:`invisible
113    <openerp.web.search.Input.visible>`, its completion function will
114    *not* be called.
115
116Providing drawer/supplementary UI
117+++++++++++++++++++++++++++++++++
118
119For some inputs (fields or not), interaction via autocompletion may be
120awkward or even impossible.
121
122These may opt to being rendered in a "drawer" as well or instead. In
123that case, they will undergo the normal widget lifecycle and be
124rendered inside the drawer.
125
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)
132
133Any input can note its desire to be rendered in the drawer by
134returning a truthy value from
135:js:func:`~openerp.web.search.Input.in_drawer`.
136
137By default, :js:func:`~openerp.web.search.Input.in_drawer` returns the
138value of :js:attr:`~openerp.web.search.Input._in_drawer`, which is
139``false``. The behavior can be toggled either by redefining the
140attribute to ``true`` (either on the class or on the input), or by
141overriding :js:func:`~openerp.web.search.Input.in_drawer` itself.
142
143The input will be rendered in the full width of the drawer, it will be
144started only once (per view).
145
146.. todo:: drawer API (if a widget wants to close the drawer in some
147          way), part of the low-level SearchView API/interactions?
148
149
150.. todo:: handle filters and filter groups via a "driver" input which
151          dynamically collects, lays out and renders filters? =>
152          exercises drawer thingies
153
154.. note::
155
156    An :js:func:`invisible <openerp.web.search.Input.visible>` input
157    will not be inserted into the drawer.
158
159Converting from facet objects
160+++++++++++++++++++++++++++++
161
162Ultimately, the point of the search view is to allow searching. In
163OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
164the other hand, the OpenERP Web 7 search view's state is modelled
165after a collection of :js:class:`~openerp.web.search.Facet`, and each
166field of a search view may have special requirements when it comes to
167the domains it produces [#special]_.
168
169So there needs to be some way of mapping
170:js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
171
172This 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
176supposed to generate (a domain or a context, respectively). Either can
177return ``null`` if the current value does not map to a domain or
178context, and can throw an :js:class:`~openerp.web.search.Invalid`
179exception if the value is not valid at all for the field.
180
181.. note::
182
183    The :js:class:`~openerp.web.search.Facet` object can have any
184    number of values (from 1 upwards)
185
186.. note::
187
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.
192
193Programmatic interactions: internal model
194-----------------------------------------
195
196This new searchview is built around an instance of
197:js:class:`~openerp.web.search.SearchQuery` available as
198:js:attr:`openerp.web.SearchView.query`.
199
200The query is a `backbone collection`_ of
201:js:class:`~openerp.web.search.Facet` objects, which can be interacted
202with directly by external objects or search view controls
203(e.g. widgets displayed in the drawer).
204
205.. js:class:: openerp.web.search.SearchQuery
206
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.
210
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.
214
215    .. js:function:: openerp.web.search.SearchQuery.add(values, options)
216
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.
221
222        :param values: facet, facet attributes or array thereof
223        :returns: the collection itself
224
225    .. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
226
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.
231
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.
237
238        :param value: facet or facet attributes
239        :returns: the collection
240
241.. js:class:: openerp.web.search.Facet
242
243    A `backbone model`_ representing a single facet of the current
244    search. May map to a search field, or to a more complex or
245    fuzzier input (e.g. a custom filter or an advanced search).
246
247    .. js:attribute:: category
248
249        The displayed name of the facet, as a ``String``. This is a
250        backbone model attribute.
251
252    .. js:attribute:: field
253
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.
258
259    .. js:attribute:: values
260
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``).
267
268    .. js:attribute:: [icon]
269
270        optional, a single ASCII letter (a-z or A-Z) mapping to the
271        bundled mnmliconsRegular icon font.
272
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``.
276
277        By default, only filters make use of this facility.
278
279.. js:class:: openerp.web.search.FacetValues
280
281    `Backbone collection`_ of
282    :js:class:`~openerp.web.search.FacetValue` instances.
283
284.. js:class:: openerp.web.search.FacetValue
285
286    `Backbone model`_ representing a single value within a facet,
287    represents a pair of (displayed name, logical value).
288
289    .. js:attribute:: label
290
291        Backbone model attribute storing the "displayable"
292        representation of the value, visually output to the
293        user. Must be a string.
294
295    .. js:attribute:: value
296
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
300        and contexts.
301
302        Can be of any type.
303
304Field services
305--------------
306
307:js:class:`~openerp.web.search.Field` provides a default
308implementation of :js:func:`~openerp.web.search.Input.get_domain` and
309:js:func:`~openerp.web.search.Input.get_context` taking care of most
310of the peculiarities pertaining to OpenERP's handling of fields in
311search views. It also provides finer hooks to let developers of new
312fields and widgets customize the behavior they want without
313necessarily having to reimplement all of
314:js:func:`~openerp.web.search.Input.get_domain` or
315:js:func:`~openerp.web.search.Input.get_context`:
316
317.. js:function:: openerp.web.search.Field.get_context(facet)
318
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.
328
329    :param facet:
330    :type facet: openerp.web.search.Facet
331    :returns: a context (literal or compound)
332
333.. js:function:: openerp.web.search.Field.get_domain(facet)
334
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`.
341
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``.
346
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
350    of the result.
351
352    :param facet:
353    :type facet: openerp.web.search.Facet
354    :returns: a domain (literal or compound)
355
356.. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
357
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.
363
364    Can be overridden to build more complex default domains.
365
366    :param String name: the field's name
367    :param String operator: the operator to use in the field's domain
368    :param facetValue:
369    :type facetValue: openerp.web.search.FacetValue
370    :returns: Array<(String, String, Object)>
371
372.. js:function:: openerp.web.search.Field.value_from(facetValue)
373
374    Extracts a "bare" javascript value from the provided
375    :js:class:`~openerp.web.search.FacetValue`, and returns it.
376
377    The default implementation will simply return the ``value``
378    backbone property of the argument.
379
380    :param facetValue:
381    :type facetValue: openerp.web.search.FacetValue
382    :returns: Object
383
384.. js:attribute:: openerp.web.search.Field.default_operator
385
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`
389
390Arbitrary data storage
391----------------------
392
393:js:class:`~openerp.web.search.Facet` and
394:js:class:`~openerp.web.search.FacetValue` objects (and structures)
395provided 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
397in these structures if you need to (because you have more complex
398needs than the attributes described in this document).
399
400Ideally this should be avoided, but the possibility remains.
401
402Changes
403-------
404
405.. todo:: merge in changelog instead?
406
407The displaying of the search view was significantly altered from
408OpenERP Web 6.1 to OpenERP Web 7.
409
410As a result, while the external API used to interact with the search
411view does not change many internal details — including the interaction
412between the search view and its widgets — were significantly altered:
413
414Internal operations
415+++++++++++++++++++
416
417* :js:func:`openerp.web.SearchView.do_clear` has been removed
418* :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
419
420Widgets API
421+++++++++++
422
423* :js:func:`openerp.web.search.Widget.render` has been removed
424
425* :js:func:`openerp.web.search.Widget.make_id` has been removed
426
427* Search field objects are not openerp widgets anymore, their
428  ``start`` is not generally called
429
430* :js:func:`~openerp.web.search.Input.clear` has been removed since
431  clearing the search view now simply consists of removing all search
432  facets
433
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
438
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`.
444
445Filters
446+++++++
447
448* :js:func:`openerp.web.search.Filter.is_enabled` has been removed
449
450* :js:class:`~openerp.web.search.FilterGroup` instances are still
451  rendered (and started) in the "advanced search" drawer.
452
453Fields
454++++++
455
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.
461
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.
466
467Custom filters
468++++++++++++++
469
470Instead of being an intrinsic part of the search view, custom filters
471are now a special case of filter groups. They are treated specially
472still, but much less so than they used to be.
473
474Many To One
475+++++++++++
476
477* Because the autocompletion service is now provided by the search
478  view itself,
479  :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
480  been removed.
481
482Advanced Search
483+++++++++++++++
484
485* The advanced search is now a more standard
486  :js:class:`~openerp.web.search.Input` configured to be rendered in
487  the drawer.
488
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()``)
492
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
496  to the
497  :js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
498  constructor, and bound to its
499  :js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
500
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.
506
507.. [#previous]
508
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
512    reimplementation
513
514.. [#no_impl]
515
516    In case you are extending the search view with a brand new type of
517    input
518
519.. [#completion]
520
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.
524
525.. [#facet-field]
526
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.
536
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.
543
544.. [#special]
545
546    search view fields may also bundle context data to add to the
547    search context
548
549.. _Backbone:
550    http://documentcloud.github.com/backbone/
551
552.. _Backbone.Collection:
553.. _Backbone collection:
554    http://documentcloud.github.com/backbone/#Collection
555
556.. _Backbone model:
557    http://documentcloud.github.com/backbone/#Model
558
559.. _commit 3fca87101d:
560    https://github.com/documentcloud/visualsearch/commit/3fca87101d
561