documentation/wowl_markdown_doc/components/dropdown.md
Géry Debongnie eff7c05465 [DOC] add master-wowl doc (UNFINISHED)
I know, the doc is still in md, it was only temporary. we will convert
it to rst someday
2021-05-31 15:29:27 +02:00

12 KiB

Dropdown Component

Overview

As dropdowns are common in Odoo, we decided to make a generic dropdown component.

It contains all the logic you can usually expect a dropdown to behave.

Features

  • Toggle the list on click
  • Direct siblings dropdowns: when one is open, toggle others on hover
  • Close on outside click
  • Close the list when an item is selected
  • Emits an event to inform which list item is clicked
  • Infinite multi-level support
  • SIY: style it yourself
  • Configurable hotkey to open/close a dropdown or select a dropdown item
  • Keyboard navigation (arrows, enter...)

API

Behind the scenes

A <Dropdown/> component is simply a <div class="o_dropdown"/> having a <button class="o_dropdown_toggler"/> next to an unordered list (<ul class="o_dropdown_menu"/>). The button is responsible for the list being present in the DOM or not.

A <DropdownItem/> is simply a list item (<li class="o_dropdown_item"/>). On click, you can ask this item to return you a payload (which you'll receive back in a custom dropdown-item-selected event). This payload is an object, so feel free to put anything you want in it. Most likely, you will use ids as payloads to know which item was clicked.

Illustration of what the final DOM could look like:

<div class="o_dropdown">
    <button class="o_dropdown_toggler">
        <span>Click me to toggle the dropdown menu !</span>
    </button>
    <!-- following <ul/> list will or won't appear in the DOM depending on the state controlled by the button -->
    <ul class="o_dropdown_menu">
        <li class="o_dropdown_item">
            <span>Menu Item 1</span>
        </li>
        <li class="o_dropdown_item">
            <span>Menu Item 2</span>
        </li>
    </ul>
</div>

Slots

In order to properly use a <Dropdown/> component, you need to populate two OWL slots:

The default slot
It contains the toggler elements of your dropdown and will take place inside your dropdown <button><span/></button> elements.
The menu slot
It contains the elements of the dropdown menu itself and will take place inside your dropdown <ul/> element.
Although it is not mandatory, you will usually place at least one <DropdownItem/> element in the menu slot.

Manage items selection

When a <DropdownItem/> gets selected, it emits a custom dropdown-item-selected event containing its payload. (see OWL Business Events)

If you want to react when a <DropdownItem/> gets selected, you need to define two things:

The dropdown-item-selected event listener
It will receive the payload of the selected item.
A payload for each <DropdownItem/> element
They are just JS objects you declare the way you want. If a payload is not specified, it defaults to null.

Direct Siblings Dropdowns

When many dropdowns share a single parent in the DOM, they will automatically notify each other about their state changes.

Doing so, when one sibling dropdown is open, the others will automatically open themselves on hover.

Available Properties

<Dropdown/> props

Prop name Default Value Value type Description
startOpen false boolean initial dropdown open state
menuClass / string could be used to style the dropdown menu <ul/>
togglerClass / string could be used to style the toggler <button/>
hotkey / string could be used to toggle the opening through keyboard
beforeOpen / function hook to execute logic just before opening
manualOnly false boolean if true, only toggle the dropdown when the button is clicked on

<DropdownItem/> props

Prop name Default Value Value type Description
payload null Object item payload that will be part of the dropdown-item-selected event
parentClosingMode all none | closest | all when item clicked, control which parent dropdown will get closed: none, closest or all
hotkey / string click on the item via an hotkey activation

Z-Index

As Odoo previous dropdown menus made use of Bootstrap dropdowns, we added the same z-index value for the dropdown menu. See Bootstrap documentation.

.o_dropdown_menu {
    z-index: 1000;
}

Usage

Step 1: make it appear on your app

So in your qweb template, you would write something like that:

<Dropdown>
    <!-- "default" slot content should be defined here -->
    Click me to toggle the dropdown menu !
    <t t-set-slot="menu">
      <!-- "dropdown" slot content should be defined here-->
      <DropdownItem>Menu Item 1</DropdownItem>
      <DropdownItem>Menu Item 2</DropdownItem>
    </t>
</Dropdown>

And in the DOM it would get translated similarly to:

<div class="o_dropdown">
  <button class="o_dropdown_toggler">
    <!-- "default" slot content will take place here -->
    <span>Click me to toggle the dropdown menu !</span>
  </button>

  <ul class="o_dropdown_menu">
    <!-- "dropdown" slot content will take place here -->
    <li class="o_dropdown_item">
      <span>Menu Item 1</span>
    </li>
    <li class="o_dropdown_item">
      <span>Menu Item 2</span>
    </li>
  </ul>
</div>

Step 2: make it react to clicks

So in your qweb template you would write something like that:

<Dropdown t-on-dropdown-item-selected="onItemSelected"><t t-set-slot="menu"><DropdownItem payload="{a:15}">Menu Item</DropdownItem></t>
</Dropdown>

And in your JS file, when an item is selected, you would receive the payload back like that:

itemSelected(event) {
  const eventDetail = event.detail;
  const itemPayload = eventDetail.payload;
  console.log(itemPayload.a === 15);
}

In this case, if you click on this menu item, the console will print « true ».

Step 3: make it shine

Now that you understand the basics of the Dropdown Component, all you need to do is style it the way you want.

Are you ready to make it shine?

Default CSS classes are:

  • .o_dropdown : the whole dropdown
  • .o_dropdown_toggler : the dropdown button
  • .o_dropdown_menu : the dropdown menu list
  • .o_dropdown_item : a dropdown item

But you can go even further by extending them:

  • <Dropdown class="my_class"/> will become
    <div class="o_dropdown my_class">...</div>
    
  • <Dropdown togglerClass="my_class"/> will become
    <div class="o_dropdown">
      <button class="o_dropdown_toggler my_class">
        <span>...</span>
      </button>
      ...
    </div>
    
  • <Dropdown menuClass="my_class"/> will become
    <div class="o_dropdown">
      <button>...</button>
      <ul class="o_dropdown_menu my_class">...</ul>
    </div>
    
  • <DropdownItem class="my_class"/> will become
    <li class="o_dropdown_item my_class">
      <span>...</span>
    </li>
    

You can also make dropdown right aligned by passing 'o_dropdown_menu_right' in menuClass

  • <Dropdown menuClass="'o_dropdown_menu_right'"/> will become
    <div class="o_dropdown">
      <button>...</button>
      <ul class="o_dropdown_menu o_dropdown_menu_right">...</ul>
    </div>
    

More Examples

Direct Siblings Dropdown

When one dropdown toggler is clicked (File, Edit or About), the others will open themselves on hover.

This example uses the dropdown components without added style.

<div t-on-dropdown-item-selected="onItemSelected">
  <Dropdown>
    File
    <t t-set-slot="menu">
      <DropdownItem payload="'file-open'">Open</DropdownItem>
      <DropdownItem payload="'file-new-document'">New Document</DropdownItem>
      <DropdownItem payload="'file-new-spreadsheet'">New Spreadsheet</DropdownItem>
    </t>
  </Dropdown>
  <Dropdown>
    Edit
    <t t-set-slot="menu">
      <DropdownItem payload="'edit-undo'">Undo</DropdownItem>
      <DropdownItem payload="'edit-redo'">Redo</DropdownItem>
      <DropdownItem payload="'edit-find'">Search</DropdownItem>
    </t>
  </Dropdown>
  <Dropdown>
    About
    <t t-set-slot="menu">
      <DropdownItem payload="'about-help'">Help</DropdownItem>
      <DropdownItem payload="'about-update'">Check update</DropdownItem>
    </t>
  </Dropdown>
</div>

Multi-level Dropdown

This example uses the dropdown components without added style.

Flat version

<Dropdown t-on-dropdown-item-selected="onItemSelected" owl="1">
  File
  <t t-set-slot="menu">
    <DropdownItem payload="'file-open'">Open</DropdownItem>
    <t t-call="addon.Dropdown.File.New"/>
    <DropdownItem payload="'file-save'">Save</DropdownItem>
    <t t-call="addon.Dropdown.File.Save.As"/>
  </t>
</Dropdown>

<Dropdown t-name="addon.Dropdown.File.New" owl="1">
  New
  <t t-set-slot="menu">
    <DropdownItem payload="'file-new-document'">Document</DropdownItem>
    <DropdownItem payload="'file-new-spreadsheet'">Spreadsheet</DropdownItem>
  </t>
</Dropdown>

<Dropdown t-name="addon.Dropdown.File.Save.As" owl="1">
  Save as...
  <t t-set-slot="menu">
    <DropdownItem payload="'file-save-as-csv'">CSV</DropdownItem>
    <DropdownItem payload="'file-save-as-pdf'">PDF</DropdownItem>
  </t>
</Dropdown>

Nested version

<Dropdown t-on-dropdown-item-selected="onItemSelected" owl="1">
  File
  <t t-set-slot="menu">
    <DropdownItem payload="'file-open'">Open</DropdownItem>
    <Dropdown>
      New
      <t t-set-slot="menu">
        <DropdownItem payload="'file-new-document'">Document</DropdownItem>
        <DropdownItem payload="'file-new-spreadsheet'">Spreadsheet</DropdownItem>
      </t>
    </Dropdown>
    <DropdownItem payload="'file-save'">Save</DropdownItem>
    <Dropdown>
      Save as...
      <t t-set-slot="menu">
        <DropdownItem payload="'file-save-as-csv'">CSV</DropdownItem>
        <DropdownItem payload="'file-save-as-pdf'">PDF</DropdownItem>
      </t>
    </Dropdown>
  </t>
</Dropdown>

Recursive Multi-level Dropdown

This example make use of inline style.

<div t-name="addon.MainTemplate" t-on-dropdown-item-selected="onItemSelected">
  <t t-call="addon.RecursiveDropdown">
    <t t-set="name" t-value="'Main Menu'" />
    <t t-set="items" t-value="state.menuItems" />
  </t>
</div>

<Dropdown t-name="addon.RecursiveDropdown" owl="1">
  <div style="display: inline-flex; color:white; background-color: red; padding: 2px; border: 1px white solid; opacity: 50%">
    <t t-esc="name" />
  </div>

  <t t-set-slot="menu">
    <t t-foreach="items" t-as="item" t-key="item.id">
      <t t-if="!item.childrenTree.length">
        <!-- If this item has no child: make it a <DropdownItem/> -->
        <DropdownItem payload="item">
          <div style="display: inline-flex; color:white; background-color: blue; padding: 2px;border: 1px white solid;  opacity: 50%;">
            <t t-esc="item.name" />
          </div>
        </DropdownItem>
      </t>

      <!-- Else: recursively call the current dropdown template. -->
      <t t-else="" t-call="addon.RecursiveDropdown">
        <t t-set="name" t-value="item.name" />
        <t t-set="items" t-value="item.childrenTree" />
      </t>
    </t>
  </t>
</Dropdown>