ember-power-select icon indicating copy to clipboard operation
ember-power-select copied to clipboard

Request: Expose some method for infinite scroll/pagination/scroll bottom

Open luketheobscure opened this issue 7 years ago • 10 comments

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.

luketheobscure avatar Mar 23 '17 16:03 luketheobscure

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 avatar May 15 '17 15:05 kswilster

@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 avatar May 15 '17 16:05 cibernox

@cibernox hi. still nothing here?

heyitskippy avatar Jun 28 '17 14:06 heyitskippy

@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)

NLincoln avatar Jun 28 '17 15:06 NLincoln

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

erichaus avatar Jul 14 '17 23:07 erichaus

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.

luketheobscure avatar Jul 15 '17 04:07 luketheobscure

@erichonkanen You can try project https://github.com/cibernox/ember-power-select-collection

kumkanillam avatar Jul 15 '17 05:07 kumkanillam

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}}

angelomachado avatar Dec 01 '17 19:12 angelomachado

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:

PrzemoRevolve avatar Jun 28 '19 07:06 PrzemoRevolve

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>

martony38 avatar Jun 15 '20 13:06 martony38