Refactor search into its own controller.
authorChris Jaekl <cejaekl@yahoo.com>
Sat, 18 Nov 2017 14:23:46 +0000 (23:23 +0900)
committerChris Jaekl <cejaekl@yahoo.com>
Sat, 18 Nov 2017 14:23:46 +0000 (23:23 +0900)
Listen for [Enter] in search text fields, and trigger search when it is pressed.

app/index.html
app/lib.css
app/lib.js [deleted file]
js/BooksModel.js
js/Gruntfile.js
js/Main.js
js/PagingController.js
js/SearchController.js [new file with mode: 0644]

index 76730a3c968f71a12aaaa43bde3cd409a2761f17..26a0374394ad6c710463cef14e3e5ecd17b39fbf 100644 (file)
@@ -9,9 +9,9 @@
   <body> 
     <form>
       <input id="search" onclick="onSearch();" type="button" value="Search"/> 
-      Author: <input id="aut" type="text"/>
-      Title: <input id="tit" type="text"/>
-      Series: <input id="ser" type="text"/>
+      <span class="term">Author: <input id="aut" type="text"/></span>
+      <span class="term">Title: <input id="tit" type="text"/></span>
+      <span class="term">Series: <input id="ser" type="text"/></span>
     </form>
 
     <div class="pager">
@@ -21,7 +21,7 @@
       Showing <span id="first">0</span> through <span id="last">0</span> out of <span id="count">0</span> matching books.
     </div>
 
-    <div id="books" onmousemove="onMouseMove();">(No books found)</div>
+    <div id="books">(No books found)</div>
 
     <div id="details" class="tooltip" onclick="hideDetails();">(No information available)</div>
 
index 97d68221f72547ae37dc9c3db37047f39de2ab2e..1c4baa0851606597f703ed78681b2efa4d7f46fc 100644 (file)
@@ -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 (file)
index 4a77ffc..0000000
+++ /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 = '<div class="book">'
-        +   '<table>'
-        +     '<tr>'
-        +       '<td><a href="/book/' + book.Id + '">';
-    if (0 == book.CoverId) {
-        result +=          '(No cover available)';
-    } else {
-        result +=          '<img class="cover-thumb" src="/download/' + book.CoverId + '"/>';
-    }
-    result     +=       '</a></td>'
-        +       '<td onclick="displayDetails(' + book.Id + ');" '
-        +          ' onmouseover="startTooltipTimer(' + book.Id + ');">'
-        +         '<p><b>' + book.Title + '</b></p>'
-        +         '<p>'
-        +           '<i>' + book.AuthorReading + '</i>';
-    if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) {
-        result +=          '<br/><i>' + book.SeriesName + ' ' + book.Volume + '</i>';
-    }
-    result     +=         '</p>'
-        +       '</td>'
-        +     '</tr>'
-        +   '</table>'
-        + '</div>';
-    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 = '<div><p><b>' + book.Title + '</b></p>'
-    + '<p><i>' + ce(book.AuthorReading) + '<br/>' + ce(book.Series) + ' ' + ce(book.Volume) + '</i></p></div><div>'
-    + ce(book.Description)
-    + '</div>';
-
-    // 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();
-}
-
index 0c0b915769e0cfcd29501ccceef7740a31ec9eec..984519d137409329027f20ca894beeeb18409ab0 100644 (file)
@@ -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);
     };
index 690a13040974ba25beb7dbc4919b2fb3bb4add8b..c6ebaa933d7f787c77f73ab189d1a86aea7ee7c8 100644 (file)
@@ -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']
         }
       }
     }
index 246044e2f0edae799c91f7de676a60387296b650..6ceac8256665e4fbd49b5aaaf3661ccf918d1d5f 100644 (file)
@@ -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();
 }
 
index d5edea4559f32273740bd6dc64f21b73c6fc50db..c5bfb2d76322374ccd98f82fbce238f2a37e7812 100644 (file)
@@ -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 (file)
index 0000000..5b770b5
--- /dev/null
@@ -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;
+})();