Improved algorithm for updating facets
[pazpar2-moved-to-github.git] / www / demo / search.js
1 /* $Id: search.js,v 1.38 2007-01-17 17:24:44 quinn Exp $
2  * ---------------------------------------------------
3  * Javascript container
4  */
5
6 var xmlHttp
7 var xinitSession;
8 var xloadTargets;
9 var xsearch;
10 var xshow;
11 var xstat;
12 var xtermlist;
13 var xfetchDetails;
14 var session = false;
15 var targetsloaded = false;
16 var shown;
17 var searchtimer;
18 var showtimer;
19 var termtimer;
20 var stattimer;
21 var session_cells = Array('query', 'startrec', 'action_type');
22 var old_session = session_read();
23 var url_surveillence;
24 var recstoshow = 20;
25 var page_window = 5;  // Number of pages prior to and after the current page
26 var facet_list;
27 var cur_sort = "relevance";
28 var searched = 0;
29 var cur_id = -1;
30 var cur_rec = 0;
31
32 function initialize ()
33 {
34     facet_list = get_available_facets();
35     start_session();
36     session_check();
37     set_sort();
38 }
39
40 function GetXmlHttpObject()
41
42     var objXMLHttp=null
43     if (window.XMLHttpRequest)
44       {
45       objXMLHttp=new XMLHttpRequest()
46       }
47     else if (window.ActiveXObject)
48       {
49       objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP")
50       }
51     return objXMLHttp
52
53
54 function SendXmlHttpObject(obj, url, handler)
55 {
56     obj.onreadystatechange=handler;
57     obj.open("GET", url);
58     obj.send(null);
59 }
60
61 function session_started()
62 {
63     if (xinitSession.readyState != 4)
64         return;
65     var xml = xinitSession.responseXML;
66     var sesid = xml.getElementsByTagName("session")[0].childNodes[0].nodeValue;
67     assign_text(document.getElementById("status"), 'Live');
68     session = sesid;
69     setTimeout(ping_session, 50000);
70 }
71
72 function start_session()
73 {
74     xinitSession = GetXmlHttpObject();
75     var url="search.pz2?";
76     url += "command=init";
77     xinitSession.onreadystatechange=session_started;
78     xinitSession.open("GET", url);
79     xinitSession.send(null);
80 }
81
82 function ping_session()
83 {
84     if (!session)
85         return;
86     var url = "search.pz2?command=ping&session=" + session;
87     SendXmlHttpObject(xpingSession = GetXmlHttpObject(), url, session_pinged);
88 }
89
90 function session_pinged()
91 {
92     if (xpingSession.readyState != 4)
93         return;
94     var xml = xpingSession.responseXML;
95     var error = xml.getElementsByTagName("error");
96     if (error[0])
97         location = "?";
98     else
99         setTimeout(ping_session, 50000);
100 }
101
102 function update_action (new_action) {
103     document.search.action_type.value = new_action;
104 }
105
106
107 function make_pager (hits, offset, max) {
108     var html = '';
109     var off;
110     var start_offset = offset - page_window * max;
111     var div_elem = document.createElement('div');
112     
113     div_elem.className = 'pages';
114
115     if (start_offset < 0) {
116         start_offset = 0;
117     }
118
119     for (off = start_offset;
120          off < hits && off < (start_offset + 2 * page_window * max); 
121          off += max) {
122         
123         var p = off / max + 1;
124         var page_elem = create_element('a', p);
125         var newline_node = document.createTextNode(' ');
126
127         if ((offset >= off) && (offset < (off + max))) {
128             page_elem.className = 'select';
129         }
130
131         page_elem.setAttribute('off', off);
132         page_elem.style.cursor = 'pointer';
133         page_elem.onclick = function () {
134             update_offset(this.getAttribute('off'));
135         };
136
137         div_elem.appendChild(page_elem);
138         div_elem.appendChild(newline_node);
139     }
140
141     return div_elem;
142 }
143
144
145 function update_offset (offset) {
146     clearTimeout(searchtimer);
147     document.search.startrec.value = offset;
148     update_action('page');
149     check_search();
150     update_history();
151     return false;
152 }
153
154
155 function create_element (name, cdata) {
156     var elem_node = document.createElement(name);
157     var text_node = document.createTextNode(cdata);
158     elem_node.appendChild(text_node);
159
160     return elem_node;
161 }
162
163
164 function clear_cell (cell) {
165     while (cell.hasChildNodes())
166         cell.removeChild(cell.firstChild);
167 }
168
169
170 function append_text(cell, text) {
171     text_node = document.createTextNode(text);
172     cell.appendChild(text_node);
173 }
174
175
176 function assign_text (cell, text) {
177     clear_cell(cell);
178     append_text(cell, text);
179 }
180
181 function set_sort_opt(n, opt, str)
182 {
183     var txt = document.createTextNode(str);
184     if (opt == cur_sort)
185         n.appendChild(txt);
186     else
187     {
188         var a = document.createElement('a');
189         a.appendChild(txt);
190         a.setAttribute('href', "");
191         a.setAttribute('onclick', "set_sort('" + opt + "'); return false");
192         n.appendChild(a);
193     }
194 }
195
196 function set_sort(sort)
197 {
198     if (sort && sort != cur_sort)
199     {
200         cur_sort = sort;
201         if (searched)
202             check_search();
203     }
204
205     var t = document.getElementById("sortselect");
206     clear_cell(t);
207     t.appendChild(document.createTextNode("Sort results by: "));
208     set_sort_opt(t, 'relevance', 'Relevance');
209     t.appendChild(document.createTextNode(" or "));
210     set_sort_opt(t, 'title:1', 'Title');
211 }
212
213 function displayname(name)
214 {
215     if (name == 'md-author')
216         return 'Author';
217     else if (name == 'md-subject')
218         return 'Subject';
219     else if (name == 'md-date')
220         return 'Date';
221     else if (name == 'md-isbn')
222         return 'ISBN';
223     else if (name == 'md-publisher')
224         return 'Publisher';
225     else if (name == 'md-url')
226         return 'URL';
227     else
228         return name;
229 }
230
231 function hyperlink_field(name)
232 {
233     if (name == 'md-author')
234         return 'au';
235     else if (name == 'md-subject')
236         return 'su';
237     else if (name == 'md-url')
238         return 'URL';
239     else
240         return 0;
241 }
242
243 function  paint_details_tr(name, dn)
244 {
245     //emit a table row
246     var dname = displayname(name);
247     var ln = create_element('b', dname);
248     var tln = document.createElement('td');
249     tln.setAttribute('width', 70);
250     tln.setAttribute('valign', 'top');
251     tln.appendChild(ln);
252     var tr = document.createElement('tr');
253     tr.appendChild(tln);
254     tr.appendChild(dn);
255     return tr;
256 }
257
258 function paint_details(body, xml)
259 {
260     // This is some ugly display code. Replace with your own ting o'beauty
261     clear_cell(body);
262     //body.appendChild(document.createElement('br'));
263     var nodes = xml.childNodes[0].childNodes;
264     var i;
265     var table = document.createElement('table');
266     table.setAttribute('cellpadding', 2);
267     var dn = 0;
268     var lastname = '';
269     for (i = 0; i < nodes.length; i++)
270     {
271         if (nodes[i].nodeType != 1)
272             continue;
273         var name = nodes[i].nodeName;
274         if (name == 'recid' || name == 'md-title')
275             continue;
276         if (name != lastname)
277         {
278             if (dn)
279             {
280                 var tr = paint_details_tr(lastname, dn);
281                 table.appendChild(tr);
282             }
283             dn = document.createElement('td');
284             lastname = name;
285         }
286
287         if (!nodes[i].childNodes[0])
288                 continue;
289         var value = nodes[i].childNodes[0].nodeValue;
290         if (dn.childNodes[0])
291             dn.appendChild(document.createTextNode('; '));
292         var hyl = hyperlink_field(name);
293         var nv;
294         if (hyl)
295         {
296             nv = create_element('a', value);
297             if (hyl == 'URL')
298             {
299                 nv.setAttribute('href', value);
300                 nv.setAttribute('target', '_blank');
301             }
302             else
303             {
304                 nv.setAttribute('href', '#');
305                 nv.setAttribute('term', value);
306                 nv.setAttribute('searchfield', hyl);
307                 nv.onclick = function() { hyperlink_search(this); return false; };
308             }
309         }
310         else
311             nv = document.createTextNode(value);
312         dn.appendChild(nv);
313     }
314     if (dn)
315     {
316         var tr = paint_details_tr(lastname, dn);
317         table.appendChild(tr);
318     }
319     body.appendChild(table);
320     body.style.display = 'inline';
321 }
322
323 function show_details()
324 {
325     if (xfetchDetails.readyState != 4)
326         return;
327     var xml = xfetchDetails.responseXML;
328     var error = xml.getElementsByTagName("error");
329     if (error[0])
330     {
331         var msg = error[0].childNodes[0].nodeValue;
332         alert(msg);
333         location = "?";
334         return;
335     }
336
337     var idn = xml.getElementsByTagName('recid');
338     if (!idn[0])
339         return;
340     var id = idn[0].childNodes[0].nodeValue;
341     cur_id = id;
342     cur_rec = xml;
343
344     var nodes = document.getElementsByName('listrecord');
345     var i;
346     for (i = 0; i < nodes.length; i++)
347     {
348         var dets = nodes[i].getElementsByTagName('div');
349         if (dets[0])
350             dets[0].style.display = 'none';
351     }
352
353     var body = document.getElementById('rec_' + id);
354     if (!body)
355         return;
356     paint_details(body, xml);
357 }
358
359 function hyperlink_search(obj)
360 {
361     var field = obj.getAttribute('searchfield');
362     var term = obj.getAttribute('term');
363     var queryfield  = document.getElementById('query');
364     queryfield.value = field + '=' + term;
365     start_search();
366 }
367
368 function fetch_details(id)
369 {
370     cur_id = -1;
371     if (id == cur_id)
372     {
373         cur_id = -1;
374         return;
375     }
376     if (!session)
377         return;
378     var url = "search.pz2?session=" + session +
379         "&command=record" +
380         "&id=" + id;
381     SendXmlHttpObject(xfetchDetails = GetXmlHttpObject(), url, show_details);
382 }
383
384 function show_records()
385 {
386     if (xshow.readyState != 4)
387         return;
388     var i;
389     var xml = xshow.responseXML;
390     var body = document.getElementById("body");
391     var hits = xml.getElementsByTagName("hit");
392
393     clear_cell(body);
394
395     if (!hits[0]) // We should never get here with blocking operations
396     {
397         assign_text(body, 'No records yet');
398         searchtimer = setTimeout(check_search, 250);
399     }
400     else
401     {
402         var total = Number(xml.getElementsByTagName('total')[0].childNodes[0].nodeValue);
403         var merged = Number(xml.getElementsByTagName('merged')[0].childNodes[0].nodeValue);
404         var start = Number(xml.getElementsByTagName('start')[0].childNodes[0].nodeValue);
405         var num = Number(xml.getElementsByTagName('num')[0].childNodes[0].nodeValue);
406         var clients = Number(xml.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue);
407         var pager = make_pager(merged, start,recstoshow);
408         var break_node1 = document.createElement('br');
409         var break_node2 = document.createElement('br');
410         var record_container = document.createElement('div');
411         var interval = create_element('div', 'Records : ' + (start + 1) +
412                                              ' to ' + (start + num) + ' of ' +
413                                              merged + ' (total hits: ' +
414                                              total + ')');
415         searched = 1;
416         interval.className = 'results';
417         record_container.className = 'records';
418
419         body.appendChild(pager);
420         body.appendChild(interval);
421         body.appendChild(break_node1);
422         body.appendChild(break_node2);
423         body.appendChild(record_container);
424
425         for (i = 0; i < hits.length; i++)
426         {
427             var tn = hits[i].getElementsByTagName("md-title");
428             var title = '';
429             var an = hits[i].getElementsByTagName("md-author");
430             var author = '';
431             var cn = hits[i].getElementsByTagName("count");
432             var count = 1;
433             var idn = hits[i].getElementsByTagName("recid");
434
435             if (tn[0]) {
436                 title = tn[0].childNodes[0].nodeValue;
437             } else {
438                 title = 'N/A';
439             }
440             if (an[0] && an[0].childNodes[0])
441                     author = an[0].childNodes[0].nodeValue;
442             if (cn[0])
443                 count = Number(cn[0].childNodes[0].nodeValue);
444             var id = idn[0].childNodes[0].nodeValue;
445             
446             var record_div = document.createElement('div');
447             record_div.className = 'record';
448             record_div.setAttribute('name', 'listrecord');
449
450             var record_cell = create_element('a', title);
451             record_cell.setAttribute('href', '#' + id);
452             record_cell.setAttribute('onclick', 'fetch_details(' + id + '); return false');
453             record_div.appendChild(record_cell);
454             if (author)
455             {
456                 record_div.appendChild(document.createTextNode(', by '));
457                 var al = create_element('a', author);
458                 al.setAttribute('href', '#');
459                 al.setAttribute('term', author);
460                 al.setAttribute('searchfield', 'au');
461                 al.onclick = function() { hyperlink_search(this); return false; };
462                 record_div.appendChild(al);
463             }
464             if (count > 1)
465                 record_div.appendChild(document.createTextNode(
466                         ' (' + count + ')'));
467             var det_div = document.createElement('div');
468             if (id == cur_id)
469                 paint_details(det_div, cur_rec);
470             else
471                 det_div.style.display = 'none';
472             det_div.setAttribute('id', 'rec_' + id);
473             det_div.setAttribute('name', 'details');
474             record_div.appendChild(det_div);
475             record_container.appendChild(record_div);
476         }
477
478         shown++;
479         if (clients > 0)
480         {
481             if (shown < 5)
482                 searchtimer = setTimeout(check_search, 1000);
483             else
484                 searchtimer = setTimeout(check_search, 2000);
485         }
486     }
487     if (!termtimer)
488         termtimer = setTimeout(check_termlist, 500);
489 }
490
491 function check_search()
492 {
493     clearTimeout(searchtimer);
494     var url = "search.pz2?" +
495         "command=show" +
496         "&start=" + document.search.startrec.value +
497         "&num=" + recstoshow +
498         "&session=" + session +
499         "&sort=" + cur_sort +
500         "&block=1";
501     xshow = GetXmlHttpObject();
502     xshow.onreadystatechange=show_records;
503     xshow.open("GET", url);
504     xshow.send(null);
505 }
506
507
508 function refine_query (obj) {
509     var term = obj.getAttribute('term');
510     var cur_termlist = obj.getAttribute('facet');
511     var query_cell = document.getElementById('query');
512     
513     term = term.replace(/[\(\)]/g, '');
514     
515     if (cur_termlist == 'subject')
516         query_cell.value += ' and su=(' + term + ')';
517     else if (cur_termlist == 'author')
518         query_cell.value += ' and au=(' + term + ')';
519     else if (cur_termlist == 'date')
520         query_cell.value += ' and date=' + term;
521
522     start_search();
523 }
524
525 function clear_termlists()
526 {
527     var i;
528     for (i = 0; i < facet_list.length; i++)
529         clear_cell(facet_list[i][1]);
530 }
531
532 function show_termlists()
533 {
534     if (xtermlist.readyState != 4)
535         return;
536
537     var i;
538     var xml = xtermlist.responseXML;
539     var clients =
540         Number(xml.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue);
541     var lists = xml.getElementsByTagName("list");
542
543     for (i = 0; i < lists.length; i++)
544     {
545         var listname = lists[i].getAttribute('name');
546         var body = document.getElementById('facet_' + listname + '_terms');
547         if (body.style.display == 'none')
548             continue;
549         clear_cell(body);
550         var terms = lists[i].getElementsByTagName('term');
551         var t;
552         for (t = 0; t < terms.length; t++)
553         {
554             var namen = terms[t].getElementsByTagName("name");
555             var freqn = terms[t].getElementsByTagName("frequency");
556             if (namen[0])
557                 var term = namen[0].childNodes[0].nodeValue;
558                 var freq = freqn[0].childNodes[0].nodeValue;
559                 var refine_cell = create_element('a', term + ' (' + freq + ')');
560                 refine_cell.setAttribute('href', '#');
561                 refine_cell.setAttribute('term', term);
562                 refine_cell.setAttribute('facet', listname);
563                 refine_cell.onclick = function () {
564                     refine_query(this);
565                     return false;
566                 };
567                 body.appendChild(refine_cell);
568         }
569     }
570     if (clients > 0)
571         termtimer = setTimeout(check_termlist, 1000);
572 }
573
574 function check_termlist()
575 {
576     var facet_names = '';
577     var i;
578     for (i = 0; i < facet_list.length; i++)
579         if (facet_list[i][1].style.display != 'none')
580         {
581             if (facet_names)
582                 facet_names += ',';
583             facet_names += facet_list[i][0];
584         }
585     var url = "search.pz2?" +
586         "command=termlist" +
587         "&session=" + session +
588         "&name=" + facet_names +
589         "&num=12";
590     SendXmlHttpObject(xtermlist = GetXmlHttpObject(), url, show_termlists);
591 }
592
593 function show_stat()
594 {
595     if (xstat.readyState != 4)
596         return;
597     var i;
598     var xml = xstat.responseXML;
599     var body = document.getElementById("stat");
600     var nodes = xml.childNodes[0].childNodes;
601     var clients =
602         Number(xml.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue);
603     if (!nodes[0])
604     {
605         stattimer  = setTimeout(check_stat, 500);
606     }
607     else
608     {
609         assign_text(body, '(');
610         for (i = 0; i < nodes.length; i++)
611         {
612             if (nodes[i].nodeType != 1)
613                 continue;
614             var value = nodes[i].childNodes[0].nodeValue;
615             if (value == 0)
616                 continue;
617             var name = nodes[i].nodeName;
618             append_text(body, ' ' + name + '=' + value);
619         }
620
621         append_text(body, ')');
622         if (clients > 0)
623             stattimer = setTimeout(check_stat, 2000);
624     }
625 }
626
627 function check_stat()
628 {
629     var url = "search.pz2?" +
630         "command=stat" +
631         "&session=" + session;
632     xstat = GetXmlHttpObject();
633     xstat.onreadystatechange=show_stat;
634     xstat.open("GET", url);
635     xstat.send(null);
636 }
637
638 function search_started()
639 {
640     if (xsearch.readyState != 4)
641         return;
642     var xml = xsearch.responseXML;
643     var error = xml.getElementsByTagName("error");
644     if (error[0])
645     {
646         var msg = error[0].childNodes[0].nodeValue;
647         alert(msg);
648         return;
649     }
650     check_search();
651     stattimer = setTimeout(check_stat, 1000);
652 }
653
654 function start_search()
655 {
656     clearTimeout(termtimer);
657     termtimer = 0;
658     clearTimeout(searchtimer);
659     searchtimer = 0;
660     clearTimeout(stattimer);
661     stattimer = 0;
662     clearTimeout(showtimer);
663     showtimer = 0;
664     cur_id = -1;
665     clear_termlists();
666     var query = escape(document.getElementById('query').value);
667     var url = "search.pz2?" +
668         "command=search" +
669         "&session=" + session +
670         "&query=" + query;
671     xsearch = GetXmlHttpObject();
672     xsearch.onreadystatechange=search_started;
673     xsearch.open("GET", url);
674     xsearch.send(null);
675     clear_cell(document.getElementById("body"));
676     update_history();
677     shown = 0;
678     document.search.startrec.value = 0;
679 }
680
681 function session_encode ()
682 {
683     var i;
684     var session = '';
685
686     for (i = 0; i < session_cells.length; i++)
687     {
688         var name = session_cells[i];
689         var value = escape(document.getElementById(name).value);
690         session += '&' + name + '=' + value;
691     }
692
693     return session;
694 }
695
696
697 function session_restore (session)
698 {
699     var fields = session.split(/&/);
700     var i;
701
702     for (i = 1; i < fields.length; i++)
703     {
704         var pair = fields[i].split(/=/);
705         var key = pair.shift();
706         var value = pair.join('=');
707         var cell = document.getElementById(key);
708
709         cell.value = value;
710     }
711     
712 }
713
714
715 function session_read ()
716 {
717     var ses = window.location.hash.replace(/^#/, '');
718     return ses;
719 }
720
721
722 function session_store (new_value)
723 {
724     window.location.hash = '#' + new_value;
725 }
726
727
728 function update_history ()
729 {
730     var session = session_encode();
731     session_store(session);
732     old_session = session;
733 }
734
735
736 function session_check ()
737 {
738     var session = session_read();
739     var action = document.search.action_type.value;
740
741     clearInterval(url_surveillence);
742
743     if ( session != unescape(old_session) )
744     {
745         session_restore(session);
746
747         if (action == 'search') {
748             start_search();
749         } else if (action == 'page') {
750             check_search();
751         } else {
752             alert('Unregocnized action_type: ' + action);
753             return;
754         }
755     }
756     
757     url_surveillence = setInterval(session_check, 200);
758 }
759
760
761 function get_available_facets () {
762     var facet_container = document.getElementById('termlists');
763     var facet_cells = facet_container.childNodes;
764     var facets = Array();
765     var i;
766
767     for (i = 0; i < facet_cells.length; i++) {
768         var cell = facet_cells.item(i);
769
770         if (cell.className == 'facet') {
771             var facet_name = cell.id.replace(/^facet_([^_]+)_terms$/, "$1");
772             facets.push(Array(facet_name, cell));
773         }
774     }
775
776     return facets;
777 }
778
779
780 function get_facet_container (obj) {
781     return document.getElementById(obj.id + '_terms');
782 }
783
784
785 function toggle_facet (obj) {
786     var container = get_facet_container(obj);
787
788     if (obj.className == 'selected') {
789         obj.className = 'unselected';
790         container.style.display = 'inline';
791         check_termlist();
792     } else {
793         obj.className = 'selected';
794         container.style.display = 'none';
795     }
796 }