From 32c817b6cf7484487c1160cec8a4b9e770b8404a Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sat, 18 Nov 2017 23:23:46 +0900 Subject: [PATCH] Refactor search into its own controller. Listen for [Enter] in search text fields, and trigger search when it is pressed. --- app/index.html | 8 +- app/lib.css | 4 + app/lib.js | 313 ----------------------------------------- js/BooksModel.js | 29 ++++ js/Gruntfile.js | 2 +- js/Main.js | 52 +------ js/PagingController.js | 26 +--- js/SearchController.js | 87 ++++++++++++ 8 files changed, 134 insertions(+), 387 deletions(-) delete mode 100644 app/lib.js create mode 100644 js/SearchController.js diff --git a/app/index.html b/app/index.html index 76730a3..26a0374 100644 --- a/app/index.html +++ b/app/index.html @@ -9,9 +9,9 @@
- Author: - Title: - Series: + Author: + Title: + Series:
@@ -21,7 +21,7 @@ Showing 0 through 0 out of 0 matching books.
-
(No books found)
+
(No books found)
(No information available)
diff --git a/app/lib.css b/app/lib.css index 97d6822..1c4baa0 100644 --- a/app/lib.css +++ b/app/lib.css @@ -75,6 +75,10 @@ span.popup:hover span.pop-inner { z-index:6; } +span.term { + display: inline-block; +} + div.tooltip { background: #cfffff; border-color: black; diff --git a/app/lib.js b/app/lib.js deleted file mode 100644 index 4a77ffc..0000000 --- a/app/lib.js +++ /dev/null @@ -1,313 +0,0 @@ -//QuanLib: eBook Library -//(C) 2017 by Christian Jaekl (cejaekl@yahoo.com) - -'use strict'; - -var g_state = { - cache: [], - count: 0, - mousePos: { // Last known position of the mouse cursor - x: undefined, - y: undefined - }, - first: 0, - ids: [], - last: (-1), - map: {}, // map from book.Id to index into cache[] - pageSize: 20, - tooltip: { - bookId: undefined, - milliSecs: 500, // time to wait before displaying tooltip - mousePos: { - x: undefined, - y: undefined - }, - threshold: 10, // number of pixels that mouse can move before tip is dismissed - timer: undefined - } -}; - -document.onmousemove = onMouseMove; - -function adjustPos(setting) { - var value = parseInt(setting); - - if (g_state.first === value) { - // No change - return; - } - - var maxFirst = Math.max(0, g_state.count - g_state.pageSize); - - if (value < 0) { - g_state.first = 0; - } else if (value > maxFirst) { - g_state.first = maxFirst; - } else { - g_state.first = value; - } - - g_state.last = g_state.first + g_state.pageSize - 1; - if (g_state.last >= g_state.count) { - g_state.last = g_state.count - 1; - } - - document.getElementById('slider').value = setting; - - refreshData(); -} - -function bookHtml(book) { - var result = '
' - + '' - + '' - + '' - + '' - + '' - + '
'; - if (0 == book.CoverId) { - result += '(No cover available)'; - } else { - result += ''; - } - result += '' - + '

' + book.Title + '

' - + '

' - + '' + book.AuthorReading + ''; - if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) { - result += '
' + book.SeriesName + ' ' + book.Volume + ''; - } - result += '

' - + '
' - + '
'; - return result; -} - -//ce(s): "clear if empty()" -//return s, unless it's undefined, in which case return an empty ("clear") string -function ce(s) { - if (typeof s !== 'undefined') { - return s; - } - return ''; -} - -function constructSearchUrl() { - var url = window.location.protocol + '//' + window.location.host + '/search/'; - - var firstTime = true; - var terms = ['aut', 'tit', 'ser']; - - for (var idx in terms) { - var term = terms[idx]; - var elem = document.getElementById(term); - if (null === elem) { - console.log('Error: could not find form element for search term "' + term + '".'); - continue; - } - - var value = elem.value; - if (value.length > 0) { - if (firstTime) { - url += '?'; - firstTime = false; - } - else { - url += '&'; - } - url += term + '=' + encodeURIComponent('%' + value + '%'); - } - } - - return url; -} - -// Set the book ID for the details pane, and then show it -function displayDetails(bookId) { - g_state.tooltip.bookId = bookId; - showDetails(); -} - -function hideDetails() { - g_state.tooltip.mousePos.x = undefined; - g_state.tooltip.mousePos.y = undefined; - - var elem = document.getElementById('details'); - elem.innerHTML = ''; - elem.style.display = 'none'; -} - -function onMouseMove(event) { - if (typeof event === 'undefined') { - return; - } - - var x = event.pageX; - var y = event.pageY; - - if ( x === g_state.mousePos.x - && y === g_state.mousePos.y) - { - // No change from previous known position. - // Nothing to see (or do) here, move along. - return; - } - - // Remember current mouse (x,y) position - g_state.mousePos.x = x; - g_state.mousePos.y = y; - - // Is there an active tooltip? - if (typeof g_state.tooltip.mousePos.x === 'undefined') { - // No active tooltip, so nothing further to do - return; - } - - var deltaX = Math.abs(x - g_state.tooltip.mousePos.x); - var deltaY = Math.abs(y - g_state.tooltip.mousePos.y); - - if ( deltaX > g_state.tooltip.threshold - || deltaY > g_state.tooltip.threshold ) - { - hideDetails(); - } -} - -function onNext() { - if (g_state.last < (g_state.count - 1)) { - adjustPos(g_state.first + g_state.pageSize); - } -} - -function onPrev() { - if (g_state.first > 0) { - adjustPos(g_state.first - g_state.pageSize); - } -} - -function onSlide(value) { - adjustPos(value); -} - -function onSearch() { - var url = constructSearchUrl(); - - fetch(url, {method:'GET', cache:'default'}) - .then(response => response.json()) - .then((jsonValue) => { - // console.log('JSON response: ', jsonValue); - g_state.ids = jsonValue; - g_state.count = g_state.ids.length; - g_state.first = (-1); - - var elem = document.getElementById('slider'); - elem.max = g_state.count; - - adjustPos(0); - }) - .catch(err => { - var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack; - console.log(msg); - report(msg); - }); -} - -function refreshData() { - var i; - var url = '/info/?ids='; - g_state.map = {}; - for (i = g_state.first; i <= g_state.last; ++i) { - if (i > g_state.first) { - url += ','; - } - var id = g_state.ids[i]; - url += id; - g_state.map[id] = i - g_state.first; - } - - fetch(url, {method:'GET', cache:'default'}) - .then(response => response.json()) - .then((jsonValue) => { - console.log('JSON response for info: ', jsonValue); - g_state.cache = jsonValue; - refreshLayout(); - }) - .catch(err => { - var msg = 'Error fetching book details via URL: ' + url + ': ' + err; - console.log(msg, err.stack); - report(msg); - }); -} - -function refreshLayout() { - var i; - var html = ''; - var limit = g_state.last - g_state.first; - for (i = 0; i <= limit; ++i) { - var book = g_state.cache[i]; - html += bookHtml(book); - } - - document.getElementById('books').innerHTML = html; - document.getElementById('first').innerHTML = (g_state.first + 1); - document.getElementById('last').innerHTML = (g_state.last + 1); - document.getElementById('count').innerHTML = g_state.count; -} - -function report(message) { - document.getElementById('books').innerHTML = message; -} - -function showDetails() { - var id = g_state.tooltip.bookId; - var elem = document.getElementById('details'); - var index = g_state.map[id]; - var book = g_state.cache[index]; - var html = '

' + book.Title + '

' - + '

' + ce(book.AuthorReading) + '
' + ce(book.Series) + ' ' + ce(book.Volume) + '

' - + ce(book.Description) - + '
'; - - // Remember the current mouse (x,y). - // If we move the mouse too far from this point, that will trigger hiding the tooltip. - g_state.tooltip.mousePos.x = g_state.mousePos.x; - g_state.tooltip.mousePos.y = g_state.mousePos.y; - - elem.innerHTML = html; - - elem.style.display = 'block'; // show, and calculate size, so that we can query it below - - var x = g_state.mousePos.x; - var y = g_state.mousePos.y; - - var bcr = elem.getBoundingClientRect(); - - var width = bcr.width; - var height = bcr.height; - - x = Math.max(x - (width / 2), 0); - y = Math.max(y - (height / 2), 0); - - elem.style.left = x + 'px'; - elem.style.top = y + 'px'; -} - -function startTooltipTimer(bookId) { - if (typeof g_state.tooltip.timer !== 'undefined') { - clearTimeout(g_state.tooltip.timer); - } - g_state.tooltip.bookId = bookId; - g_state.tooltip.timer = setTimeout(showDetails, g_state.tooltip.milliSecs); -} - -function stopTooltipTimer() { - if (typeof g_state.tooltip.timer === 'undefined') { - return; - } - - clearTimeout(g_state.tooltip.timer); - g_state.tooltip.timer = undefined; - hideDetails(); -} - diff --git a/js/BooksModel.js b/js/BooksModel.js index 0c0b915..984519d 100644 --- a/js/BooksModel.js +++ b/js/BooksModel.js @@ -24,6 +24,35 @@ var BooksModel = (function() { // ============== // Public methods + my.adjustPos = function(setting) { + + var value = parseInt(setting); + + var prev = { + first: my.first, + last: my.last, + }; + + var maxFirst = Math.max(0, my.count - my.pageSize); + + if (value < 0) { + my.first = 0; + } else if (value > maxFirst) { + my.first = maxFirst; + } else { + my.first = value; + } + + my.last = my.first + my.pageSize - 1; + if (my.last >= my.count) { + my.last = my.count - 1; + } + + if (prev.first !== my.first || prev.last !== my.last) { + my.refreshData(); + } + }; + my.listen = function(subscriber) { listeners.push(subscriber); }; diff --git a/js/Gruntfile.js b/js/Gruntfile.js index 690a130..c6ebaa9 100644 --- a/js/Gruntfile.js +++ b/js/Gruntfile.js @@ -10,7 +10,7 @@ module.exports = function(grunt) { }, app: { files: { - '../app/lib.min.js': ['BooksModel.js', 'BooksView.js', 'PagingController.js', 'ToolTip.js', 'Main.js'] + '../app/lib.min.js': ['BooksModel.js', 'BooksView.js', 'PagingController.js', 'SearchController.js', 'ToolTip.js', 'Main.js'] } } } diff --git a/js/Main.js b/js/Main.js index 246044e..6ceac82 100644 --- a/js/Main.js +++ b/js/Main.js @@ -18,6 +18,7 @@ document.onmousemove = onMouseMove; BooksView.init(BooksModel); PagingController.init(BooksModel); +SearchController.init(BooksModel); // ================ // Global functions @@ -28,36 +29,6 @@ function report(message) { document.getElementById('books').innerHTML = message; } -function constructSearchUrl() { - var url = window.location.protocol + '//' + window.location.host + '/search/'; - - var firstTime = true; - var terms = ['aut', 'tit', 'ser']; - - for (var idx in terms) { - var term = terms[idx]; - var elem = document.getElementById(term); - if (null === elem) { - console.log('Error: could not find form element for search term "' + term + '".'); - continue; - } - - var value = elem.value; - if (value.length > 0) { - if (firstTime) { - url += '?'; - firstTime = false; - } - else { - url += '&'; - } - url += term + '=' + encodeURIComponent('%' + value + '%'); - } - } - - return url; -} - function onMouseMove(event) { if (typeof event === 'undefined') { return; @@ -98,25 +69,6 @@ function onSlide(value) { } function onSearch() { - var url = constructSearchUrl(); - - fetch(url, {method:'GET', cache:'default'}) - .then(response => response.json()) - .then((jsonValue) => { - // console.log('JSON response: ', jsonValue); - BooksModel.ids = jsonValue; - BooksModel.count = BooksModel.ids.length; - BooksModel.first = (-1); - - var elem = document.getElementById('slider'); - elem.max = BooksModel.count; - - PagingController.adjustPos(0); - }) - .catch(err => { - var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack; - console.log(msg); - report(msg); - }); + SearchController.onSearch(); } diff --git a/js/PagingController.js b/js/PagingController.js index d5edea4..c5bfb2d 100644 --- a/js/PagingController.js +++ b/js/PagingController.js @@ -11,34 +11,22 @@ var PagingController = (function() { my.init = function(linkedBooksModel) { booksModel = linkedBooksModel; + booksModel.listen(my); }; - my.adjustPos = function (setting) { + my.adjustPos = function(setting) { var value = parseInt(setting); if (booksModel.first === value) { // No change return; } + + booksModel.adjustPos(setting); + }; - var maxFirst = Math.max(0, booksModel.count - booksModel.pageSize); - - if (value < 0) { - booksModel.first = 0; - } else if (value > maxFirst) { - booksModel.first = maxFirst; - } else { - booksModel.first = value; - } - - booksModel.last = booksModel.first + booksModel.pageSize - 1; - if (booksModel.last >= booksModel.count) { - booksModel.last = booksModel.count - 1; - } - - document.getElementById('slider').value = setting; - - booksModel.refreshData(); + my.notify = function() { + document.getElementById('slider').value = booksModel.first; }; return my; diff --git a/js/SearchController.js b/js/SearchController.js new file mode 100644 index 0000000..5b770b5 --- /dev/null +++ b/js/SearchController.js @@ -0,0 +1,87 @@ +// ================ +// SearchController + +var SearchController = (function () { + var my = {}, + booksModel = undefined; + + const terms = ['aut', 'tit', 'ser']; + + // ============== + // Public methods + + my.init = function(linkedBooksModel) { + booksModel = linkedBooksModel; + + for (var idx in terms) { + addEnterListener(terms[idx]); + } + }; + + my.onSearch = function() { + var url = constructSearchUrl(); + + fetch(url, {method:'GET', cache:'default'}) + .then(response => response.json()) + .then((jsonValue) => { + // console.log('JSON response: ', jsonValue); + booksModel.ids = jsonValue; + booksModel.count = booksModel.ids.length; + booksModel.first = (-1); + + var elem = document.getElementById('slider'); + elem.max = booksModel.count; + + PagingController.adjustPos(0); + }) + .catch(err => { + var msg = 'Error fetching JSON from URL: ' + url + ': ' + err + ':' + err.stack; + console.log(msg); + report(msg); + }); + }; + + // =============== + // Private methods + + // KeyUp listener. If the key is [Enter], then trigger a click on the [Search] button. + function addEnterListener(ctrlId) { + document.getElementById(ctrlId).addEventListener('keyup', function(event) { + event.preventDefault(); + if (event.keyCode === 13) { + document.getElementById('search').click(); + } + }); + } + + function constructSearchUrl() { + var url = window.location.protocol + '//' + window.location.host + '/search/'; + + var firstTime = true; + + for (var idx in terms) { + var term = terms[idx]; + var elem = document.getElementById(term); + if (null === elem) { + console.log('Error: could not find form element for search term "' + term + '".'); + continue; + } + + var value = elem.value; + if (value.length > 0) { + if (firstTime) { + url += '?'; + firstTime = false; + } + else { + url += '&'; + } + url += term + '=' + encodeURIComponent('%' + value + '%'); + } + } + + return url; + } + + return my; +})(); -- 2.30.2