Add super basic and ugly search with openlibrary
This commit is contained in:
parent
34ae314e74
commit
21f9c41eaa
|
@ -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">≡</label>
|
<label for="navMenu" class="burger pseudo button">≡</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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>`,
|
||||||
|
];
|
||||||
|
}
|
Loading…
Reference in New Issue