diff --git a/app/components/img-fancy-load.js b/app/components/img-fancy-load.js
index dc7e9e5..62c7460 100644
--- a/app/components/img-fancy-load.js
+++ b/app/components/img-fancy-load.js
@@ -3,7 +3,7 @@ import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object'
export default class SearchBooksComponent extends Component {
- placeholderSrc = 'http://placehold.jp/d1d1d1/707070/400x300.png?text=No%20Image';
+ placeholderSrc = 'placeholder-img.png';
@tracked showPlaceholder = true;
@action
@@ -12,8 +12,8 @@ export default class SearchBooksComponent extends Component {
}
@action
- error() {
- this.src = this.placeholderSrc;
+ error(event) {
+ event.target.src = this.placeholderSrc;
this.showPlaceholder = false;
}
}
diff --git a/app/components/list-books.hbs b/app/components/list-books.hbs
index a8c6ca7..9a884f5 100644
--- a/app/components/list-books.hbs
+++ b/app/components/list-books.hbs
@@ -4,4 +4,10 @@
There are no books to be displayed. Try looking on {{#link-to "books" class="alert-link"}}homepage{{/link-to}} for more awesome books!
-{{/each}}
\ No newline at end of file
+{{/each}}
+
+{{#if @model }}
+
+
+
+{{/if}}
\ No newline at end of file
diff --git a/app/models/book.js b/app/models/book.js
index 16f69a8..6d3e619 100644
--- a/app/models/book.js
+++ b/app/models/book.js
@@ -9,6 +9,6 @@ export default class BookModel extends Model {
get image() {
const firstImage = this.photos.get('firstObject');
- return firstImage ? firstImage : { uri: 'http://placehold.jp/d1d1d1/707070/400x300.png?text=No%20Image', title: 'No Image' };
+ return firstImage ? firstImage : { uri: 'placeholder-img.png', title: 'No Image' };
}
}
diff --git a/app/routes/books.js b/app/routes/books.js
index bf06fc8..29e9232 100644
--- a/app/routes/books.js
+++ b/app/routes/books.js
@@ -4,13 +4,16 @@ import ENV from 'bigriver-bookstore/config/environment'
export default class BooksRoute extends Route {
@service store;
+ @service infinity;
model() {
- return this.store.query('book', {
- page: {
- size: ENV.BOOKS_PER_PAGE
- },
- include: 'author,photos'
+ return this.infinity.model('book', {
+ include: 'author,photos',
+ perPage: ENV.BOOKS_PER_PAGE,
+ startingPage: 1,
+ perPageParam: 'page[size]',
+ pageParam: 'page[number]',
+ countParam: 'meta.total_resources'
});
}
}
diff --git a/app/routes/search.js b/app/routes/search.js
index e6a4b80..3a95201 100644
--- a/app/routes/search.js
+++ b/app/routes/search.js
@@ -4,6 +4,7 @@ import ENV from 'bigriver-bookstore/config/environment'
export default class SearchRoute extends Route {
@service store;
+ @service infinity;
queryParams = {
query: {
@@ -14,14 +15,16 @@ export default class SearchRoute extends Route {
model(params) {
const query = params.query ? params.query : '';
- return this.store.query('book', {
- page: {
- size: ENV.BOOKS_PER_PAGE,
- },
+ return this.infinity.model('book', {
filter: {
'author.name': query
},
- include: 'author,photos'
+ include: 'author,photos',
+ perPage: ENV.BOOKS_PER_PAGE,
+ startingPage: 1,
+ perPageParam: 'page[size]',
+ pageParam: 'page[number]',
+ countParam: 'meta.total_resources'
});
}
}
diff --git a/app/styles/app.css b/app/styles/app.css
index 02dfca7..d2a0c6f 100644
--- a/app/styles/app.css
+++ b/app/styles/app.css
@@ -27,7 +27,7 @@ h1 {
bottom: 0;
left: 0;
background-color: #ccc;
- background-image: url('http://placehold.it/400x300?text=Loading Image');
+ background-image: url('/loading-img.png');
background-repeat: no-repeat;
background-size: 100%;
}
@@ -39,4 +39,17 @@ h1 {
footer .badge {
font-size: 0.9rem;
+}
+
+.infinity-loader {
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.infinity-loader img {
+ width: 100px;
+}
+
+.infinity-loader.reached-infinity img {
+ display: none;
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 28c8ca3..192d90e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1486,6 +1486,16 @@
}
}
},
+ "@ember/render-modifiers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@ember/render-modifiers/-/render-modifiers-1.0.2.tgz",
+ "integrity": "sha512-6tEnHl5+62NTSAG2mwhGMFPhUrJQjoVqV+slsn+rlTknm2Zik+iwxBQEbwaiQOU1FUYxkS8RWcieovRNMR8inQ==",
+ "dev": true,
+ "requires": {
+ "ember-cli-babel": "^7.10.0",
+ "ember-modifier-manager-polyfill": "^1.1.0"
+ }
+ },
"@ember/test-helpers": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@ember/test-helpers/-/test-helpers-1.7.1.tgz",
@@ -8382,6 +8392,93 @@
"focus-trap": "^5.0.1"
}
},
+ "ember-in-viewport": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/ember-in-viewport/-/ember-in-viewport-3.7.2.tgz",
+ "integrity": "sha512-Jv664t0cPiAMv6SOuQS+JmMfFeLnhGlhK06y5Hytolauwp8XJWDoq44wqQ7+oyCJEhf5bL+sam5tENep6euA9Q==",
+ "dev": true,
+ "requires": {
+ "ember-auto-import": "^1.5.2",
+ "ember-cli-babel": "^7.7.3",
+ "ember-modifier": "^1.0.2",
+ "fast-deep-equal": "^2.0.1",
+ "intersection-observer-admin": "~0.2.12",
+ "raf-pool": "~0.1.4"
+ },
+ "dependencies": {
+ "fast-deep-equal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+ "dev": true
+ }
+ }
+ },
+ "ember-infinity": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ember-infinity/-/ember-infinity-2.1.1.tgz",
+ "integrity": "sha512-tJrSYqcZCT0quLeu+IoXy46TAMaFtTRPF0jgHgKfa9xP4NvHDqENwdLpMewlkS4vbs3SCkxo4yBtQw5JG9GuEw==",
+ "dev": true,
+ "requires": {
+ "@ember/render-modifiers": "^1.0.2",
+ "ember-cli-babel": "~7.11.0",
+ "ember-cli-htmlbars": "^3.0.1",
+ "ember-in-viewport": "~3.7.2"
+ },
+ "dependencies": {
+ "ember-cli-babel": {
+ "version": "7.11.1",
+ "resolved": "https://registry.npmjs.org/ember-cli-babel/-/ember-cli-babel-7.11.1.tgz",
+ "integrity": "sha512-Qgd7y9NVbRLEtwjBW/vPHXdTQrIgfgoCSFHfvBpEmLuWSWNpE/J6qwXrSbB9nEIlfzyjH0Almv4m0jwuJsB3ow==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.0.0",
+ "@babel/plugin-proposal-class-properties": "^7.3.4",
+ "@babel/plugin-proposal-decorators": "^7.3.0",
+ "@babel/plugin-transform-modules-amd": "^7.0.0",
+ "@babel/plugin-transform-runtime": "^7.2.0",
+ "@babel/polyfill": "^7.0.0",
+ "@babel/preset-env": "^7.0.0",
+ "@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.12.0",
+ "babel-plugin-module-resolver": "^3.1.1",
+ "broccoli-babel-transpiler": "^7.3.0",
+ "broccoli-debug": "^0.6.4",
+ "broccoli-funnel": "^2.0.1",
+ "broccoli-source": "^1.1.0",
+ "clone": "^2.1.2",
+ "ember-cli-babel-plugin-helpers": "^1.1.0",
+ "ember-cli-version-checker": "^2.1.2",
+ "ensure-posix-path": "^1.0.2",
+ "semver": "^5.5.0"
+ }
+ },
+ "ember-cli-htmlbars": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/ember-cli-htmlbars/-/ember-cli-htmlbars-3.1.0.tgz",
+ "integrity": "sha512-cgvRJM73IT0aePUG7oQ/afB7vSRBV3N0wu9BrWhHX2zkR7A7cUBI7KC9VPk6tbctCXoM7BRGsCC4aIjF7yrfXA==",
+ "dev": true,
+ "requires": {
+ "broccoli-persistent-filter": "^2.3.1",
+ "hash-for-dep": "^1.5.1",
+ "json-stable-stringify": "^1.0.1",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "ember-cli-version-checker": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz",
+ "integrity": "sha512-G+KtYIVlSOWGcNaTFHk76xR4GdzDLzAS4uxZUKdASuFX0KJE43C6DaqL+y3VTpUFLI2FIkAS6HZ4I1YBi+S3hg==",
+ "dev": true,
+ "requires": {
+ "resolve": "^1.3.3",
+ "semver": "^5.3.0"
+ }
+ }
+ }
+ },
"ember-inflector": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ember-inflector/-/ember-inflector-3.0.1.tgz",
@@ -9099,6 +9196,19 @@
"ember-cli-babel": "^7.1.0"
}
},
+ "ember-modifier": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/ember-modifier/-/ember-modifier-1.0.3.tgz",
+ "integrity": "sha512-vWuFyvdkULUyasvEXxe5lcfuPZV/Uqe+b0IQ1yU+TY1RSJnFdVUu/CVHT8Bu4HUJInqzAihwPMTwty7fypzi5Q==",
+ "dev": true,
+ "requires": {
+ "ember-cli-babel": "^7.11.1",
+ "ember-cli-is-package-missing": "^1.0.0",
+ "ember-cli-normalize-entity-name": "^1.0.0",
+ "ember-cli-string-utils": "^1.1.0",
+ "ember-modifier-manager-polyfill": "^1.2.0"
+ }
+ },
"ember-modifier-manager-polyfill": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ember-modifier-manager-polyfill/-/ember-modifier-manager-polyfill-1.2.0.tgz",
@@ -13138,6 +13248,12 @@
"through": "^2.3.6"
}
},
+ "intersection-observer-admin": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/intersection-observer-admin/-/intersection-observer-admin-0.2.12.tgz",
+ "integrity": "sha512-97+A29MV0kp6Xzkb4CxBNxxDU4qldyVFNzvZIiLMYqIZFutT2DJCzE1TEv0hXdmFQfAIY4KhNhL6L1BEEU0J8w==",
+ "dev": true
+ },
"into-stream": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
@@ -15693,6 +15809,12 @@
"broccoli-merge-trees": "^3.0.1"
}
},
+ "raf-pool": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/raf-pool/-/raf-pool-0.1.4.tgz",
+ "integrity": "sha512-BBPamTVuSprPq7CUmgxc+ycbsYUtUYnQtJYEfMHXMaostPaNpQzipLfSa/rwjmlgjBPiD7G+I+8W340sLOPu6g==",
+ "dev": true
+ },
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
diff --git a/package.json b/package.json
index 9b11e62..81c0b26 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"ember-cli-uglify": "^3.0.0",
"ember-data": "~3.14.0",
"ember-export-application-global": "^2.0.0",
+ "ember-infinity": "^2.1.1",
"ember-load-initializers": "^2.0.0",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-qunit": "^4.4.1",
diff --git a/public/loading-img.png b/public/loading-img.png
new file mode 100644
index 0000000..3510d3d
Binary files /dev/null and b/public/loading-img.png differ
diff --git a/public/placeholder-img.png b/public/placeholder-img.png
new file mode 100644
index 0000000..31d6b64
Binary files /dev/null and b/public/placeholder-img.png differ
diff --git a/public/spinner.gif b/public/spinner.gif
new file mode 100644
index 0000000..cfec910
Binary files /dev/null and b/public/spinner.gif differ
diff --git a/tests/acceptance/books-test.js b/tests/acceptance/books-test.js
index 96912d1..e0e553b 100644
--- a/tests/acceptance/books-test.js
+++ b/tests/acceptance/books-test.js
@@ -1,5 +1,5 @@
import { module, test } from 'qunit';
-import { visit, currentURL } from '@ember/test-helpers';
+import { visit, currentURL, waitUntil } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import page from 'bigriver-bookstore/tests/pages/books';
@@ -25,4 +25,18 @@ module('Acceptance | books', function(hooks) {
assert.ok(page.books(0).hasAuthor);
assert.ok(page.books(0).hasImage);
});
+
+ test('fetch more data when scrolled down', async function(assert) {
+ await page.visit();
+
+ assert.equal(page.books().count, 9);
+
+ document.querySelector(page.loaderClass).scrollIntoView();
+
+ await waitUntil(() => {
+ return page.books().count === 18;
+ });
+
+ assert.equal(page.books().count, 18);
+ });
});
diff --git a/tests/pages/books.js b/tests/pages/books.js
index 9a1ec9d..7597c1a 100644
--- a/tests/pages/books.js
+++ b/tests/pages/books.js
@@ -19,4 +19,5 @@ export default create({
searchDisabledPresent: isPresent('[data-test="search-btn"]:disabled'),
fillInSearchInput: fillable('input', { scope: '[data-test="search-by-author-form"]' }),
fancyPlaceholder: isPresent('[data-test="fancy-placeholder"]'),
+ loaderClass: '.infinity-loader',
});
diff --git a/tests/unit/models/book-test.js b/tests/unit/models/book-test.js
index 576b224..32005ba 100644
--- a/tests/unit/models/book-test.js
+++ b/tests/unit/models/book-test.js
@@ -23,9 +23,9 @@ module('Unit | Model | book', function(hooks) {
test('when no image provided return the placeholder', function (assert) {
const store = this.owner.lookup('service:store');
- const placeholder = { uri: 'http://placehold.jp/d1d1d1/707070/400x300.png?text=No%20Image', title: 'No Image' };
+ const placeholder = { uri: 'placeholder-img.png', title: 'No Image' };
const model = store.createRecord('book', { title: 'Book #1' });
- assert.equal(model.image.uri, 'http://placehold.jp/d1d1d1/707070/400x300.png?text=No%20Image');
+ assert.equal(model.image.uri, 'placeholder-img.png');
});
});