From 54c92f73e32c0dea3a5e40ef2251b89efd110456 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 13:53:50 -0700 Subject: [PATCH 01/12] Replace Ember.Evented for own implementation --- .eslintrc.js | 1 + addon/-private/defaults.js | 185 ++++++++++++++++ addon/-private/evented.js | 35 +++ addon/-private/notifier.js | 36 +++ addon/components/infinity-loader.js | 6 +- addon/lib/infinity-model.js | 259 ++++------------------ addon/services/infinity.js | 10 +- addon/utils.js | 7 +- package-lock.json | 39 +++- package.json | 7 +- tests/integration/infinity-loader-test.js | 2 +- 11 files changed, 356 insertions(+), 231 deletions(-) create mode 100644 addon/-private/defaults.js create mode 100644 addon/-private/evented.js create mode 100644 addon/-private/notifier.js diff --git a/.eslintrc.js b/.eslintrc.js index 3a1abe9e..93a9cfb5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { root: true, + parser: 'babel-eslint', parserOptions: { ecmaVersion: 2018, sourceType: 'module' diff --git a/addon/-private/defaults.js b/addon/-private/defaults.js new file mode 100644 index 00000000..9bd55bc0 --- /dev/null +++ b/addon/-private/defaults.js @@ -0,0 +1,185 @@ +export const DEFAULTS = { + /** + Increases or decreases depending on scroll direction + + @private + @property currentPage + @type Integer + @default 0 + */ + currentPage: 0, + + /** + @private + @property extraParams + @type Object + @default null + */ + extraParams: null, + + /** + Used as a marker for the page the route starts on + + @private + @property firstPage + @type Integer + @default 0 + */ + firstPage: 0, + + /** + @public + @property isError + @type Boolean + @default false + */ + isError: false, + + /** + @public + @property isLoaded + @type Boolean + @default false + */ + isLoaded: false, + + /** + @public + @property loadingMore + @type Boolean + @default false + */ + loadingMore: false, + + /** + Arbitrary meta copied over from + the HTTP response, to maintain the + default behavior of ember-data requests + @type objects + @default null + */ + meta: null, + + /** + @private + @property _perPage + @type Integer + @default 25 + */ + perPage: 25, + + /** + @public + @property reachedInfinity + @default false + */ + reachedInfinity: false, + + /** + @public + @property store + @default null + */ + store: null, + + /** + Name of the "per page" param in the + resource request payload + @type {String} + @default "per_page" + */ + perPageParam: 'per_page', + + /** + Name of the "page" param in the + resource request payload + @type {String} + @default "page" + */ + pageParam: 'page', + + /** + Path of the "total pages" param in + the HTTP response + @type {String} + @default "meta.total_pages" + */ + totalPagesParam: 'meta.total_pages', + + /** + Path of the "count" param in indicating + number of records from HTTP response + @type {String} + @default "meta.count" + */ + countParam: 'meta.count', + + /** + The supported findMethod name for + the developers Ember Data version. + Provided here for backwards compat. + @public + @property storeFindMethod + @default null + */ + storeFindMethod: null, + + /** + @private + @property _count + @type Integer + @default 0 + */ + _count: 0, + + /** + @private + @property _totalPages + @type Integer + @default 0 + */ + _totalPages: 0, + + /** + @private + @property _infinityModelName + @type String + @default null + */ + _infinityModelName: null, + + /** + @private + @property _firstPageLoaded + @type Boolean + @default false + */ + _firstPageLoaded: false, + + /** + @private + @property _increment + @type Integer + @default 1 + */ + _increment: 1, + + /** + simply used for previous page scrolling abilities and passed from + infinity-loader component and set on infinityModel + @private + @property _scrollable + @type Integer + @default null + */ + _scrollable: null, + + /** + determines if can load next page or previous page (if applicable) + + @private + @property _canLoadMore + @type Boolean + */ + _canLoadMore: null +} diff --git a/addon/-private/evented.js b/addon/-private/evented.js new file mode 100644 index 00000000..0b7dbf8c --- /dev/null +++ b/addon/-private/evented.js @@ -0,0 +1,35 @@ +import Notifier from './notifier'; + +export default class Evented { + on(eventName, listener) { + return notifierForEvent(this, eventName).addListener(listener); + } + + off(eventName, listener) { + return notifierForEvent(this, eventName).removeListener(listener); + } + + trigger(eventName, ...args) { + const notifier = notifierForEvent(this, eventName); + if (notifier) { + notifier.trigger.apply(notifier, args); + } + } +} + +function notifierForEvent( + object, + eventName +) { + if (object._eventedNotifiers === undefined) { + object._eventedNotifiers = {}; + } + + let notifier = object._eventedNotifiers[eventName]; + + if (!notifier) { + notifier = object._eventedNotifiers[eventName] = new Notifier(); + } + + return notifier; +} diff --git a/addon/-private/notifier.js b/addon/-private/notifier.js new file mode 100644 index 00000000..9d6506c4 --- /dev/null +++ b/addon/-private/notifier.js @@ -0,0 +1,36 @@ +export default class Notifier { + constructor() { + this.listeners = []; + } + + /** + * Add a callback as a listener, which will be triggered when sending + * notifications. + */ + addListener(listener) { + this.listeners.push(listener); + + return () => this.removeListener(listener); + } + + /** + * Remove a listener so that it will no longer receive notifications. + */ + removeListener(listener) { + const listeners = this.listeners; + + for (let i = 0, len = listeners.length; i < len; i++) { + if (listeners[i] === listener) { + listeners.splice(i, 1); + return; + } + } + } + + /** + * Notify registered listeners. + */ + trigger(...args) { + this.listeners.slice(0).forEach(listener => listener(...args)); + } +} diff --git a/addon/components/infinity-loader.js b/addon/components/infinity-loader.js index 1aadf002..b9cffb45 100644 --- a/addon/components/infinity-loader.js +++ b/addon/components/infinity-loader.js @@ -123,7 +123,7 @@ const InfinityLoaderComponent = Component.extend(InViewportMixin, { get(this, 'infinityModelContent') .then((infinityModel) => { - infinityModel.off('infinityModelLoaded', this, this._loadStatusDidChange); + infinityModel.off('infinityModelLoaded', this, this._loadStatusDidChange.bind(this)); }); this.removeObserver('infinityModel', this, this._initialInfinityModelSetup); @@ -166,7 +166,7 @@ const InfinityLoaderComponent = Component.extend(InViewportMixin, { _initialInfinityModelSetup() { get(this, 'infinityModelContent') .then((infinityModel) => { - infinityModel.on('infinityModelLoaded', this, this._loadStatusDidChange); + infinityModel.on('infinityModelLoaded', this._loadStatusDidChange.bind(this)); set(infinityModel, '_scrollable', get(this, 'scrollable')); set(this, 'isDoneLoading', false); if (!get(this, 'hideOnInfinity')) { @@ -238,7 +238,7 @@ const InfinityLoaderComponent = Component.extend(InViewportMixin, { // service action get(this, 'infinity').infinityLoad(content, 1) .then(() => { - if (get(content, '_canLoadMore')) { + if (get(content, 'canLoadMore')) { this._checkScrollableHeight(); } }); diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index b3752676..35babdfb 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -1,201 +1,32 @@ import ArrayProxy from "@ember/array/proxy" -import Evented from '@ember/object/evented'; -import { oneWay } from '@ember/object/computed'; +import Evented from "../-private/evented" +import { DEFAULTS } from "../-private/defaults" import { computed, get, set, getProperties } from '@ember/object'; import { objectAssign } from '../utils'; -import { typeOf } from '@ember/utils'; import { resolve } from 'rsvp'; +function copyEventedProperties(target, source) { + for (let key of Object.getOwnPropertyNames(source)) { + if (key !== 'constructor' && key !== 'prototype' && key !== 'name') { + let desc = Object.getOwnPropertyDescriptor(source, key); + Object.defineProperty(target, key, desc); + } + } +} + /** @class InfinityModel @namespace EmberInfinity @module ember-infinity/lib/infinity-model @extends Ember.ArrayProxy */ -export default ArrayProxy.extend(Evented, { - /** - Increases or decreases depending on scroll direction - - @private - @property currentPage - @type Integer - @default 0 - */ - currentPage: 0, - - /** - @private - @property extraParams - @type Object - @default null - */ - extraParams: null, - - /** - Used as a marker for the page the route starts on - - @private - @property firstPage - @type Integer - @default 0 - */ - firstPage: 0, - - /** - @public - @property isError - @type Boolean - @default false - */ - isError: false, - - /** - @public - @property isLoaded - @type Boolean - @default false - */ - isLoaded: false, - - /** - @public - @property loadingMore - @type Boolean - @default false - */ - loadingMore: false, - - /** - Arbitrary meta copied over from - the HTTP response, to maintain the - default behavior of ember-data requests - @type objects - @default null - */ - meta: null, - - /** - @private - @property _perPage - @type Integer - @default 25 - */ - perPage: 25, +class InfinityModel extends ArrayProxy { + constructor(...args) { + super(...args); - /** - @public - @property reachedInfinity - @default false - */ - reachedInfinity: false, - - /** - @public - @property store - @default null - */ - store: null, - - /** - Name of the "per page" param in the - resource request payload - @type {String} - @default "per_page" - */ - perPageParam: 'per_page', - - /** - Name of the "page" param in the - resource request payload - @type {String} - @default "page" - */ - pageParam: 'page', - - /** - Path of the "total pages" param in - the HTTP response - @type {String} - @default "meta.total_pages" - */ - totalPagesParam: 'meta.total_pages', - - /** - Path of the "count" param in indicating - number of records from HTTP response - @type {String} - @default "meta.count" - */ - countParam: 'meta.count', - - /** - The supported findMethod name for - the developers Ember Data version. - Provided here for backwards compat. - @public - @property storeFindMethod - @default null - */ - storeFindMethod: null, - - /** - @private - @property _count - @type Integer - @default 0 - */ - _count: 0, - - /** - @private - @property _totalPages - @type Integer - @default 0 - */ - _totalPages: 0, - - /** - @private - @property _infinityModelName - @type String - @default null - */ - _infinityModelName: null, - - /** - @private - @property _firstPageLoaded - @type Boolean - @default false - */ - _firstPageLoaded: false, - - /** - @private - @property _increment - @type Integer - @default 1 - */ - _increment: 1, - - /** - simply used for previous page scrolling abilities and passed from - infinity-loader component and set on infinityModel - @private - @property _scrollable - @type Integer - @default null - */ - _scrollable: null, - - /** - determines if can load next page or previous page (if applicable) - - @private - @property _canLoadMore - @type Boolean - */ - _canLoadMore: oneWay('canLoadMore'), + copyEventedProperties(this, Evented.prototype); + objectAssign(this, { ...DEFAULTS }); + } /** determines if can load next page or previous page (if applicable) @@ -205,27 +36,31 @@ export default ArrayProxy.extend(Evented, { @type Boolean @default false */ - canLoadMore: computed('_totalPages', '_count', 'currentPage', '_increment', { - get() { - let { _count, _totalPages , currentPage, perPage, _increment } = getProperties(this, '_count', '_totalPages', 'currentPage', 'perPage', '_increment'); - let shouldCheck = _increment === 1 && currentPage !== undefined; - if (shouldCheck) { - if (_totalPages) { - return (currentPage < _totalPages) ? true : false; - } else if (_count) { - return (currentPage < _count / perPage) ? true : false; - } - } - if (get(this, 'firstPage') > 1) { - // load previous page if starting page was not 1. Otherwise ignore this block - return get(this, 'firstPage') > 1 ? true : false; + @computed('_totalPages', '_count', 'currentPage', '_increment') + get canLoadMore() { + if (this._canLoadMore) { + return this._canLoadMore; + } + + let { _count, _totalPages , currentPage, perPage, _increment } = getProperties(this, '_count', '_totalPages', 'currentPage', 'perPage', '_increment'); + let shouldCheck = _increment === 1 && currentPage !== undefined; + if (shouldCheck) { + if (_totalPages) { + return (currentPage < _totalPages) ? true : false; + } else if (_count) { + return (currentPage < _count / perPage) ? true : false; } - return false; - }, - set(key, value) { - set(this, '_canLoadMore', value); } - }), + if (get(this, 'firstPage') > 1) { + // load previous page if starting page was not 1. Otherwise ignore this block + return get(this, 'firstPage') > 1 ? true : false; + } + return false; + } + + set canLoadMore(value) { + set(this, '_canLoadMore', value); + } /** build the params for the next page request @@ -237,15 +72,15 @@ export default ArrayProxy.extend(Evented, { buildParams(increment) { const pageParams = {}; let { perPageParam, pageParam } = getProperties(this, 'perPageParam', 'pageParam'); - if (typeOf(perPageParam) === 'string') { + if (typeof perPageParam === 'string') { pageParams[perPageParam] = get(this, 'perPage'); } - if (typeOf(pageParam) === 'string' ) { + if (typeof pageParam === 'string' ) { pageParams[pageParam] = get(this, 'currentPage') + increment; } return objectAssign(pageParams, get(this, 'extraParams')); - }, + } /** abstract after-model hook, can be overridden in subclasses @@ -260,14 +95,14 @@ export default ArrayProxy.extend(Evented, { afterInfinityModel(newObjects/*, infinityModel*/) { // override in your subclass to customize return resolve(newObjects); - }, + } /** lifecycle hooks @method infinityModelLoaded */ - infinityModelLoaded() {}, + infinityModelLoaded() {} /** lifecycle hooks @@ -275,4 +110,6 @@ export default ArrayProxy.extend(Evented, { @method infinityModelUpdated */ infinityModelUpdated() {} -}); +} + +export default InfinityModel; diff --git a/addon/services/infinity.js b/addon/services/infinity.js index 3d79740f..99689dbf 100644 --- a/addon/services/infinity.js +++ b/addon/services/infinity.js @@ -178,7 +178,7 @@ export default Service.extend({ // this is duplicated if this method is called from the route. set(infinityModel, '_increment', increment); - if (get(infinityModel, 'loadingMore') || !get(infinityModel, '_canLoadMore')) { + if (get(infinityModel, 'loadingMore') || !get(infinityModel, 'canLoadMore')) { return resolve(); } @@ -274,6 +274,12 @@ export default Service.extend({ content: A() }; + for (let key in initParams) { + if (typeof initParams[key] === 'undefined') { + delete initParams[key]; + } + } + const infinityModel = InfinityModelFactory.create(initParams); get(this, '_ensureCompatibility')(get(infinityModel, 'store'), get(infinityModel, 'storeFindMethod')); @@ -342,7 +348,7 @@ export default Service.extend({ } set(infinityModel, '_firstPageLoaded', true); - let canLoadMore = get(infinityModel, '_canLoadMore'); + let canLoadMore = get(infinityModel, 'canLoadMore'); set(infinityModel, 'reachedInfinity', !canLoadMore); if (!canLoadMore) { diff --git a/addon/utils.js b/addon/utils.js index 47217860..f5c9b6f0 100644 --- a/addon/utils.js +++ b/addon/utils.js @@ -1,5 +1,4 @@ import { get } from '@ember/object'; -import { typeOf } from '@ember/utils'; import InfinityModel from 'ember-infinity/lib/infinity-model'; import EmberError from '@ember/error'; @@ -36,12 +35,12 @@ export let objectAssign = Object.assign || function objectAssign(target) { * @return {String} parameter value */ export function paramsCheck(key, options, extendedInfinityModel) { - const paramDefault = get(extendedInfinityModel.proto(), key); + const paramDefault = get(extendedInfinityModel, key); const paramOverride = options[key]; - if (typeOf(paramOverride) === 'null') { + if (paramOverride === null) { // allow user to set to null if passed into infinityRoute explicitly - return; + return null; } else if (paramOverride) { return paramOverride; diff --git a/package-lock.json b/package-lock.json index 747b9ea4..34b042ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1266,9 +1266,9 @@ } }, "@ember/optional-features": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@ember/optional-features/-/optional-features-0.6.4.tgz", - "integrity": "sha512-nKmKxMk+Q/BGE8cmfq8KTHnYHVgrU3GHhy/eZ/OTj/fUvzXZhxaEVFOfAXssiOzV3FOQDJjznpbua2TEtHaQRw==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@ember/optional-features/-/optional-features-0.7.0.tgz", + "integrity": "sha512-qLXvL/Kq/COb43oQmCrKx7Fy8k1XJDI2RlgbCnZHH26AGVgJT/sZugx1A2AIxKdamtl/Mi+rQSjGIuscSjqjDw==", "dev": true, "requires": { "chalk": "^2.3.0", @@ -2129,6 +2129,31 @@ "source-map": "^0.5.7" } }, + "babel-eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", @@ -6207,9 +6232,9 @@ } }, "ember-cli-babel": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/ember-cli-babel/-/ember-cli-babel-7.7.3.tgz", - "integrity": "sha512-/LWwyKIoSlZQ7k52P+6agC7AhcOBqPJ5C2u27qXHVVxKvCtg6ahNuRk/KmfZmV4zkuw4EjTZxfJE1PzpFyHkXg==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/ember-cli-babel/-/ember-cli-babel-7.11.0.tgz", + "integrity": "sha512-ykEsr7XoEPaADCBCJMViycCok1grtBRGvZ1k/atlL/gQYCQ1W4E4OROY/Mm2YBgyLftBv6buH7IZsULyQRZUmg==", "requires": { "@babel/core": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.3.4", @@ -6221,7 +6246,7 @@ "@babel/runtime": "^7.2.0", "amd-name-resolver": "^1.2.1", "babel-plugin-debug-macros": "^0.3.0", - "babel-plugin-ember-modules-api-polyfill": "^2.8.0", + "babel-plugin-ember-modules-api-polyfill": "^2.12.0", "babel-plugin-module-resolver": "^3.1.1", "broccoli-babel-transpiler": "^7.1.2", "broccoli-debug": "^0.6.4", diff --git a/package.json b/package.json index 3a77a4e3..14336a67 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,12 @@ "node": "8.* || >= 10.*" }, "dependencies": { - "ember-cli-babel": "~7.7.2", + "ember-cli-babel": "~7.11.0", "ember-in-viewport": "~3.5.8" }, "devDependencies": { - "@ember/optional-features": "^0.6.3", + "@ember/optional-features": "^0.7.0", + "babel-eslint": "^10.0.3", "broccoli-asset-rev": "^2.7.0", "ember-cli": "~3.12.0", "ember-cli-dependency-checker": "^3.1.0", @@ -43,6 +44,7 @@ "ember-cli-pretender": "^1.0.1", "ember-cli-shims": "^1.2.0", "ember-cli-sri": "^2.1.1", + "ember-cli-template-lint": "^1.0.0-beta.1", "ember-cli-test-loader": "^2.2.0", "ember-cli-uglify": "^2.1.0", "ember-data": "~3.12.0", @@ -55,7 +57,6 @@ "ember-resolver": "^5.0.1", "ember-source": "~3.12.0", "ember-source-channel-url": "^1.1.0", - "ember-cli-template-lint": "^1.0.0-beta.1", "ember-try": "^1.0.0", "eslint-plugin-ember": "^6.2.0", "eslint-plugin-node": "^9.0.1", diff --git a/tests/integration/infinity-loader-test.js b/tests/integration/infinity-loader-test.js index 160c837d..547178e3 100644 --- a/tests/integration/infinity-loader-test.js +++ b/tests/integration/infinity-loader-test.js @@ -19,7 +19,7 @@ module('infinity-loader', function(hooks) { }; this.infinityModel = { name: 'dot', - _canLoadMore: false, + canLoadMore: false, on: () => {}, off: () => {} }; From c30cda52b9b9cddd7efffbf0403b5248a6bc3601 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 15:11:17 -0700 Subject: [PATCH 02/12] avoid computed decorator --- addon/lib/infinity-model.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index 35babdfb..c39f32e3 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -1,11 +1,12 @@ import ArrayProxy from "@ember/array/proxy" import Evented from "../-private/evented" import { DEFAULTS } from "../-private/defaults" -import { computed, get, set, getProperties } from '@ember/object'; +import { get, set } from '@ember/object'; import { objectAssign } from '../utils'; import { resolve } from 'rsvp'; function copyEventedProperties(target, source) { + // similar to Reflect.ownKeys() but ie11 compat for (let key of Object.getOwnPropertyNames(source)) { if (key !== 'constructor' && key !== 'prototype' && key !== 'name') { let desc = Object.getOwnPropertyDescriptor(source, key); @@ -36,13 +37,12 @@ class InfinityModel extends ArrayProxy { @type Boolean @default false */ - @computed('_totalPages', '_count', 'currentPage', '_increment') get canLoadMore() { - if (this._canLoadMore) { + if (typeof this._canLoadMore === 'boolean') { return this._canLoadMore; } - let { _count, _totalPages , currentPage, perPage, _increment } = getProperties(this, '_count', '_totalPages', 'currentPage', 'perPage', '_increment'); + let { _count, _totalPages , currentPage, perPage, _increment } = this; let shouldCheck = _increment === 1 && currentPage !== undefined; if (shouldCheck) { if (_totalPages) { @@ -51,9 +51,9 @@ class InfinityModel extends ArrayProxy { return (currentPage < _count / perPage) ? true : false; } } - if (get(this, 'firstPage') > 1) { + if (this.firstPage > 1) { // load previous page if starting page was not 1. Otherwise ignore this block - return get(this, 'firstPage') > 1 ? true : false; + return this.firstPage > 1 ? true : false; } return false; } @@ -71,7 +71,7 @@ class InfinityModel extends ArrayProxy { */ buildParams(increment) { const pageParams = {}; - let { perPageParam, pageParam } = getProperties(this, 'perPageParam', 'pageParam'); + let { perPageParam, pageParam } = this; if (typeof perPageParam === 'string') { pageParams[perPageParam] = get(this, 'perPage'); } From e9fd55f3da074f6703fc04ee542822330ecd0a39 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 15:24:37 -0700 Subject: [PATCH 03/12] overridable --- addon/lib/infinity-model.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index c39f32e3..c585de27 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -36,6 +36,7 @@ class InfinityModel extends ArrayProxy { @property canLoadMore @type Boolean @default false + @overridable */ get canLoadMore() { if (typeof this._canLoadMore === 'boolean') { From c57f92dfdb41d0047d1122867ce765247184b307 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 15:28:57 -0700 Subject: [PATCH 04/12] fix dummy store --- tests/dummy/app/routes/custom-store.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/dummy/app/routes/custom-store.js b/tests/dummy/app/routes/custom-store.js index eb7d139c..b81d56e9 100644 --- a/tests/dummy/app/routes/custom-store.js +++ b/tests/dummy/app/routes/custom-store.js @@ -7,6 +7,7 @@ export default Route.extend({ infinity: service(), model() { - return get(this, 'infinity').model('custom-model', { store: get(this, 'customStore'), storeFindMethod: 'findAll' }); + this.customStore.push('custom-model', { name: 'Zooloo' }) + return get(this, 'infinity').model('custom-model', { store: this.customStore, storeFindMethod: 'findAll' }); } }); From 731a684b1967ac29a3f0220049262bdeaee9d958 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 15:41:46 -0700 Subject: [PATCH 05/12] try without null defaults --- addon/-private/defaults.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/addon/-private/defaults.js b/addon/-private/defaults.js index 9bd55bc0..004da40a 100644 --- a/addon/-private/defaults.js +++ b/addon/-private/defaults.js @@ -9,14 +9,6 @@ export const DEFAULTS = { */ currentPage: 0, - /** - @private - @property extraParams - @type Object - @default null - */ - extraParams: null, - /** Used as a marker for the page the route starts on @@ -51,15 +43,6 @@ export const DEFAULTS = { */ loadingMore: false, - /** - Arbitrary meta copied over from - the HTTP response, to maintain the - default behavior of ember-data requests - @type objects - @default null - */ - meta: null, - /** @private @property _perPage @@ -75,13 +58,6 @@ export const DEFAULTS = { */ reachedInfinity: false, - /** - @public - @property store - @default null - */ - store: null, - /** Name of the "per page" param in the resource request payload @@ -114,16 +90,6 @@ export const DEFAULTS = { */ countParam: 'meta.count', - /** - The supported findMethod name for - the developers Ember Data version. - Provided here for backwards compat. - @public - @property storeFindMethod - @default null - */ - storeFindMethod: null, - /** @private @property _count From bd0677499d8b8f2f05e34bd0b890db41e482ce83 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 16:06:45 -0700 Subject: [PATCH 06/12] init --- addon/lib/infinity-model.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index c585de27..5988d387 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -22,11 +22,12 @@ function copyEventedProperties(target, source) { @extends Ember.ArrayProxy */ class InfinityModel extends ArrayProxy { - constructor(...args) { - super(...args); + init(...args) { + super.init(...args); + const [params] = args; + objectAssign(this, { ...DEFAULTS }, { ...params }); copyEventedProperties(this, Evented.prototype); - objectAssign(this, { ...DEFAULTS }); } /** From 714a242b49b3a524750611e7963bfbcc14392b66 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 16:17:08 -0700 Subject: [PATCH 07/12] add null values back --- addon/-private/defaults.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/addon/-private/defaults.js b/addon/-private/defaults.js index 004da40a..9bd55bc0 100644 --- a/addon/-private/defaults.js +++ b/addon/-private/defaults.js @@ -9,6 +9,14 @@ export const DEFAULTS = { */ currentPage: 0, + /** + @private + @property extraParams + @type Object + @default null + */ + extraParams: null, + /** Used as a marker for the page the route starts on @@ -43,6 +51,15 @@ export const DEFAULTS = { */ loadingMore: false, + /** + Arbitrary meta copied over from + the HTTP response, to maintain the + default behavior of ember-data requests + @type objects + @default null + */ + meta: null, + /** @private @property _perPage @@ -58,6 +75,13 @@ export const DEFAULTS = { */ reachedInfinity: false, + /** + @public + @property store + @default null + */ + store: null, + /** Name of the "per page" param in the resource request payload @@ -90,6 +114,16 @@ export const DEFAULTS = { */ countParam: 'meta.count', + /** + The supported findMethod name for + the developers Ember Data version. + Provided here for backwards compat. + @public + @property storeFindMethod + @default null + */ + storeFindMethod: null, + /** @private @property _count From ad24553ed2d6613448c9f7f897429727363ac058 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 16:50:07 -0700 Subject: [PATCH 08/12] fix test --- tests/dummy/app/routes/custom-store.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/dummy/app/routes/custom-store.js b/tests/dummy/app/routes/custom-store.js index b81d56e9..eb2267cb 100644 --- a/tests/dummy/app/routes/custom-store.js +++ b/tests/dummy/app/routes/custom-store.js @@ -7,7 +7,6 @@ export default Route.extend({ infinity: service(), model() { - this.customStore.push('custom-model', { name: 'Zooloo' }) return get(this, 'infinity').model('custom-model', { store: this.customStore, storeFindMethod: 'findAll' }); } }); From 190a44d97e2a4b1a05c45b3b6cfe625035d8d7f4 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 17:22:11 -0700 Subject: [PATCH 09/12] do public properties work? --- addon/lib/infinity-model.js | 191 +++++++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 5 deletions(-) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index 5988d387..50728f3d 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -1,6 +1,5 @@ import ArrayProxy from "@ember/array/proxy" import Evented from "../-private/evented" -import { DEFAULTS } from "../-private/defaults" import { get, set } from '@ember/object'; import { objectAssign } from '../utils'; import { resolve } from 'rsvp'; @@ -21,15 +20,199 @@ function copyEventedProperties(target, source) { @module ember-infinity/lib/infinity-model @extends Ember.ArrayProxy */ -class InfinityModel extends ArrayProxy { +export default class InfinityModel extends ArrayProxy { init(...args) { super.init(...args); const [params] = args; - objectAssign(this, { ...DEFAULTS }, { ...params }); + objectAssign(this, { ...params }); copyEventedProperties(this, Evented.prototype); } + /** + Increases or decreases depending on scroll direction + + @private + @property currentPage + @type Integer + @default 0 + */ + currentPage = 0 + + /** + @private + @property extraParams + @type Object + @default null + */ + extraParams = null + + /** + Used as a marker for the page the route starts on + + @private + @property firstPage + @type Integer + @default 0 + */ + firstPage = 0 + + /** + @public + @property isError + @type Boolean + @default false + */ + isError = false + + /** + @public + @property isLoaded + @type Boolean + @default false + */ + isLoaded = false + + /** + @public + @property loadingMore + @type Boolean + @default false + */ + loadingMore = false + + /** + Arbitrary meta copied over from + the HTTP response, to maintain the + default behavior of ember-data requests + @type objects + @default null + */ + meta = null + + /** + @private + @property _perPage + @type Integer + @default 25 + */ + perPage = 25 + + /** + @public + @property reachedInfinity + @default false + */ + reachedInfinity = false + + /** + @public + @property store + @default null + */ + store = null + + /** + Name of the "per page" param in the + resource request payload + @type {String} + @default "per_page" + */ + perPageParam = 'per_page' + + /** + Name of the "page" param in the + resource request payload + @type {String} + @default "page" + */ + pageParam = 'page' + + /** + Path of the "total pages" param in + the HTTP response + @type {String} + @default "meta.total_pages" + */ + totalPagesParam = 'meta.total_pages' + + /** + Path of the "count" param in indicating + number of records from HTTP response + @type {String} + @default "meta.count" + */ + countParam = 'meta.count' + + /** + The supported findMethod name for + the developers Ember Data version. + Provided here for backwards compat. + @public + @property storeFindMethod + @default null + */ + storeFindMethod = null + + /** + @private + @property _count + @type Integer + @default 0 + */ + _count = 0 + + /** + @private + @property _totalPages + @type Integer + @default 0 + */ + _totalPages = 0 + + /** + @private + @property _infinityModelName + @type String + @default null + */ + _infinityModelName = null + + /** + @private + @property _firstPageLoaded + @type Boolean + @default false + */ + _firstPageLoaded = false + + /** + @private + @property _increment + @type Integer + @default 1 + */ + _increment = 1 + + /** + simply used for previous page scrolling abilities and passed from + infinity-loader component and set on infinityModel + @private + @property _scrollable + @type Integer + @default null + */ + _scrollable = null + + /** + determines if can load next page or previous page (if applicable) + + @private + @property _canLoadMore + @type Boolean + */ + _canLoadMore = null + /** determines if can load next page or previous page (if applicable) @@ -113,5 +296,3 @@ class InfinityModel extends ArrayProxy { */ infinityModelUpdated() {} } - -export default InfinityModel; From 8dfb6193b59333948cb5976082a92013bff1392a Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 17:45:37 -0700 Subject: [PATCH 10/12] try setProperties --- addon/lib/infinity-model.js | 189 +----------------------------------- 1 file changed, 3 insertions(+), 186 deletions(-) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index 50728f3d..dfba1d18 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -1,6 +1,7 @@ import ArrayProxy from "@ember/array/proxy" import Evented from "../-private/evented" -import { get, set } from '@ember/object'; +import { DEFAULTS } from "../-private/defaults" +import { get, set, setProperties } from '@ember/object'; import { objectAssign } from '../utils'; import { resolve } from 'rsvp'; @@ -25,194 +26,10 @@ export default class InfinityModel extends ArrayProxy { super.init(...args); const [params] = args; - objectAssign(this, { ...params }); + setProperties(this, { ...DEFAULTS, ...params }); copyEventedProperties(this, Evented.prototype); } - /** - Increases or decreases depending on scroll direction - - @private - @property currentPage - @type Integer - @default 0 - */ - currentPage = 0 - - /** - @private - @property extraParams - @type Object - @default null - */ - extraParams = null - - /** - Used as a marker for the page the route starts on - - @private - @property firstPage - @type Integer - @default 0 - */ - firstPage = 0 - - /** - @public - @property isError - @type Boolean - @default false - */ - isError = false - - /** - @public - @property isLoaded - @type Boolean - @default false - */ - isLoaded = false - - /** - @public - @property loadingMore - @type Boolean - @default false - */ - loadingMore = false - - /** - Arbitrary meta copied over from - the HTTP response, to maintain the - default behavior of ember-data requests - @type objects - @default null - */ - meta = null - - /** - @private - @property _perPage - @type Integer - @default 25 - */ - perPage = 25 - - /** - @public - @property reachedInfinity - @default false - */ - reachedInfinity = false - - /** - @public - @property store - @default null - */ - store = null - - /** - Name of the "per page" param in the - resource request payload - @type {String} - @default "per_page" - */ - perPageParam = 'per_page' - - /** - Name of the "page" param in the - resource request payload - @type {String} - @default "page" - */ - pageParam = 'page' - - /** - Path of the "total pages" param in - the HTTP response - @type {String} - @default "meta.total_pages" - */ - totalPagesParam = 'meta.total_pages' - - /** - Path of the "count" param in indicating - number of records from HTTP response - @type {String} - @default "meta.count" - */ - countParam = 'meta.count' - - /** - The supported findMethod name for - the developers Ember Data version. - Provided here for backwards compat. - @public - @property storeFindMethod - @default null - */ - storeFindMethod = null - - /** - @private - @property _count - @type Integer - @default 0 - */ - _count = 0 - - /** - @private - @property _totalPages - @type Integer - @default 0 - */ - _totalPages = 0 - - /** - @private - @property _infinityModelName - @type String - @default null - */ - _infinityModelName = null - - /** - @private - @property _firstPageLoaded - @type Boolean - @default false - */ - _firstPageLoaded = false - - /** - @private - @property _increment - @type Integer - @default 1 - */ - _increment = 1 - - /** - simply used for previous page scrolling abilities and passed from - infinity-loader component and set on infinityModel - @private - @property _scrollable - @type Integer - @default null - */ - _scrollable = null - - /** - determines if can load next page or previous page (if applicable) - - @private - @property _canLoadMore - @type Boolean - */ - _canLoadMore = null - /** determines if can load next page or previous page (if applicable) From 8a4d89ca94961bf269fe849efd4f93391c09d27a Mon Sep 17 00:00:00 2001 From: snewcomer Date: Sun, 15 Sep 2019 19:51:51 -0700 Subject: [PATCH 11/12] just rely on instance properties --- addon/lib/infinity-model.js | 3 +-- addon/services/infinity.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index dfba1d18..16673020 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -25,8 +25,7 @@ export default class InfinityModel extends ArrayProxy { init(...args) { super.init(...args); - const [params] = args; - setProperties(this, { ...DEFAULTS, ...params }); + setProperties(this, { ...DEFAULTS }); copyEventedProperties(this, Evented.prototype); } diff --git a/addon/services/infinity.js b/addon/services/infinity.js index 99689dbf..8ade38a0 100644 --- a/addon/services/infinity.js +++ b/addon/services/infinity.js @@ -6,7 +6,7 @@ import { getOwner } from '@ember/application'; import { A } from '@ember/array'; import { typeOf } from '@ember/utils'; import { scheduleOnce } from '@ember/runloop'; -import { computed, get, set } from '@ember/object'; +import { computed, get, set, setProperties } from '@ember/object'; import { checkInstanceOf, convertToArray, objectAssign, paramsCheck } from '../utils'; import { assert } from '@ember/debug'; import { resolve } from 'rsvp'; @@ -230,20 +230,20 @@ export default Service.extend({ const store = options.store || get(this, 'store'); const storeFindMethod = options.storeFindMethod || 'query'; - let InfinityModelFactory; + let infinityModel; if (ExtendedInfinityModel) { // if custom InfinityModel, then use as base for creating an instance - InfinityModelFactory = ExtendedInfinityModel; + infinityModel = ExtendedInfinityModel.create(); } else { - InfinityModelFactory = InfinityModel; + infinityModel = InfinityModel.create(); } // check if user passed in param w/ infinityModel, else default - const perPageParam = paramsCheck('perPageParam', options, InfinityModelFactory); - const pageParam = paramsCheck('pageParam', options, InfinityModelFactory); - const totalPagesParam = paramsCheck('totalPagesParam', options, InfinityModelFactory); - const countParam = paramsCheck('countParam', options, InfinityModelFactory); - const infinityCache = paramsCheck('infinityCache', options, InfinityModelFactory); + const perPageParam = paramsCheck('perPageParam', options, infinityModel); + const pageParam = paramsCheck('pageParam', options, infinityModel); + const totalPagesParam = paramsCheck('totalPagesParam', options, infinityModel); + const countParam = paramsCheck('countParam', options, infinityModel); + const infinityCache = paramsCheck('infinityCache', options, infinityModel); // create identifier for use in storing unique cached infinity model let identifier = stringifyObjectValues(options); @@ -280,7 +280,7 @@ export default Service.extend({ } } - const infinityModel = InfinityModelFactory.create(initParams); + setProperties(infinityModel, { ...initParams }); get(this, '_ensureCompatibility')(get(infinityModel, 'store'), get(infinityModel, 'storeFindMethod')); // route specific (for backwards compat) From cc2cd2b0a5eeb354d83f8e562e9e8eb5d1bfe5e1 Mon Sep 17 00:00:00 2001 From: snewcomer Date: Mon, 16 Sep 2019 16:02:48 -0700 Subject: [PATCH 12/12] go with mixin pattern --- addon/-private/evented.js | 25 ++++++++++++++----------- addon/lib/infinity-model.js | 15 ++------------- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/addon/-private/evented.js b/addon/-private/evented.js index 0b7dbf8c..718d87a6 100644 --- a/addon/-private/evented.js +++ b/addon/-private/evented.js @@ -1,18 +1,21 @@ import Notifier from './notifier'; -export default class Evented { - on(eventName, listener) { - return notifierForEvent(this, eventName).addListener(listener); - } +// in lieue of a decorator, lets just use Mixin/composition pattern +export function addEvented(Base) { + return class extends Base { + on(eventName, listener) { + return notifierForEvent(this, eventName).addListener(listener); + } - off(eventName, listener) { - return notifierForEvent(this, eventName).removeListener(listener); - } + off(eventName, listener) { + return notifierForEvent(this, eventName).removeListener(listener); + } - trigger(eventName, ...args) { - const notifier = notifierForEvent(this, eventName); - if (notifier) { - notifier.trigger.apply(notifier, args); + trigger(eventName, ...args) { + const notifier = notifierForEvent(this, eventName); + if (notifier) { + notifier.trigger.apply(notifier, args); + } } } } diff --git a/addon/lib/infinity-model.js b/addon/lib/infinity-model.js index 16673020..4335d733 100644 --- a/addon/lib/infinity-model.js +++ b/addon/lib/infinity-model.js @@ -1,32 +1,21 @@ import ArrayProxy from "@ember/array/proxy" -import Evented from "../-private/evented" +import { addEvented } from "../-private/evented" import { DEFAULTS } from "../-private/defaults" import { get, set, setProperties } from '@ember/object'; import { objectAssign } from '../utils'; import { resolve } from 'rsvp'; -function copyEventedProperties(target, source) { - // similar to Reflect.ownKeys() but ie11 compat - for (let key of Object.getOwnPropertyNames(source)) { - if (key !== 'constructor' && key !== 'prototype' && key !== 'name') { - let desc = Object.getOwnPropertyDescriptor(source, key); - Object.defineProperty(target, key, desc); - } - } -} - /** @class InfinityModel @namespace EmberInfinity @module ember-infinity/lib/infinity-model @extends Ember.ArrayProxy */ -export default class InfinityModel extends ArrayProxy { +export default class InfinityModel extends addEvented(ArrayProxy) { init(...args) { super.init(...args); setProperties(this, { ...DEFAULTS }); - copyEventedProperties(this, Evented.prototype); } /**