You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP
Socket
Sign inDemoInstall
Socket

ember-infinity

Package Overview
Dependencies
Maintainers
2
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ember-infinity - npm Package Compare versions

Comparing version

to
1.0.0-beta.2

addon/services/infinity-loader.js

23

addon/components/infinity-loader.js

@@ -9,4 +9,4 @@ import { alias } from '@ember/object/computed';

const InfinityLoaderComponent = Component.extend(InViewportMixin, {
classNames: ["infinity-loader"],
classNameBindings: ["infinityModelContent.reachedInfinity", "viewportEntered:in-viewport"],
classNames: ['infinity-loader'],
classNameBindings: ['infinityModelContent.reachedInfinity', 'viewportEntered:in-viewport'],
/**

@@ -183,3 +183,9 @@ * @public

function loadPreviousPage() {
this.sendAction('loadPreviousAction', infinityModelContent, -1);
if (typeof(get(this, 'infinityLoad')) === 'function') {
// closure action
return get(this, 'infinityLoad')(infinityModelContent, -1);
} else {
// old action
this.sendAction('loadPreviousAction', infinityModelContent, -1);
}
}

@@ -201,3 +207,12 @@

function loadMore() {
this.sendAction('loadMoreAction', get(this, 'infinityModelContent'));
let infinityModelContent = get(this, 'infinityModelContent');
if (typeof(get(this, 'infinityLoad')) === 'function') {
// closure action
return get(this, 'infinityLoad')(infinityModelContent);
} else {
// old action
this.sendAction('loadMoreAction', infinityModelContent);
}
}

@@ -204,0 +219,0 @@ this._debounceTimer = run.debounce(this, loadMore, get(this, 'eventDebounce'));

254

addon/mixins/route.js

@@ -1,2 +0,2 @@

import { alias } from '@ember/object/computed';
import { readOnly } from '@ember/object/computed';
import EmberError from '@ember/error';

@@ -8,2 +8,3 @@ import InfinityModel from 'ember-infinity/lib/infinity-model';

import { A } from '@ember/array';
import { inject as service } from '@ember/service';
import { computed, get, set } from '@ember/object';

@@ -27,7 +28,11 @@ import { deprecate } from '@ember/application/deprecations';

infinityLoader: service(),
_infinityModels: readOnly('infinityLoader.infinityModels'),
// these are here for backwards compat
_infinityModel: computed('_infinityModels.[]', function() {
_infinityModel: computed('_infinityModels.[]', '_infinityModels', function() {
return get(this, '_infinityModels.firstObject');
}).readOnly(),
currentPage: alias('_infinityModel.currentPage').readOnly(),
currentPage: readOnly('_infinityModel.currentPage'),

@@ -44,6 +49,6 @@ actions: {

infinityLoad(infinityModel, increment = 1) {
let matchingInfinityModel = this._infinityModels.find(model => model === infinityModel);
let matchingInfinityModel = get(this, '_infinityModels').find(model => model === infinityModel);
if (matchingInfinityModel) {
set(infinityModel, '_increment', increment);
this._infinityLoad(matchingInfinityModel, increment);
this.infinityLoad(matchingInfinityModel, increment);
} else {

@@ -56,59 +61,14 @@ return true;

/**
@private
@property _previousScrollHeight
@type Integer
@default 0
*/
_previousScrollHeight: 0,
/**
@private
@property _store
@type String
@default 'store'
*/
_store: 'store',
Proxy to underlying service
/**
The supported findMethod name for
the developers Ember Data version.
Provided here for backwards compat.
@private
@property _storeFindMethod
@type {String}
@default "query"
*/
_storeFindMethod: 'query',
/**
Determine if Ember data is valid
Ensure _store is set on route with a query method
Ensure model passed to infinity model
@method _ensureCompatibility
@method infinityLoad
@param {Ember.ArrayProxy} infinityModel
@param {Integer} increment - to increase page by 1 or -1
@return {Ember.RSVP.Promise}
*/
_ensureCompatibility() {
if (isEmpty(get(this, this._store)) || isEmpty(get(this, this._store)[this._storeFindMethod])){
throw new EmberError("Ember Infinity: Store is not available to infinityModel");
}
infinityLoad(matchingInfinityModel, increment) {
return get(this, 'infinityLoader')['infinityLoad'](matchingInfinityModel, increment);
},
/**
If pass in custom store, ensure passed string
Ensure query method exists, otherwise pass method (that returns a promise) in as storeFindMethod in options
@method _ensureCustomStoreCompatibility
@param {Option} options
*/
_ensureCustomStoreCompatibility(options) {
if (typeof options.store !== 'string') {
throw new EmberError("Ember Infinity: Must pass custom data store as a string");
}
const store = get(this, options.store);
if (!store[get(this, '_storeFindMethod')]) {
throw new EmberError("Ember Infinity: Custom data store must specify query method");
}
},
/**
Use the infinityModel method in the place of `this.store.query('model')` to

@@ -141,4 +101,5 @@ initialize the Infinity Model for your route.

if (!this._infinityModels) {
this._infinityModels = A();
let service = get(this, 'infinityLoader');
if (!get(service, 'infinityModels')) {
set(service, 'infinityModels', A());
}

@@ -150,8 +111,11 @@

if (options.storeFindMethod) {
set(this, '_storeFindMethod', options.storeFindMethod);
set(service, '_storeFindMethod', options.storeFindMethod);
}
this._ensureCustomStoreCompatibility(options);
if (typeof options.store !== 'string') {
throw new EmberError('Ember Infinity: Must pass custom data store as a string');
}
get(this, 'infinityLoader._ensureCustomStoreCompatibility')(options, get(this, options.store), get(service, '_storeFindMethod'));
set(this, '_store', options.store);
set(service, '_store', options.store);

@@ -162,2 +126,5 @@ delete options.store;

set(service, 'store', get(this, get(service, '_store')));
set(service, 'infinityModelLoaded', get(this, 'infinityModelLoaded'));
// default is to start at 0, request next page and increment

@@ -212,6 +179,6 @@ const currentPage = options.startingPage === undefined ? 0 : options.startingPage - 1;

const infinityModel = InfinityModelFactory.create(initParams);
this._ensureCompatibility();
this._infinityModels.pushObject(infinityModel);
get(this, 'infinityLoader._ensureCompatibility')(get(service, 'store'), get(service, '_storeFindMethod'));
get(this, 'infinityLoader.infinityModels').pushObject(infinityModel);
return InfinityPromiseArray.create({ promise: this._loadNextPage(infinityModel) });
return InfinityPromiseArray.create({ promise: service['loadNextPage'](infinityModel) });
},

@@ -237,144 +204,2 @@

/**
Call additional functions after finding the infinityModel in the Ember store.
@private
@method _afterInfinityModel
@param {Function} infinityModelPromise The resolved result of the Ember store find method. Passed in automatically.
@return {Ember.RSVP.Promise}
*/
_afterInfinityModel(_this) {
return function(infinityModelPromiseResult, infinityModel) {
if (typeof _this.afterInfinityModel === 'function') {
let result = _this.afterInfinityModel(infinityModelPromiseResult, infinityModel);
if (result) {
return result;
}
}
return infinityModelPromiseResult;
};
},
/**
Trigger a load of the next page of results.
@private
@method _infinityLoad
@param {Ember.ArrayProxy} infinityModel
@param {Integer} increment - to increase page by 1 or -1
*/
_infinityLoad(infinityModel, increment) {
if (get(infinityModel, '_loadingMore') || !get(infinityModel, '_canLoadMore')) {
return;
}
this._loadNextPage(infinityModel, increment);
},
/**
load the next page from the adapter and update the model
set current height of elements. If loadPrevious, we will use this value to scroll back down the page
@private
@method _loadNextPage
@param {Ember.ArrayProxy} infinityModel
@param {Integer} increment - to increase page by 1 or -1. Default to increase by one page
@return {Ember.RSVP.Promise} A Promise that resolves the model
*/
_loadNextPage(infinityModel, increment = 1) {
set(infinityModel, '_loadingMore', true);
set(this, '_previousScrollHeight', this._calculateHeight(infinityModel));
const modelName = get(infinityModel, '_infinityModelName');
const params = infinityModel.buildParams(increment);
return this._requestNextPage(modelName, params)
.then(newObjects => this._afterInfinityModel(this)(newObjects, infinityModel))
.then(newObjects => this._doUpdate(newObjects, infinityModel))
.then(infinityModel => {
if (increment === 1) {
// scroll down to load next page
infinityModel.incrementProperty('currentPage');
} else {
if (typeof FastBoot === 'undefined') {
let viewportElem = get(infinityModel, '_scrollable') ? document.querySelector(get(infinityModel, '_scrollable')) : document.documentElement;
scheduleOnce('afterRender', this, '_updateScrollTop', { infinityModel, viewportElem });
// scrolled up to load previous page
infinityModel.decrementProperty('currentPage');
}
}
set(infinityModel, '_firstPageLoaded', true);
let canLoadMore = get(infinityModel, '_canLoadMore');
set(infinityModel, 'reachedInfinity', !canLoadMore);
if (!canLoadMore) { this._notifyInfinityModelLoaded(); }
return infinityModel;
}).finally(() => set(infinityModel, '_loadingMore', false));
},
/**
@private
@method _calculateHeight
@param {Object} infinityModel
@return Integer
*/
_calculateHeight(infinityModel) {
if (typeof FastBoot === 'undefined') {
let viewportElem = get(infinityModel, '_scrollable') ? document.querySelector(get(infinityModel, '_scrollable')) : document.documentElement;
return get(infinityModel, '_scrollable') ? viewportElem.scrollHeight : viewportElem.scrollHeight;
}
},
/**
This method calculates the difference if loadPrevious=true
The browser by default will scroll to the top of the element list when the previous page
loads. As a result, we need to scroll back down the page.
The math behind this is as follows:
(height after loading previous elems) - (old height)
So 150px - 100px === 150px
178px - 100px = 78px
120px - 10px = 110px
@private
@method _updateScrollTop
@return Integer
*/
_updateScrollTop({ infinityModel, viewportElem }) {
let scrollDiff = this._calculateHeight(infinityModel) - get(this, '_previousScrollHeight');
viewportElem.scrollTop += scrollDiff;
},
/**
request the next page from the adapter
@private
@method _requestNextPage
@param {String} modelName
@param {Object} options
@returns {Ember.RSVP.Promise} A Promise that resolves the next page of objects
*/
_requestNextPage(modelName, params) {
return get(this, this._store)[this._storeFindMethod](modelName, params);
},
/**
set _totalPages param on infinityModel
Update the infinity model with new objects with either adding to end or start of Array of objects
@private
@method _doUpdate
@param {Ember.Enumerable} queryObject The new objects to add to the model
@param {Ember.ArrayProxy} infinityModel
@return {Ember.Array} returns the new objects
*/
_doUpdate(queryObject, infinityModel) {
const totalPages = queryObject.get(get(infinityModel, 'totalPagesParam'));
set(infinityModel, '_totalPages', totalPages);
set(infinityModel, 'meta', get(queryObject, 'meta'));
if (infinityModel.get('_increment') === 1) {
return infinityModel.pushObjects(queryObject.toArray());
} else {
return infinityModel.unshiftObjects(queryObject.toArray());
}
},
/**
notify that the infinity model has been updated

@@ -399,17 +224,2 @@

});
},
/**
finish the loading cycle by notifying that infinity has been reached
@private
@method _notifyInfinityModelLoaded
*/
_notifyInfinityModelLoaded() {
if (!this.infinityModelLoaded) {
return;
}
const totalPages = get(this, '_totalPages');
scheduleOnce('afterRender', this, 'infinityModelLoaded', { totalPages: totalPages });
}

@@ -416,0 +226,0 @@ });

{
"name": "ember-infinity",
"version": "1.0.0-beta.1",
"version": "1.0.0-beta.2",
"description": "Simple, flexible infinite scroll for Ember CLI Apps.",

@@ -25,3 +25,3 @@ "keywords": [

"ember-cli-babel": "^6.6.0",
"ember-in-viewport": "~3.0.0"
"ember-in-viewport": "~3.0.1"
},

@@ -37,2 +37,3 @@ "devDependencies": {

"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-mirage": "^0.4.3",
"ember-cli-pretender": "^1.0.1",

@@ -39,0 +40,0 @@ "ember-cli-qunit": "^4.1.1",

@@ -27,4 +27,13 @@ # Ember Infinity

Importing the ember-infinity Route Mixin and extend your route will give you access to this.infinifyModel in your model hook.
`ember-infinity` exposes 3 consumable items for your application.
· **Route Mixin**
· **infinity-loader component**
· **infinity-loader service**
Importing the `ember-infinity` Route Mixin and extending your route will give you access to `this.infinifyModel` in your model hook.
```js

@@ -42,3 +51,3 @@ import Route from '@ember/routing/route';

Then, you'll need to add the Infinity Loader component to your template, like so, in which `model` is an instance of InfinityModel returned from your model hook.
Then, you'll need to add the `infinity-loader` component to your template, like so, in which `model` is an instance of InfinityModel returned from your model hook.

@@ -54,9 +63,188 @@ ```hbs

Now, whenever the `infinity-loader` is in view, it will send an action to the route
(the one where you initialized the infinityModel) to start loading the next page.
Now, whenever the `infinity-loader` component is in view, it will send an action to the route or to your specific `loadMoreProduct` action
(the one where you initialized the infinityModel) to start loading the next page. This method uses action bubbling, which may not be the preferred way of passing data around your application. See [Closure Actions](#ClosureActions).
When the new records are loaded, they will automatically be pushed into the Model array.
Lastly, by default, ember-infinity expects the server response to contain something about how many total pages it can expect to fetch. ember-infinity defaults to looking for something like `meta: { total_pages: 20 }` in your response. See [Advanced Usage](#AdvancedUsage).
Lastly, by default, ember-infinity expects the server response to contain something about how many total pages it can expect to fetch. `ember-infinity` defaults to looking for something like `meta: { total_pages: 20 }` in your response. See [Advanced Usage](#AdvancedUsage).
### Closure Actions<a name="ClosureActions"></a>
If you want to use closure actions with `ember-infinity` and the `infinity-loader` component, you need to be a little bit more explicit. No more secret bubbling of an `infinityLoad` action up to your route. This is how your code will look like with controller actions.
See the Ember docs on passing actions to components [here](https://guides.emberjs.com/v3.0.0/components/triggering-changes-with-actions/#toc_passing-the-action-to-the-component).
```js
import Controller from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default Controller.extend({
infinityLoader: service(),
actions: {
/**
Note this must be handled by you. An action will be called with the result of your Route model hook from the infinityLoader component, similar to this:
// closure action in infinity-loader component
get(this, 'infinityLoad')(infinityModelContent);
@method loadMoreProduct
@param {InfinityModel} products
*/
loadMoreProduct(products) {
get(this, 'infinityLoader').infinityLoad(products);
}
}
});
```
```js
import Route from '@ember/routing/route';
import InfinityRoute from "ember-infinity/mixins/route";
export default Route.extend(InfinityRoute, {
model() {
return this.infinityModel("product");
}
});
```
```hbs
{{!-- some nested component in your template file where action bubbling does not reach your route --}}
{{#each model as |product|}}
<h1>{{product.name}}</h1>
<h2>{{product.description}}</h2>
{{/each}}
{{infinity-loader infinityModel=model infinityLoad=(action "loadMoreProduct")}}
```
### Multiple Infinity Models in one Route
Let's look at a more complicated example using multiple infinity models in a route.
```js
import Controller from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default Controller.extend({
infinityLoader: service(),
actions: {
/**
Note this must be handled by you. An action will be called with the result of your Route model hook from the infinityLoader component, similar to this:
// closure action in infinity-loader component
get(this, 'infinityLoad')(infinityModelContent);
@method loadMoreProduct
@param {InfinityModel} products
*/
loadMoreProduct(products) {
get(this, 'infinityLoader').infinityLoad(products);
}
/**
@method loadMoreUsers
@param {InfinityModel} users
*/
loadMoreUsers(users) {
get(this, 'infinityLoader').infinityLoad(users);
}
}
});
```
```js
import Route from '@ember/routing/route';
import RSVP from 'rsvp';
import InfinityRoute from "ember-infinity/mixins/route";
export default Route.extend(InfinityRoute, {
model() {
return RSVP.hash({
products: this.infinityModel("product"),
users: this.infinityModel("user")
});
}
});
```
```hbs
{{!-- templates/products.hbs --}}
<aside>
{{#each model.users as |user|}}
<h1>{{user.username}}</h1>
{{/each}}
{{infinity-loader infinityModel=model.users infinityLoad=(action "loadMoreUsers")}}
</aside>
<section>
{{#each model.products as |product|}}
<h1>{{product.name}}</h1>
<h2>{{product.description}}</h2>
{{/each}}
{{infinity-loader infinityModel=model.products infinityLoad=(action "loadMoreProduct")}}
<section>
```
The ability to use closure actions will be available in the `1.0.0-beta` series. Also, this method uses Controllers. Despite what you may have heard, Controllers are a great primitive in Ember's ecosystem. Their singleton nature is great for handling queryParams and actions propagated from somewhere in your component tree.
### Service Methods
The infinity-loader service exposes 4 methods:
1. replace
2. flush
3. pushObjects
3. unshiftObjects
Let's see an example of using `replace`.
```js
import Controller from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default Controller.extend({
infinityLoader: service(),
actions: {
/**
@method filterProducts
@param {String} query
*/
async filterProducts(query) {
let products = await this.store.query('product', { query });
// model is the collection returned from the route model hook
get(this, 'infinityLoader').replace(get(this, 'model'), products);
}
}
});
```
```js
import Route from '@ember/routing/route';
import InfinityRoute from "ember-infinity/mixins/route";
export default Route.extend(InfinityRoute, {
model() {
return this.infinityModel("product");
}
});
```
```hbs
<input type="search" placeholder="Search Products" oninput={{action "filterProducts"}} />
{{#each model as |product|}}
<h1>{{product.name}}</h1>
<h2>{{product.description}}</h2>
{{/each}}
{{infinity-loader infinityModel=model infinityLoad=(action "loadMoreProduct")}}
```
### Non-Blocking Model Hooks

@@ -302,6 +490,11 @@

* totalPages
**infinityModelLoaded**
* lastPageLoaded
* totalPages
* infinityModel
Triggered on the route when the infinityModel is fully loaded.

@@ -311,9 +504,5 @@

* lastPageLoaded
* totalPages
* infinityModel
```js

@@ -365,6 +554,19 @@ import Ember from 'ember';

* **infinityLoad**
Closure actions are enabled in the `1.0.0-beta` series.
```hbs
{{infinity-loader
infinityModel=model
infinityLoad=(action "loadMoreProducts")}}
```
* **hideOnInfinity**
```hbs
{{infinity-loader infinityModel=model hideOnInfinity=true}}
{{infinity-loader
infinityModel=model
infinityLoad=(action "loadMoreProducts")
hideOnInfinity=true}}
```

@@ -379,3 +581,6 @@

```hbs
{{infinity-loader infinityModel=model developmentMode=true}}
{{infinity-loader
infinityModel=model
infinityLoad=(action "loadMoreProducts")
developmentMode=true}}
```

@@ -389,3 +594,7 @@

```hbs
{{infinity-loader infinityModel=model loadingText="Loading..." loadedText="Loaded!"}}
{{infinity-loader
infinityModel=model
infinityLoad=(action "loadMoreProducts")
loadingText="Loading..."
loadedText="Loaded!"}}
```

@@ -398,3 +607,3 @@

```hbs
{{#infinity-loader infinityModel=model}}
{{#infinity-loader infinityModel=model infinityLoad=(action "infinityLoad")}}
<img src="loading-spinner.gif" />

@@ -526,3 +735,3 @@ {{/infinity-loader}}

{{infinity-loader
infinityModel=content
infinityModel=model
loadPrevious=true

@@ -532,3 +741,3 @@ loadedText=null

{{#each content as |item|}}
{{#each model as |item|}}
<li>{{item.id}}. {{item.name}}</li>

@@ -538,3 +747,3 @@ {{/each}}

{{infinity-loader
infinityModel=content
infinityModel=model
loadingText="Loading more awesome records..."

@@ -541,0 +750,0 @@ loadedText="Loaded all the records!"

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet