DEV Community

loading...
Cover image for Faux occlusion with Ember-Power-Select

Faux occlusion with Ember-Power-Select

michalbryxi profile image Michal Bryxí ・2 min read

The problem

Ember-Power-Select is a very powerful EmberJS addon for creating drop-down selects.

Recently I had an interesting case that I needed to have all options loaded client-side and the number of options was not small (several thousands). At this point I have learned that the component does not have any form of occlusion rendering, so that all the thousand options would just be spit into the DOM. Which is not great.

After few iterations I found a decent solution:

The solution

// index.hbs

<PowerSelect
  @selected={{@selected}}
  @onChange={{@onChange}}
  @options={{this.initialSet}}
  @searchEnabled={{true}}
  @search={{this.search}} as |item|
>
  {{item.name}}
</PowerSelect >
Enter fullscreen mode Exit fullscreen mode
// index.js

import Component from "@glimmer/component";
import { action } from "@ember/object";

export default class MySelectComponent extends Component {
  // Maximum number of items to be displayed in the drop-down.
  MAX_ITEMS = 100;

  // All the items before any filter is applied.
  get initialSet() {
    // We use `null` filter, to limit the number
    // of items in initial list to `MAX_ITEMS` 
    return [...this.filter(null)];
  }

  // Our component receives all the possible options 
  // as `items` argument.
  get allItems() {
    return this.args.items;
  }

  // Search action invoked by the PowerSelect.
  @action
  search(needle) {
    return [...this.filter(needle)];
  }

  // We're going to filter options using a generator
  *filter(needle) {
    const { allItems, maxSize } = this;
    let count = 0;
    let i = 0;

    // Iterate over all items from initial set.
    // Stop when we found maximum number of items
    // or when we went through the whole initial set.
    while (count < this.MAX_ITEMS && i < allItems.length) {
      // If there is no search term, every item matches.
      // Otherwise test current item against given needle.
      if (!needle || this.check(allItems[i], needle)) {
        // If we found a match, yield it.
        yield allItems[i];
        count++;
      }
      i++;
    }
  }

  // Function that will test whether item matches needle
  check(item, needle) {
    // For simplicity let's say each item has property
    // `name` and we just check for a substring match.
    if (item.name.includes(needle)) {
      return true;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

With this approach I'm able to have all the options loaded client-side and display only relatively small portion in the Ember-Power-Select dropdown.

It is worth noting that this approach is suitable only for quite a narrow amount of items. At one point the payload from the server would be too big to transfer / filter on efficiently. Use your own judgement. I myself would look for different approach for bigger numbers than lower thousands of items.


Photo by Wade Austin Ellis on Unsplash

Discussion (2)

pic
Editor guide
Collapse
iamdtang profile image
David Tang

Nice! Have you tried using vertical-collection as the optionsComponent in Power Select?

Collapse
michalbryxi profile image
Michal Bryxí Author

Yes. First I've seen cibernox's own integration attempt which seems unmaintained. So it felt like there is some problem with it. And later on when I tried to integrate it myself, there really was some hard caveat that I can't remember right now 🤔