Launchpad automatic translations update.
[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 .. 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
116 Providing drawer/supplementary UI
117 +++++++++++++++++++++++++++++++++
118
119 For some inputs (fields or not), interaction via autocompletion may be
120 awkward or even impossible.
121
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.
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
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`.
136
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.
142
143 The input will be rendered in the full width of the drawer, it will be
144 started 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
159 Converting from facet objects
160 +++++++++++++++++++++++++++++
161
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]_.
168
169 So there needs to be some way of mapping
170 :js:class:`~openerp.web.search.Facet` objects to OpenERP search data.
171
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.
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
193 Programmatic interactions: internal model
194 -----------------------------------------
195
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`.
199
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).
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     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).
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
304 Field services
305 --------------
306
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`:
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
390 Arbitrary data storage
391 ----------------------
392
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).
399
400 Ideally this should be avoided, but the possibility remains.
401
402 Changes
403 -------
404
405 .. todo:: merge in changelog instead?
406
407 The displaying of the search view was significantly altered from
408 OpenERP Web 6.1 to OpenERP Web 7.
409
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:
413
414 Internal 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
420 Widgets 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
445 Filters
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
453 Fields
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
467 Custom filters
468 ++++++++++++++
469
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.
473
474 Many 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
482 Advanced 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