Add timeout before displaying tooltip with book details.
authorChris Jaekl <cejaekl@yahoo.com>
Fri, 10 Nov 2017 15:39:18 +0000 (00:39 +0900)
committerChris Jaekl <cejaekl@yahoo.com>
Fri, 10 Nov 2017 15:39:18 +0000 (00:39 +0900)
app/index.html
app/lib.css
app/lib.js

index 9b3331144f8d0159930c05858b51def93ad1da12..8bd44c43c69f15ed8d14009d0bb63ab1626bcb27 100644 (file)
@@ -3,6 +3,7 @@
     <title>eBook Library</title>
     <link href="lib.css" rel="stylesheet" type="text/css"/>
     <meta charset="utf-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
   </head>
   
   <body> 
     <div class="pager">
       <input id="back" onclick="onPrev();" value="Back" type="button"/>
       <input id="forward" onclick="onNext();" value="Forward" type="button"/>
-      <input id="slider" onchange="onSlide(this.value);" min="1" max="1" type="range" value="0"/>
+      <input id="slider" onchange="onSlide(this.value);" min="0" max="0" type="range" value="0"/>
       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">(No books found)</div>
 
+    <div id="details" class="tooltip" onclick="hideDetails();">(No information available)</div>
+
     <script src="lib.js"></script>
   </body>
 </html>
index 0845b0d0411c41f5bbb379b15001662ff3bbe0a7..843adedd9643eafbbc48a5d8773a5e37d67e1900 100644 (file)
@@ -75,6 +75,21 @@ span.popup:hover span.pop-inner {
   z-index:6;
 }
 
+div.tooltip {
+  background: #cfffff;
+  border-color: black;
+  border-style: solid;
+  border-width: 1px;
+  display: none;
+  margin: 4px 0px 0px 0px;
+  padding: 3px 3px 3px 3px;
+  position: fixed;
+  left: 50;
+  text-decoration: none;
+  top: 50;
+  z-index: 6;
+}
+
 table.header {
   width: 100%;
   line-height: 1.8;
index 50086d0db6c12fda13f253e31f41edbf7489a164..d59c350cb45264b8b1be9efb780d0160eacc5960 100644 (file)
-// QuanLib:  eBook Library
-// (C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
+//QuanLib:  eBook Library
+//(C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
 
 g_state = {
-  cache: {},
-  count: 0,
-  first: 0,
-  ids: [],
-  last: (-1),
-  pageSize: 9
+               cache: [],
+               count: 0,
+               first: 0,
+               ids: [],
+               last: (-1),
+               map: {},        // map from book.Id to index into cache[]
+               pageSize: 9,
+               tooltip: {
+                       bookId: undefined,
+                       milliSecs: 500,     // time to wait before displaying tooltip
+                       timer: undefined
+               }
 }
 
 function adjustPos(setting) {
-  var value = parseInt(setting)
-  
-  if (g_state.first === value) {
-    // No change
-    return;
-  }
+       var value = parseInt(setting)
 
-  var maxFirst = g_state.count - g_state.pageSize;
+       if (g_state.first === value) {
+               // No change
+               return;
+       }
 
-  if (value < 0) {
-    g_state.first = 0;
-  } else if (value > maxFirst) {
-    g_state.first = maxFirst;
-  } else {
-    g_state.first = value;
-  }
+       var maxFirst = g_state.count - g_state.pageSize;
 
-  g_state.last = g_state.first + g_state.pageSize - 1;
-  if (g_state.last >= g_state.count) {
-    g_state.last = g_state.count - 1;
-  }
+       if (value < 0) {
+               g_state.first = 0;
+       } else if (value > maxFirst) {
+               g_state.first = maxFirst;
+       } else {
+               g_state.first = value;
+       }
 
-  refreshData();
+       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 + ');" '
+               +          ' onmouseout="stopTooltipTimer(); hideDetails();" '
+               +          ' 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 (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;
+       var url = window.location.protocol + '//' + window.location.host + '/search/';
+
+       var firstTime = true;
+       var terms = ['aut', 'tit', 'ser'];
+
+       for (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) {
+       console.log('displayDetails()', bookId);
+       g_state.tooltip.bookId = bookId;
+       showDetails();
+}
+
+function hideDetails() {
+       var elem = document.getElementById('details');
+       elem.innerHtml = '';
+       elem.style.display = 'none';
 }
 
 function onNext() {
-  if (g_state.last < (g_state.count - 1)) {
-    adjustPos(g_state.first + g_state.pageSize);
-  }
+       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);
-  }
+       if (g_state.first > 0) {
+               adjustPos(g_state.first - g_state.pageSize);
+       }
 }
 
 function onSlide(value) {
-  adjustPos(value);
+       adjustPos(value);
 }
 
 function onSearch() {
-  console.log('onSearch()');
-
-  var url = constructSearchUrl();
-
-  report('Loading data from server, please wait...')
-  console.log('Fetching:  "' + url + '"...')
-
-  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;
-    elem.value = '0'
-
-    adjustPos(0);
-  })
-  .catch(err => { 
-    var msg = 'Error fetching JSON from URL:  ' + url + ': ' + err + ':' + err.stack;
-    console.log(msg);
-    report(msg);
-  });
+       console.log('onSearch()');
+
+       var url = constructSearchUrl();
+
+       report('Loading data from server, please wait...')
+       console.log('Fetching:  "' + url + '"...')
+
+       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() {
-  report('Loading details for books ' + g_state.first + ' through ' + g_state.last + ', please wait...');
-
-  var i;
-  var url = '/info/?ids=';
-  for (i = g_state.first; i <= g_state.last; ++i) {
-    if (i > g_state.first) {
-      url += ',';
-    }
-    url += g_state.ids[i];
-  }
-
-  fetch(url, {method:'GET', cache:'default'})
-  .then(response => response.json())
-  .then((jsonValue) => {
-    console.log('JSON response for info:  ', jsonValue);
-    report('');
-    g_state.cache = jsonValue;
-    refreshLayout();
-  })
-  .catch(err => {
-    var msg = 'Error fetching book details via URL:  ' + url + ': ' + err;
-    console.log(msg, err.stack);
-    report(msg);
-  });
+       report('Loading details for books ' + g_state.first + ' through ' + g_state.last + ', please wait...');
+
+       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);
+               report('');
+               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;
+       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;
+       document.getElementById('books').innerHTML = message;
 }
 
-function bookHtml(book) {
-  console.log('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>'
-             +         '<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;
+function showDetails() {
+       console.log('showDetails()', g_state.tooltip.bookId);
+       
+       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>';
+
+       elem.innerHTML = html;
+       elem.style.display = 'block';
+}
+
+function startTooltipTimer(bookId) {
+       if (typeof g_state.tooltip.timer !== 'undefined') {
+               clearTimer(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();
 }