rm files, move postload hook
[odoo/odoo.git] / addons / web / doc / search-view.rst
1 Search View
2 ===========
3
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:
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
16 The internal structure of the faceted search is inspired by
17 `VisualSearch <http://documentcloud.github.com/visualsearch/>`_
18 [#previous]_.
19
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.
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
36 Working with the search view: creating new inputs
37 -------------------------------------------------
38
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.
42
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
46 can be added.
47
48 Search view inputs have four main roles:
49
50 Loading defaults
51 ++++++++++++++++
52
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.
57
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).
61
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.
65
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`.
70
71 Providing completions
72 +++++++++++++++++++++
73
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`
77 method.
78
79 This method should take a single argument (the string being typed by
80 the user) and should fetch an ``Array`` of possible completions
81 [#completion]_.
82
83 A default implementation is provided which fetches nothing.
84
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
87 search 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
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.
109
110 Providing drawer/supplementary UI
111 +++++++++++++++++++++++++++++++++
112
113 For some inputs (fields or not), interaction via autocompletion may be
114 awkward or even impossible.
115
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.
119
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)
126
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`.
130
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.
136
137 The input will be rendered in the full width of the drawer, it will be
138 started only once (per view).
139
140 .. todo:: drawer API (if a widget wants to close the drawer in some
141           way), part of the low-level SearchView API/interactions?
142
143
144 .. todo:: handle filters and filter groups via a "driver" input which
145           dynamically collects, lays out and renders filters? =>
146           exercises drawer thingies
147
148 Converting from facet objects
149 +++++++++++++++++++++++++++++
150
151 Ultimately, the point of the search view is to allow searching. In
152 OpenERP this is done via :ref:`domains <openerpserver:domains>`. On
153 the other hand, the OpenERP Web 7 search view's state is modelled
154 after a collection of :js:class:`~openerp.web.search.Facet`, and each
155 field of a search view may have special requirements when it comes to
156 the domains it produces [#special]_.
157
158 So there needs to be some way of mapping
159 :js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
160
161 This is done via an input's
162 :js:func:`~openerp.web.search.Input.get_domain` and
163 :js:func:`~openerp.web.search.Input.get_context`. Each takes a
164 :js:class:`~openerp.web.search.Facet` and returns whatever it's
165 supposed to generate (a domain or a context, respectively). Either can
166 return ``null`` if the current value does not map to a domain or
167 context, and can throw an :js:class:`~openerp.web.search.Invalid`
168 exception if the value is not valid at all for the field.
169
170 .. note::
171
172     The :js:class:`~openerp.web.search.Facet` object can have any
173     number of values (from 1 upwards)
174
175 .. note::
176
177     There is a third conversion method,
178     :js:func:`~openerp.web.search.Input.get_groupby`, which returns an
179     ``Array`` of groupby domains rather than a single context. At this
180     point, it is only implemented on (and used by) filters.
181
182 Programmatic interactions: internal model
183 -----------------------------------------
184
185 This new searchview is built around an instance of
186 :js:class:`~openerp.web.search.SearchQuery` available as
187 :js:attr:`openerp.web.SearchView.query`.
188
189 The query is a `backbone collection`_ of
190 :js:class:`~openerp.web.search.Facet` objects, which can be interacted
191 with directly by external objects or search view controls
192 (e.g. widgets displayed in the drawer).
193
194 .. js:class:: openerp.web.search.SearchQuery
195
196     The current search query of the search view, provides convenience
197     behaviors for manipulating :js:class:`~openerp.web.search.Facet`
198     on top of the usual `backbone collection`_ methods.
199
200     The query ensures all of its facets contain at least one
201     :js:class:`~openerp.web.search.FacetValue` instance. Otherwise,
202     the facet is automatically removed from the query.
203
204     .. js:function:: openerp.web.search.SearchQuery.add(values, options)
205
206         Overridden from the base ``add`` method so that adding a facet
207         which is *already* in the collection will merge the value of
208         the new facet into the old one rather than add a second facet
209         with different values.
210
211         :param values: facet, facet attributes or array thereof
212         :returns: the collection itself
213
214     .. js:function:: openerp.web.search.SearchQuery.toggle(value, options)
215
216         Convenience method for toggling facet values in a query:
217         removes the values (through the facet itself) if they are
218         present, adds them if they are not. If the facet itself is not
219         in the collection, adds it automatically.
220
221         A toggling is atomic: only one change event will be triggered
222         on the facet regardless of the number of values added to or
223         removed from the facet (if the facet already exists), and the
224         facet is only removed from the query if it has no value *at
225         the end* of the toggling.
226
227         :param value: facet or facet attributes
228         :returns: the collection
229
230 .. js:class:: openerp.web.search.Facet
231
232     A `backbone model`_ representing a single facet of the current
233     research. May map to a search field, or to a more complex or
234     fuzzier input (e.g. a custom filter or an advanced search).
235
236     .. js:attribute:: category
237
238         The displayed name of the facet, as a ``String``. This is a
239         backbone model attribute.
240
241     .. js:attribute:: field
242
243         The :js:class:`~openerp.web.search.Input` instance which
244         originally created the facet [#facet-field]_, used to delegate
245         some operations (such as serializing the facet's values to
246         domains and contexts). This is a backbone model attribute.
247
248     .. js:attribute:: values
249
250         :js:class:`~openerp.web.search.FacetValues` as a javascript
251         attribute, stores all the values for the facet and helps
252         propagate their events to the facet. Is also available as a
253         backbone attribute (via ``#get`` and ``#set``) in which cases
254         it serializes to and deserializes from javascript arrays (via
255         ``Collection#toJSON`` and ``Collection#reset``).
256
257     .. js:attribute:: [icon]
258
259         optional, a single ASCII letter (a-z or A-Z) mapping to the
260         bundled mnmliconsRegular icon font.
261
262         When a facet with an ``icon`` attribute is rendered, the icon
263         is displayed (in the icon font) in the first section of the
264         facet instead of the ``category``.
265
266         By default, only filters make use of this facility.
267
268 .. js:class:: openerp.web.search.FacetValues
269
270     `Backbone collection`_ of
271     :js:class:`~openerp.web.search.FacetValue` instances.
272
273 .. js:class:: openerp.web.search.FacetValue
274
275     `Backbone model`_ representing a single value within a facet,
276     represents a pair of (displayed name, logical value).
277
278     .. js:attribute:: label
279
280         Backbone model attribute storing the "displayable"
281         representation of the value, visually output to the
282         user. Must be a string.
283
284     .. js:attribute:: value
285
286         Backbone model attribute storing the logical/internal value
287         (of itself), will be used by
288         :js:class:`~openerp.web.search.Input` to serialize to domains
289         and contexts.
290
291         Can be of any type.
292
293 Field services
294 --------------
295
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`:
305
306 .. js:function:: openerp.web.search.Field.get_context(facet)
307
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.
317
318     :param facet:
319     :type facet: openerp.web.search.Facet
320     :returns: a context (literal or compound)
321
322 .. js:function:: openerp.web.search.Field.get_domain(facet)
323
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`.
330
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``.
335
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
339     of the result.
340
341     :param facet:
342     :type facet: openerp.web.search.Facet
343     :returns: a domain (literal or compound)
344
345 .. js:function:: openerp.web.search.Field.make_domain(name, operator, facetValue)
346
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.
352
353     Can be overridden to build more complex default domains.
354
355     :param String name: the field's name
356     :param String operator: the operator to use in the field's domain
357     :param facetValue:
358     :type facetValue: openerp.web.search.FacetValue
359     :returns: Array<(String, String, Object)>
360
361 .. js:function:: openerp.web.search.Field.value_from(facetValue)
362
363     Extracts a "bare" javascript value from the provided
364     :js:class:`~openerp.web.search.FacetValue`, and returns it.
365
366     The default implementation will simply return the ``value``
367     backbone property of the argument.
368
369     :param facetValue:
370     :type facetValue: openerp.web.search.FacetValue
371     :returns: Object
372
373 .. js:attribute:: openerp.web.search.Field.default_operator
374
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`
378
379 Arbitrary data storage
380 ----------------------
381
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).
388
389 Ideally this should be avoided, but the possibility remains.
390
391 Changes
392 -------
393
394 .. todo:: merge in changelog instead?
395
396 The displaying of the search view was significantly altered from
397 OpenERP Web 6.1 to OpenERP Web 7.
398
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:
402
403 Internal operations
404 +++++++++++++++++++
405
406 * :js:func:`openerp.web.SearchView.do_clear` has been removed
407 * :js:func:`openerp.web.SearchView.do_toggle_filter` has been removed
408
409 Widgets API
410 +++++++++++
411
412 * :js:func:`openerp.web.search.Widget.render` has been removed
413
414 * :js:func:`openerp.web.search.Widget.make_id` has been removed
415
416 * Search field objects are not openerp widgets anymore, their
417   ``start`` is not generally called
418
419 * :js:func:`~openerp.web.search.Input.clear` has been removed since
420   clearing the search view now simply consists of removing all search
421   facets
422
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
427
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`.
433
434 Filters
435 +++++++
436
437 * :js:func:`openerp.web.search.Filter.is_enabled` has been removed
438
439 * :js:class:`~openerp.web.search.FilterGroup` instances are still
440   rendered (and started) in the "advanced search" drawer.
441
442 Fields
443 ++++++
444
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.
450
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.
455
456 Custom filters
457 ++++++++++++++
458
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.
462
463 Many To One
464 +++++++++++
465
466 * Because the autocompletion service is now provided by the search
467   view itself,
468   :js:func:`openerp.web.search.ManyToOneField.setup_autocomplete` has
469   been removed.
470
471 Advanced Search
472 +++++++++++++++
473
474 * The advanced search is now a more standard
475   :js:class:`~openerp.web.search.Input` configured to be rendered in
476   the drawer.
477
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()``)
481
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
485   to the
486   :js:class:`~openerp.web.search.ExtendedSearchProposition.Field`'s
487   constructor, and bound to its
488   :js:attr:`~openerp.web.search.ExtendedSearchProposition.Field.field`.
489
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.
495
496 .. [#previous]
497
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
501     reimplementation
502
503 .. [#no_impl]
504
505     In case you are extending the search view with a brand new type of
506     input
507
508 .. [#completion]
509
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.
513
514 .. [#facet-field]
515
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.
525
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.
532
533 .. [#special]
534
535     search view fields may also bundle context data to add to the
536     search context
537
538 .. _Backbone:
539     http://documentcloud.github.com/backbone/
540
541 .. _Backbone.Collection:
542 .. _Backbone collection:
543     http://documentcloud.github.com/backbone/#Collection
544
545 .. _Backbone model:
546     http://documentcloud.github.com/backbone/#Model
547
548 .. _commit 3fca87101d:
549     https://github.com/documentcloud/visualsearch/commit/3fca87101d