Ember-power-select: Request: Expose some method for infinite scroll/pagination/scroll bottom

Created on 23 Mar 2017  路  10Comments  路  Source: cibernox/ember-power-select

I've been trying to implement infinite scroll with ember-power-select, and none of the available public API's allow a clean implementation. The closest I got was using the onopen hook, but since the list is not rendered when onopen fires the implementation looks like this (CoffeeScript.... don't judge...):

onopen: (options, e) ->
  Ember.run.later =>
    element = $(e.target).parents('.ember-basic-dropdown').find('ul')[0]
    element?.addEventListener 'scroll', =>
      if element.scrollHeight - element.scrollTop is element.clientHeight
        @loadMore()
  , 500

There's two issues with this approach:

  • The event target is sometimes the selected item span, sometimes a div (which isn't a deal breaker)
  • The 500 ms delay is arbitrary, and subject to race conditions. Scheduling it afterRender or run.next didn't work (which is a deal breaker)

The way I see it, there's three potential solutions:

  • Moving onopen to fire after the list renders
  • Exposing an afteropen event
  • Exposing a scrollbottom event

Implementing a scrollbottom isn't too difficult. I got it mostly working by simply dropping this in didInsertElement at addon/components/power-select/options.js:

    this.element.addEventListener('scroll', (e) => {
      if(this.element.scrollHeight - this.element.scrollTop == this.element.clientHeight){
        this.send('scrollBottom');
      }
    });

I might be able to throw some time at this if the maintainer(s?) would like to give some direction.

Most helpful comment

We worked around it by using the afterOptionsComponent hook. We've got a component that renders a "Load more..." button that loads in the next page of records. It's working really well for us.

All 10 comments

Just chiming in to add another perspective. I have also implemented a custom scroll action, but I want to page more options in before hitting the bottom of my options. Adding a debounced scroll event listener to ember-power-select allows me to determine my own loading threshold. i.e.

scrolled(event) {
      const { scrollHeight, scrollTop, clientHeight } = event.target;
      const distanceFromBottom = scrollHeight - (scrollTop + clientHeight);

      if (distanceFromBottom < this.get('loadMoreThreshold')) {
        this.get('loadMore').perform();
      }
    },

I'd love to contribute and put up a PR exposing the onscrollaction that makes this possible, just want to get confirmation that this is the right approach or that this is a valid concern of ember-power-select

@kswilster The idea is good but I want to hold that of until I've refactored EPS to use contextual components. There is already too many option in the top level.

The idea would be:

{{#power-select-basic as |select|}}
  {{select.triggerComponent}}

  {{#select.contentComponent}}
    {{select.beforeOptionsComponent}}
    {{select.optionsComponent onscroll=(action "yourAction")}}
  {{/select.contentComponent}}

{{/power-select-basic}}

By splitting the select into sub-components we avoid having the top-level {{power-select}} being the single entry point for every single option. I want to do it in the next 3/4 weeks.

@cibernox hi. still nothing here?

@heyitskippy I have a fork going that has power-select-basic. #923

It has no docs or tests written, so use at your own risk. I'll be working on getting that working when I have time again (feel free to contribute)

any movement on this? our project def gonna need pagination as I can't imagine loading possibly thousands of records into the list..

We worked around it by using the afterOptionsComponent hook. We've got a component that renders a "Load more..." button that loads in the next page of records. It's working really well for us.

Ember Light Table does something very similar by using Ember Scrollable under the hood. It would be awesome to have something like that for infinite scroll /pagination.

{{#power-select-basic as |select|}}
  {{select.triggerComponent}}

  {{#select.contentComponent}}
    {{select.beforeOptionsComponent}}
    {{select.optionsComponent
        onScroll=(action 'onScroll')
        onScrolledToBottom=(action 'onScrolledToBottom')
    }}
  {{/select.contentComponent}}

{{/power-select-basic}}

In case anyone is interested - we're managed to achieve pagination by extending power-select/options in a custom component:

//scroll-aware-options.ts
import Options from 'ember-power-select/components/power-select/options';

export default class ScrollAwareOptions extends Options {
  onScroll = () => {}

  didInsertElement() {
    super.didInsertElement();
    this.element.addEventListener('scroll', this.onScroll, { passive: true });
  }

  willDestroyElement() {
    super.willDestroyElement();
    this.element.removeEventListener('scroll', this.onScroll);
  }

}

and then we've used a little trick with optionsComponent attribute of power-select:

// our-select.hbs
{{#power-select
  // ...some attributes
  optionsComponent=(component "scroll-aware-options" onScroll=(action "onScroll"))
}}

This way we didn't have to fork/copy-paste anything to make it work. Hope it helps somebody :grin:

I ended up using @PrzemoRevolve solution updated for glimmer components using modifiers to register the scroll listener:

// scroll-aware-options.js
import Component from '@glimmer/component';
import {action} from '@ember/object';

export default class ScrollAwareOptionComponent extends Component {
  @action
  registerScrollListener(element) {
    element.addEventListener('scroll', this.args.onScroll, {passive: true});
  }

  @action
  unregisterScrollListener(element) {
    element.removeEventListener('scroll', this.args.onScroll);
  }
}
{{! scroll-aware-options.hbs }}
<PowerSelect::Options
  {{did-insert this.registerScrollListener}}
  {{will-destroy this.unregisterScrollListener}}
  @loadingMessage={{@loadingMessage}}
  @select={{@select}}
  @options={{@options}}
  @groupIndex={{@groupIndex}}
  @optionsComponent={{@optionsComponent}}
  @extra={{@extra}}
  @highlightOnHover={{@highlightOnHover}}
  @groupComponent={{@groupComponent}}
  class="ember-power-select-options"
  as |option select|
>
  {{yield option select}}
</PowerSelect::Options>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kybishop picture kybishop  路  4Comments

knownasilya picture knownasilya  路  10Comments

davelowensohn picture davelowensohn  路  4Comments

angelosarto picture angelosarto  路  8Comments

orf picture orf  路  10Comments