Brandon Drake
Aug 17, 2017 · 5 min read

Infinite Scrolling with Power Select

First, we need to decide how we’re going to lazy-load our list. I decided that the best way to do it for my needs was to put a element at the end of the list that would trigger a load event whenever became visible to the user. This allows us to only load a small portion of the data upfront but still allow the user to scroll through everything if they feel the need to do so.

<!--  https://github.com/cibernox/ember-power-select/blob/master/addon/templates/components/power-select/options.hbs -->
{{#if select.loading}}
{{#if loadingMessage}}
<li class="ember-power-select-option ember-power-select-option--loading-message" role="option">{{loadingMessage}}</li>
{{/if}}
{{/if}}
{{#each options as |opt index|}}
{{#if (ember-power-select-is-group opt)}}
{{#component groupComponent
group=(readonly opt)
select=(readonly select)
extra=(readonly extra)
}}
{{#component optionsComponent
options=(readonly opt.options)
select=(readonly select)
groupIndex=(concat groupIndex index ".")
optionsComponent=(readonly optionsComponent)
groupComponent=(readonly groupComponent)
extra=(readonly extra)
role="group"
class="ember-power-select-options" as |option|}}
{{yield option select}}
{{/component}}
{{/component}}
{{else}}
<li class="ember-power-select-option"
aria-selected="{{ember-power-select-is-selected opt select.selected}}"
aria-disabled={{ember-power-select-true-string-if-present opt.disabled}}
aria-current="{{eq opt select.highlighted}}"
data-option-index="{{groupIndex}}{{index}}"
role="option">
{{yield opt select}}
</li>
{{/if}}
{{/each}}
import Ember from 'ember';

import PSOptionsComponent from 'ember-power-select/components/power-select/options';
const LazyOptionsComponent = PSOptionsComponent.extend({
// TODO
});
export default LazyOptionsComponent;

Customizing the Template

Per the design, we need an element at the bottom that shows when we have more items to load. We will add the following code to the bottom of our template, template/components/lazy-options.hbs, to accomplish this

{{#if (or isLoading select.loading)}}
Loading...
{{else if canLoadMore}}
<li class="ember-power-select-option ember-power-select-option--load-more" {{action loadMore}}>
Load More
</li>
{{/if}}

Extending jQuery

Next we need to extend jQuery with a custom function checkInView. I’ve placed mine in utils/extensions/jquery.js and have called it checkInView

import Ember from 'ember';

const {
$,
} = Ember;

function checkInView() {
if ($.fn.checkInView) { return; }

$.fn.checkInView = function(partial = false) {
const elem = this;

if (!elem.length) { return false; }

const container = elem.parent();
const contHeight = container.height();
const contTop = container.scrollTop();
const contBottom = contTop + contHeight;

const elemTop = elem.offset().top - container.offset().top;
const elemBottom = elemTop + elem.height();

const isTotal = elemTop >= 0 && elemBottom <= contHeight;
const isPart = (elemTop < 0 && elemBottom > 0 || elemTop > 0 && elemTop <= container.height()) && partial;

return isTotal || isPart;
};
}

export { checkInView };
import { checkInView } from '../utils/extensions/jquery';

export function initialize() {
// Add jQuery extension calls here
checkInView();
}

export default {
name: 'jq-extensions',
initialize: initialize
};

Triggering the Auto-Load

With our newly create jQuery extension, we’re ready to trigger the load event automatically. We will return to our component and update it with the following

//component/lazy-options.js
import Ember from 'ember';
import PSOptionsComponent from 'ember-power-select/components/power-select/options';const {
$,
assert,
get,
} = Ember;
const OptionsComponent = PSOptionsComponent.extend({
canLoadMore: false,
init() {
this._super(...arguments);
assert('<component:infinite-options>: You must provide a closure action name `loadMore`', get(this, 'loadMore') && typeof get(this, 'loadMore') === 'function');
},
didInsertElement() {
this._super(...arguments);
const checkVisibility = () => {
if ($(this.element).find('.ember-power-select-option--load-more').checkInView(true)) {
const loadMore = get(this, 'loadMore');
return loadMore();
}
};
$(this.element).on('scroll', () => checkVisibility());
},
willDestroyElement() {
this._super(...arguments);
$(this.element).off('scroll');
}
});export default OptionsComponent;

Using The Component

Since API tend to use different pagination schemas, I’ve left that part to the consumer to handle rather than bake it into our component. This leaves us with a more flexible API on the component it’s self since it only require the user to provide a maximum of three properties, loadMore, canLoadMore, and isLoading.

{{#power-select optionsComponent=(component 'lazy-options'
loadMore=(perform task__loadMore)
isLoading=isLoading
canLoadMore=canLoadMore
)
options=colors
selected=selectedColor
onchange=(action (mut selectedColor))
as |option|}}
{{option}}
{{/power-select}}

Demo Time

Here is a working, semi real-world, Twiddle of how you’d use the lazy-options component in your own code.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade