ember-power-select
ember-power-select copied to clipboard
Request: Expose some method for infinite scroll/pagination/scroll bottom
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 adiv
(which isn't a deal breaker) - The
500
ms delay is arbitrary, and subject to race conditions. Scheduling itafterRender
orrun.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.
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 onscroll
action 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.
@erichonkanen You can try project https://github.com/cibernox/ember-power-select-collection
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>