Creating custom dropdowns in eZ Platform

eZ Platform comes with a bunch of utilities that can help you build rich UI. They make your administration panel better in terms of UX and overall pleasure of using the eZ Platform CMS features. One of such utilities is a small component/library that enables you to implement custom dropdowns anywhere in the system.

Usage sample

In order to use the feature provided by eZ Platform you have to prepare the HTML code structure as following:

<div class="ez-custom-dropdown">
    <select class="ez-custom-dropdown__select" hidden multiple>
        <option value="1" selected>Option 1</option>
        <option value="2">Option 2</option>
        <option value="3">Option 3</option>
        <option value="4">Option 4</option>
    </select>
    <div class="ez-custom-dropdown__wrapper">
        <ul class="ez-custom-dropdown__selection-info"></ul>
        <ul class="ez-custom-dropdown__items ez-custom-dropdown__items--hidden">
            <li data-value="" class="ez-custom-dropdown__item" disabled>Select an option</li>
            <li data-value="1" class="ez-custom-dropdown__item">Option 1</li>
            <li data-value="2" class="ez-custom-dropdown__item">Option 2</li>
            <li data-value="3" class="ez-custom-dropdown__item">Option 3</li>
            <li data-value="4" class="ez-custom-dropdown__item">Option 4</li>
        </ul>
    </div>
</div>
HTML code structure

As you noticed it contains a hidden native select input. You will stored values of selection there. The reason it's hidden is that custom dropdown will replicate its functionality, but have no desire to remove it. If a field is removed it might break the functionality of any submission form.

While preparing the structure you have to generate a standard select input with `ez-custom-dropdown__select` CSS class added to an element and with at least one additional attribute: `hidden`. If you add also the `multiple` attribute, then a custom dropdown will allow users to pick multiple items from a list.

Another important thing to mention is `data-value` attribute added to replicated options with the CSS class: `ez-custom-dropdown__item`. It stores value of an option from select input.

If you provide a placeholder text for your custom dropdown then the item in the replicated list of options should not have any value `data-value` attribute and should have `disabled` attribute added as well, in order to make it not clickable/selectable.

To initialize the feature of the custom dropdown you'll need to run some JS code. The code is very simple:

(function (global, doc, eZ) {
    class MyCustomDropdown extends eZ.core.CustomDropdown {
        constructor(config) {
            super(config);

            this.displayAuthorCard = this.displayAuthorCard.bind(this);
            this.handleRequest = this.handleRequest.bind(this);
        }

        handleRequest(response) {
            if (!response.ok) {
                throw Error(response.statusText);
            }

            return response.json();
        }

        displayAuthorCard(authorData) {
            const authorCardContainer = doc.querySelector('#author-card-container');

            authorCardContainer.querySelector('.author-name').innerText = authorData.name;
            authorCardContainer.querySelector('.author-image').src = authorData.imageSrc;
            authorCardContainer.querySelector('.author-bio').innerHtml = authorData.bio;
        }

        onSelect(element, selected) {
            super.onSelect(element, selected);

            fetch(`/your/endpoint/${element.dataset.value}`)
                .then(this.handleRequest)
                .then(this.displayAuthorCard)
        }
    }

    const config = { ... };
    const myCustomDropdown = new MyCustomDropdown(config);

    myCustomDropdown.init();
})(window, window.document, window.eZ);
Advanced usage of custom dropdowns

Custom dropdown configuration options

In the code sample you can find 4 of 5 possible configuration options. The first 3 options are required. The last one is optional. You can find the full list of options (with a description) below:

- container - contains a reference to a DOM node where the custom dropdown feature will be initialized on. It is required option.

- sourceInput - contains a reference to a DOM node where the value of selected option will be stored. Presumably, it should be a reference to a select input node. It is required option.

- itemsContainer - contains a reference to a replicated items container. It is required option.

- hasDefaultSelection - contains a boolean value. If set to `true` then the first option will be selected as a placeholder or selected value. It is optional.

- selectedItemTemplate - contains a literal template string with placeholders for `value` and `label` data. By default, the template HTML code structure looks like this:
<li class="ez-custom-dropdown__selected-item" data-value="{{value}}">{{label}}<span class="ez-custom-dropdown__remove-selection"></span></li>
It is optional. In case you decide to use your own template remember to define placeholders for `value` and `label` properties output. Add the option removal button with CSS class `ez-custom-dropdown__remove-selection` and you have to also add the `ez-custom-dropdown__selected-item` CSS class to an item container. 

Advanced usage example

The custom dropdown feature itself is quite powerful and fits the most of use cases. Sometimes you need to tweak an existing solution to fit your or your project needs. For instance, let's imagine you would like to make a request to a server whenever new option is selected from the custom dropdown list.
In order to do so, you have to extend the `CustomDropdown` JS class by changing behaviour of `onSelect` method:

(function (global, doc, eZ) {
    class MyCustomDropdown extends eZ.core.CustomDropdown {
        constructor(config) {
            super(config);

            this.displayAuthorCard = this.displayAuthorCard.bind(this);
            this.handleRequest = this.handleRequest.bind(this);
        }

        handleRequest(response) {
            if (!response.ok) {
                throw Error(response.statusText);
            }

            return response.json();
        }

        displayAuthorCard(authorData) {
            const authorCardContainer = doc.querySelector('#author-card-container');

            authorCardContainer.querySelector('.author-name').innerText = authorData.name;
            authorCardContainer.querySelector('.author-image').src = authorData.imageSrc;
            authorCardContainer.querySelector('.author-bio').innerHtml = authorData.bio;
        }

        onSelect(element, selected) {
            super.onSelect(element, selected);

            fetch(`/your/endpoint/${element.dataset.value}`)
                .then(this.handleRequest)
                .then(this.displayAuthorCard)
        }
    }

    const config = { ... };
    const myCustomDropdown = new MyCustomDropdown(config);

    myCustomDropdown.init();
})(window, window.document, window.eZ);
Advanced usage of custom dropdowns

If you analyze the code sample above you will notice the convention of overriding JS class methods with using `super` keyword. The `super` keyword points to an extended JS class. In order to make a new class backward compatible with previous solution it is recommended to call, so called native methods from an extended class, for example: `super.onSelect(element, selected);`.
In the implementation of `MyCustomDropdown` class we have done a couple of things:

  • we're fetching data from a server using our custom endpoint
  • then we're parsing data to return them in a JSON object structure
  • finally, we're displaying author card somewhere

In order to keep a reference to `this` as a class instance we've done binding in the class `constructor` method. Thanks to this, you won't have to bind a function on each `onSelect` function call. That will make your code more performant and use less memory as method will be bound with the right context once, when initializing class instance.

Summary

Having custom dropdowns enriches UX of your application. It makes more fun to use then a regular select dropdown, especially when you're adding a custom interaction, like fetching data to display an author card.

In case of any questions do not hesitate to contact me on the eZ Community Slack or on Twitter

Leave us a comment
blog comments powered by Disqus