How to Add a Filter to AEM Granite Select Widget | Perficient Digital

How to Add a Filter to AEM Granite Select Widget

Adobe Experience Manager’s Granite Select Widget is a simple dropdown widget that you add to dialogs and page properties to allow users to pick an option from a list. But, the list is not searchable.

To add searchability, OOTB, you have to use the Autocomplete widget. This works well in AEM 6.4 but in AEM 6.5, it is deprecated. Besides, it has more advanced options and usages beyond what I’m trying to achieve here.

In this post, I am particularly interested in enhancing the existing Select widget to make the options searchable. And this enhancement would affect all Select widgets no matter where they are used in AEM.

NOTE: I have raise a PR for this with ACS Commons here

Please note: this was tested and implemented on AEM 6.4. 

Here is a preview of the change I have implemented:

And here is the really simple code to get it done:

// new coral text field to be use as filter input
function newFilterTextField() {
  var filter = new Coral.Textfield();
  filter.classList.add("coral-Form-field");
  filter.placeholder = "filter";
  return filter;
}

/**
 * validates some assumptions about the select element structure:
 *  1. it must have a coral-overlay child.
 *  2. coral-overlay must have a coral-selectlist child.
 *  3. coral-selectlist must have items.
 */
function isValidSelectDomStruture(selectEl) {
  var overlay = selectEl.querySelector("coral-overlay");
  var selectList = overlay ? overlay.querySelector("coral-selectlist") : null;
  var items = selectList ? selectList.items : false;
  // coral-overlay must exist & coral-selectlist must exist & coral-selectlist must have items.
  return !!overlay && !!selectList && !!items;
}

/**
 * Hides all items on the selectListEl that do not conatain the filterText, ignoring case.
 * @param {*} selectListEl the "coral-selectlist" element.
 * @param {*} filterText the search string (filter).
 */
function filterSelectList(selectListEl, filterText) {
  selectListEl.items.getAll().forEach(item => {
    var itemText = item.textContent;
    var bothNotEmpty = itemText && filterText;
    var match =
      bothNotEmpty &&
      itemText.toLowerCase().indexOf(filterText.toLowerCase()) > -1;
    var eitherIsEmpty = !itemText || !filterText;

    if (eitherIsEmpty || match) {
      item.show();
    } else {
      item.hide();
    }
  });
}

var ATTR_FILTER_READY = "data-select-filter-ready";
$(document).on("foundation-contentloaded", function(e) {
  var container = e.target;
  $("coral-select", container).each((i, el) => {
    Coral.commons.ready(el, function(selectEl) {
      if (selectEl.hasAttribute(ATTR_FILTER_READY)) {
        return; // exit, already initialized.
      } else {
        selectEl.setAttribute(ATTR_FILTER_READY, "true");
      }

      // exit here if structure does not match our assumptions
      if (!isValidSelectDomStruture(selectEl)) {
        console.info(
          "Could not add select filter to the following coral select element. It does not follow the specified structure",
          selectEl
        );
        return;
      }

      var filter = newFilterTextField();
      var overlay = selectEl.querySelector("coral-overlay");
      var selectList = overlay.querySelector("coral-selectlist");
      // add the filter field to the beginning of the list
      selectList.items.add(filter, selectList.items.first());

      // apply filter on keyup
      filter.addEventListener("keyup", function() {
        var filterValue = filter.value;
        filterSelectList(selectList, filterValue);
      });
    });
  });
});

Add above code to a clientlib with categories="coralui3"and you are done!

The code above will add the filter field, which will allow you to search through options as illustrated in the GIF above.

Leave a Reply