Add super basic and ugly search with openlibrary

This commit is contained in:
Robbie Antenesse 2019-09-04 17:28:01 -06:00
parent 34ae314e74
commit 21f9c41eaa
3 changed files with 112 additions and 0 deletions

View File

@ -1,17 +1,25 @@
import html from 'choo/html'; import html from 'choo/html';
import { homeView } from './home'; import { homeView } from './home';
import { searchView } from './search';
export const viewManager = (state, emit) => { export const viewManager = (state, emit) => {
// In viewManager all we are doing is checking the app's state // In viewManager all we are doing is checking the app's state
// and passing the state and emit to the relevant view. // and passing the state and emit to the relevant view.
let htmlContent = html`<div>loading</div>`; let htmlContent = html`<div>loading</div>`;
if (state.query.hasOwnProperty('search')) {
state.currentView = 'search'; // Override view if there's a search query
}
switch (state.currentView) { switch (state.currentView) {
case 'home': case 'home':
default: { default: {
htmlContent = homeView(state, emit); htmlContent = homeView(state, emit);
break; break;
} }
case 'search': {
htmlContent = searchView(state, emit);
break;
}
} }
// Create a wrapper for view content that includes global header/footer // Create a wrapper for view content that includes global header/footer
@ -29,6 +37,11 @@ export const viewManager = (state, emit) => {
<label for="navMenu" class="burger pseudo button">&#8801;</label> <label for="navMenu" class="burger pseudo button">&#8801;</label>
<div class="menu"> <div class="menu">
<form method="GET" style="display:inline-block;">
<label>
<input type="text" name="search" placeholder="Search">
</label>
</form>
<a href="https://gitlab.com/Alamantus/book-tracker" class="pseudo button">Repo</a> <a href="https://gitlab.com/Alamantus/book-tracker" class="pseudo button">Repo</a>
<a href="https://gitter.im/book-tracker/general" class="pseudo button">Chat</a> <a href="https://gitter.im/book-tracker/general" class="pseudo button">Chat</a>
</div> </div>

View File

@ -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;
}
});
}
}

39
src/views/search/index.js Normal file
View File

@ -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`<section>
<h2 class="subtitle">An attempt at a viable alternative to Goodreads</h2>
<article>
${controller.results.map(result => {
return html`<div class="card">
<header>
${result.covers.map(cover => {
return html`<img src=${cover} />`;
})}
<h1 class="title">${result.title}</h1>
${result.authors.map(author => {
return html`<h2 class="subtitle">${author}</h2>`;
})}
</header>
</div>`;
})}
</article>
</section>`,
];
}