diff --git a/src/views/manager.js b/src/views/manager.js
index c588776..a57fd95 100644
--- a/src/views/manager.js
+++ b/src/views/manager.js
@@ -1,17 +1,25 @@
import html from 'choo/html';
import { homeView } from './home';
+import { searchView } from './search';
export const viewManager = (state, emit) => {
// In viewManager all we are doing is checking the app's state
// and passing the state and emit to the relevant view.
let htmlContent = html`
loading
`;
+ if (state.query.hasOwnProperty('search')) {
+ state.currentView = 'search'; // Override view if there's a search query
+ }
switch (state.currentView) {
case 'home':
default: {
htmlContent = homeView(state, emit);
break;
}
+ case 'search': {
+ htmlContent = searchView(state, emit);
+ break;
+ }
}
// Create a wrapper for view content that includes global header/footer
@@ -29,6 +37,11 @@ export const viewManager = (state, emit) => {
diff --git a/src/views/search/controller.js b/src/views/search/controller.js
new file mode 100644
index 0000000..423aa0b
--- /dev/null
+++ b/src/views/search/controller.js
@@ -0,0 +1,60 @@
+import { ViewController } from '../controller';
+
+export class SearchController extends ViewController {
+ constructor(state) {
+ // Super passes state, view name, and default state to ViewController,
+ // which stores state in this.appState and the view controller's state to this.state
+ super(state, 'search', {
+ done: false,
+ results: [],
+ });
+
+ // If using controller methods in an input's onchange or onclick instance,
+ // either bind the class's 'this' instance to the method first...
+ // or use `onclick=${() => controller.submit()}` to maintain the 'this' of the class instead.
+ }
+
+ get results() {
+ return [...this.state.results];
+ }
+
+ search(term) {
+ return fetch('http://openlibrary.org/search.json?q=' + encodeURIComponent(term))
+ .then(res => res.json())
+ .then(response => {
+ if (response.hasOwnProperty('docs')) {
+ // Format the response into usable objects
+ const docs = response.docs.map(doc => {
+ return {
+ title: doc.title_suggest.trim(),
+ authors: doc.hasOwnProperty('author_name') ? doc.author_name.map(name => name.trim()) : [],
+ cover: doc.hasOwnProperty('cover_i') ? `//covers.openlibrary.org/b/id/${doc.cover_i}-S.jpg` : false,
+ };
+ });
+
+ // Filter out duplicate items with the same title and author
+ const results = docs.filter((doc, index, allDocs) => {
+ return typeof allDocs.find((filterResult, filterIndex) => {
+ return index !== filterIndex && filterResult.title === doc.title
+ && JSON.stringify(filterResult.authors) === JSON.stringify(doc.authors);
+ }) === 'undefined';
+ }).map(result => {
+ // Find any duplicates in case they have different cover data
+ const duplicates = docs.filter(doc => {
+ return doc.title.toLowerCase() === result.title.toLowerCase() && JSON.stringify(doc.authors) === JSON.stringify(result.authors);
+ });
+ result.covers = [];
+ duplicates.forEach(duplicate => {
+ if (duplicate.cover !== false) {
+ result.covers.push(duplicate.cover);
+ }
+ });
+ return result;
+ });
+
+ this.state.results = results;
+ this.state.done = true;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/views/search/index.js b/src/views/search/index.js
new file mode 100644
index 0000000..ba55e2a
--- /dev/null
+++ b/src/views/search/index.js
@@ -0,0 +1,39 @@
+import html from 'choo/html';
+
+// We'll see if code splitting is worth it in the end or if we should combine everything into `src/index.scss`
+import { SearchController } from './controller'; // The controller for this view, where processing should happen.
+
+// This is the view function that is exported and used in the view manager.
+export const searchView = (state, emit) => {
+ const controller = new SearchController(state);
+
+ if (!controller.state.done) {
+ controller.search(state.query.search).then(() => {
+ emit('render');
+ });
+ }
+
+ // Returning an array in a view allows non-shared parent HTML elements.
+ // This one doesn't have the problem right now, but it's good to remember.
+ return [
+ html`
+