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