Add timeout before displaying tooltip with book details.
[quanweb.git] / app / lib.js
1 //QuanLib:  eBook Library
2 //(C) 2017 by Christian Jaekl (cejaekl@yahoo.com)
3
4 g_state = {
5                 cache: [],
6                 count: 0,
7                 first: 0,
8                 ids: [],
9                 last: (-1),
10                 map: {},        // map from book.Id to index into cache[]
11                 pageSize: 9,
12                 tooltip: {
13                         bookId: undefined,
14                         milliSecs: 500,     // time to wait before displaying tooltip
15                         timer: undefined
16                 }
17 }
18
19 function adjustPos(setting) {
20         var value = parseInt(setting)
21
22         if (g_state.first === value) {
23                 // No change
24                 return;
25         }
26
27         var maxFirst = g_state.count - g_state.pageSize;
28
29         if (value < 0) {
30                 g_state.first = 0;
31         } else if (value > maxFirst) {
32                 g_state.first = maxFirst;
33         } else {
34                 g_state.first = value;
35         }
36
37         g_state.last = g_state.first + g_state.pageSize - 1;
38         if (g_state.last >= g_state.count) {
39                 g_state.last = g_state.count - 1;
40         }
41
42         document.getElementById('slider').value = setting;
43
44         refreshData();
45 }
46
47 function bookHtml(book) {
48         var result = '<div class="book">'
49                 +   '<table>'
50                 +     '<tr>'
51                 +       '<td><a href="/book/' + book.Id + '">';
52         if (0 == book.CoverId) {
53                 result +=          '(No cover available)'
54         } else {
55                 result +=          '<img class="cover-thumb" src="/download/' + book.CoverId + '"/>'
56         }
57         result     +=       '</a></td>'
58                 +       '<td onclick="displayDetails(' + book.Id + ');" '
59                 +          ' onmouseout="stopTooltipTimer(); hideDetails();" '
60                 +          ' onmouseover="startTooltipTimer(' + book.Id + ');">'
61                 +         '<p><b>' + book.Title + '</b></p>'
62                 +         '<p>'
63                 +           '<i>' + book.AuthorReading + '</i>';
64         if (typeof(book.SeriesName) !== 'undefined' && book.SeriesName.length > 0) {
65                 result +=          '<br/><i>' + book.SeriesName + ' ' + book.Volume + '</i>';
66         }
67         result     +=         '</p>'
68                 +       '</td>'
69                 +     '</tr>'
70                 +   '</table>'
71                 + '</div>';
72         return result;
73 }
74
75 //ce(s):  "clear if empty()"
76 //return s, unless it's undefined, in which case return an empty ("clear") string
77 function ce(s) {
78         if (typeof s !== 'undefined') {
79                 return s;
80         }
81         return '';
82 }
83
84 function constructSearchUrl() {
85         var url = window.location.protocol + '//' + window.location.host + '/search/';
86
87         var firstTime = true;
88         var terms = ['aut', 'tit', 'ser'];
89
90         for (idx in terms) {
91                 var term = terms[idx];
92                 var elem = document.getElementById(term);
93                 if (null === elem) {
94                         console.log('Error:  could not find form element for search term "' + term + '".');
95                         continue;
96                 }
97
98                 var value = elem.value;
99                 if (value.length > 0) {
100                         if (firstTime) {
101                                 url += '?';
102                                 firstTime = false;
103                         }
104                         else {
105                                 url += '&';
106                         }
107                         url += term + '=' + encodeURIComponent('%' + value + '%');
108                 }
109         }
110
111         return url;
112 }
113
114 // Set the book ID for the details pane, and then show it
115 function displayDetails(bookId) {
116         console.log('displayDetails()', bookId);
117         g_state.tooltip.bookId = bookId;
118         showDetails();
119 }
120
121 function hideDetails() {
122         var elem = document.getElementById('details');
123         elem.innerHtml = '';
124         elem.style.display = 'none';
125 }
126
127 function onNext() {
128         if (g_state.last < (g_state.count - 1)) {
129                 adjustPos(g_state.first + g_state.pageSize);
130         }
131 }
132
133 function onPrev() {
134         if (g_state.first > 0) {
135                 adjustPos(g_state.first - g_state.pageSize);
136         }
137 }
138
139 function onSlide(value) {
140         adjustPos(value);
141 }
142
143 function onSearch() {
144         console.log('onSearch()');
145
146         var url = constructSearchUrl();
147
148         report('Loading data from server, please wait...')
149         console.log('Fetching:  "' + url + '"...')
150
151         fetch(url, {method:'GET', cache:'default'})
152         .then(response => response.json())
153         .then((jsonValue) => {
154                 console.log('JSON response:  ', jsonValue);
155                 g_state.ids = jsonValue
156                 g_state.count = g_state.ids.length;
157                 g_state.first = (-1)
158
159                 var elem = document.getElementById('slider');
160                 elem.max = g_state.count;
161
162                 adjustPos(0);
163         })
164         .catch(err => { 
165                 var msg = 'Error fetching JSON from URL:  ' + url + ': ' + err + ':' + err.stack;
166                 console.log(msg);
167                 report(msg);
168         });
169 }
170
171 function refreshData() {
172         report('Loading details for books ' + g_state.first + ' through ' + g_state.last + ', please wait...');
173
174         var i;
175         var url = '/info/?ids=';
176         g_state.map = {};
177         for (i = g_state.first; i <= g_state.last; ++i) {
178                 if (i > g_state.first) {
179                         url += ',';
180                 }
181                 var id = g_state.ids[i];
182                 url += id;
183                 g_state.map[id] = i - g_state.first;
184         }
185
186         fetch(url, {method:'GET', cache:'default'})
187         .then(response => response.json())
188         .then((jsonValue) => {
189                 console.log('JSON response for info:  ', jsonValue);
190                 report('');
191                 g_state.cache = jsonValue;
192                 refreshLayout();
193         })
194         .catch(err => {
195                 var msg = 'Error fetching book details via URL:  ' + url + ': ' + err;
196                 console.log(msg, err.stack);
197                 report(msg);
198         });
199 }
200
201 function refreshLayout() {
202         var i;
203         var html = '';
204         var limit = g_state.last - g_state.first;
205         for (i = 0; i <= limit; ++i) {
206                 var book = g_state.cache[i];
207                 html += bookHtml(book);
208         }
209
210         document.getElementById('books').innerHTML = html;
211         document.getElementById('first').innerHTML = (g_state.first + 1);
212         document.getElementById('last').innerHTML = (g_state.last + 1);
213         document.getElementById('count').innerHTML = g_state.count;
214 }
215
216 function report(message) {
217         document.getElementById('books').innerHTML = message;
218 }
219
220 function showDetails() {
221         console.log('showDetails()', g_state.tooltip.bookId);
222         
223         var id = g_state.tooltip.bookId;
224         var elem = document.getElementById('details');
225         var index = g_state.map[id];
226         var book = g_state.cache[index];
227         var html = '<div><p><b>' + book.Title + '</b></p>'
228         + '<p><i>' + ce(book.AuthorReading) + '<br/>' + ce(book.Series) + ' ' + ce(book.Volume) + '</i></p></div><div>'
229         + ce(book.Description)
230         + '</div>';
231
232         elem.innerHTML = html;
233         elem.style.display = 'block';
234 }
235
236 function startTooltipTimer(bookId) {
237         if (typeof g_state.tooltip.timer !== 'undefined') {
238                 clearTimer(g_state.tooltip.timer);
239         }
240         g_state.tooltip.bookId = bookId;
241         g_state.tooltip.timer = setTimeout(showDetails, g_state.tooltip.milliSecs);
242 }
243
244 function stopTooltipTimer() {
245         if (typeof g_state.tooltip.timer === 'undefined') {
246                 return;
247         }
248         clearTimeout(g_state.tooltip.timer);
249         g_state.tooltip.timer = undefined;
250         hideDetails();
251 }
252