don't use absolute path for ?lang=<...> links
[mkws-moved-to-github.git] / experiments / spclient / mkws.js
1 /* A very simple client that shows a basic usage of the pz2.js
2 */
3
4 "use strict"; // HTML5: disable for debug >= 2
5
6 // global config object mkws_config 
7 if (!mkws_config)
8     var mkws_config = {}; // for the guys who forgot to define mkws_config...
9
10 var pazpar2_url = mkws_config.pazpar2_url ? mkws_config.pazpar2_url : "/pazpar2/search.pz2";
11 var service_proxy_url = mkws_config.service_proxy_url ? mkws_config.service_proxy_url : "/service-proxy/";
12
13 var pazpar2path = mkws_config.use_service_proxy ? service_proxy_url : pazpar2_url;
14 var usesessions = mkws_config.use_service_proxy ? false : true;
15
16 var mkws_debug = 1;
17
18 var mkws_locale_lang = {
19     "de": {
20         "Authors": "Autoren",
21         "Subjects": "Schlagw&ouml;rter",
22         "Sources": "Daten und Quellen",
23         "TERMLISTS": "Termlisten",
24         "Next": "Weiter",
25         "Prev": "Zur&uuml;ck",
26         "Search": "Suche",
27         "Sort by": "Sortieren nach",
28         "and show": "und zeige",
29         "per page": "pro Seite",
30         "Displaying": "Zeige",
31         "to": "von",
32         "of": "aus",
33         "found": "gefunden",
34         "Title": "Titel",
35         "Author": "Autor",
36         "Date": "Datum",
37         "Subject": "Schlagwort",
38         "Location": "Ort",
39
40         "dummy": "dummy"
41     },
42
43     "da": {
44         "Authors": "Forfattere",
45         "Subjects": "Emner",
46         "Sources": "Kilder",
47         "TERMLISTS": "TERMLISTS",
48         "Next": "N&aelig;ste",
49         "Prev": "Forrige",
50         "Search": "S&oslash;g",
51         "Sort by": "Sorter efter",
52         "and show": "og vis",
53         "per page": "per side",
54         "Displaying": "Viser",
55         "to": "til",
56         "of": "ud af",
57         "found": "fandt",
58         "Title": "Title",
59         "Author": "Forfatter",
60         "Date": "Dato",
61         "Subject": "Emneord",
62         "Location": "Lokation",
63
64         "dummy": "dummy"
65     }
66 };
67
68 // create a parameters array and pass it to the pz2's constructor
69 // then register the form submit event with the pz2.search function
70 // autoInit is set to true on default
71 var my_paz = new pz2( { "onshow": my_onshow,
72                     "showtime": 500,            //each timer (show, stat, term, bytarget) can be specified this way
73                     "pazpar2path": pazpar2path,
74                     "oninit": my_oninit,
75                     "onstat": my_onstat,
76                     "onterm": my_onterm,
77                     "termlist": "xtargets,subject,author",
78                     "onbytarget": my_onbytarget,
79                     "usesessions" : usesessions,
80                     "showResponseType": '', // or "json" (for debugging?)
81                     "onrecord": my_onrecord } );
82 // some state vars
83 var curPage = 1;
84 var recPerPage = 20;
85 var totalRec = 0;
86 var curDetRecId = '';
87 var curDetRecData = null;
88 var curSort = 'relevance';
89 var curFilter = null;
90 var submitted = false;
91 var SourceMax = 16;
92 var SubjectMax = 10;
93 var AuthorMax = 10;
94
95 //
96 // pz2.js event handlers:
97 //
98 function my_oninit() {
99     my_paz.stat();
100     my_paz.bytarget();
101 }
102
103 function my_onshow(data) {
104     totalRec = data.merged;
105     // move it out
106     var pager = document.getElementById("pager");
107     pager.innerHTML = "";
108     pager.innerHTML +='<hr/><div style="float: right">' + M('Displaying') + ': '
109                     + (data.start + 1) + ' ' + M('to') + ' ' + (data.start + data.num) +
110                      ' ' + M('of') + ' ' + data.merged + ' (' + M('found') + ': '
111                      + data.total + ')</div>';
112     drawPager(pager);
113     // navi
114     var results = document.getElementById("results");
115
116     var html = [];
117     for (var i = 0; i < data.hits.length; i++) {
118         var hit = data.hits[i];
119               html.push('<div class="record" id="recdiv_'+hit.recid+'" >'
120             +'<span>'+ (i + 1 + recPerPage * (curPage - 1)) +'. </span>'
121             +'<a href="#" id="rec_'+hit.recid
122             +'" onclick="showDetails(this.id);return false;"><b>'
123             + hit["md-title"] +' </b></a>');
124               if (hit["md-title-remainder"] !== undefined) {
125                 html.push('<span>' + hit["md-title-remainder"] + ' </span>');
126               }
127               if (hit["md-title-responsibility"] !== undefined) {
128             html.push('<span><i>'+hit["md-title-responsibility"]+'</i></span>');
129         }
130         if (hit.recid == curDetRecId) {
131             html.push(renderDetails(curDetRecData));
132         }
133         html.push('</div>');
134     }
135     replaceHtml(results, html.join(''));
136 }
137
138 function my_onstat(data) {
139     var stat = document.getElementById("mkwsStat");
140     if (stat == null)
141         return;
142
143     stat.innerHTML = '<b>STATUS INFO</b> -- Active clients: '
144                         + data.activeclients
145                         + '/' + data.clients + ' -- </span>'
146                         + '<span>Retrieved records: ' + data.records
147                         + '/' + data.hits + ' :.</span>';
148 }
149
150 function my_onterm(data) {
151     if (!mkws_config.termlist_menu)
152         return;
153     
154     var termlists = [];
155     termlists.push('<hr/><b>' + M('TERMLISTS') + ':</b><hr/><div class="termtitle">' + M('Sources') + '</div>');
156     for (var i = 0; i < data.xtargets.length && i < SourceMax; i++ ) {
157         termlists.push('<a href="#" target_id='+data.xtargets[i].id
158             + ' onclick="limitTarget(this.getAttribute(\'target_id\'), this.firstChild.nodeValue);return false;">' + data.xtargets[i].name
159         + ' </a><span> (' + data.xtargets[i].freq + ')</span><br/>');
160     }
161
162     termlists.push('<hr/><div class="termtitle">' + M('Subjects') + '</div>');
163     for (var i = 0; i < data.subject.length && i < SubjectMax; i++ ) {
164         termlists.push('<a href="#" onclick="limitQuery(\'su\', this.firstChild.nodeValue);return false;">' + data.subject[i].name + '</a><span>  ('
165               + data.subject[i].freq + ')</span><br/>');
166     }
167
168     termlists.push('<hr/><div class="termtitle">' + M('Authors') + '</div>');
169     for (var i = 0; i < data.author.length && i < AuthorMax; i++ ) {
170         termlists.push('<a href="#" onclick="limitQuery(\'au\', this.firstChild.nodeValue);return false;">'
171                             + data.author[i].name
172                             + ' </a><span> ('
173                             + data.author[i].freq
174                             + ')</span><br/>');
175     }
176     var termlist = document.getElementById("termlist");
177     replaceHtml(termlist, termlists.join(''));
178 }
179
180 function my_onrecord(data) {
181     // FIXME: record is async!!
182     clearTimeout(my_paz.recordTimer);
183     // in case on_show was faster to redraw element
184     var detRecordDiv = document.getElementById('det_'+data.recid);
185     if (detRecordDiv) return;
186     curDetRecData = data;
187     var recordDiv = document.getElementById('recdiv_'+curDetRecData.recid);
188     var html = renderDetails(curDetRecData);
189     recordDiv.innerHTML += html;
190 }
191
192 function my_onbytarget(data) {
193     var targetDiv = document.getElementById("bytarget");
194     var table ='<table><thead><tr><td>Target ID</td><td>Hits</td><td>Diags</td>'
195         +'<td>Records</td><td>State</td></tr></thead><tbody>';
196
197     for (var i = 0; i < data.length; i++ ) {
198         table += "<tr><td>" + data[i].id +
199             "</td><td>" + data[i].hits +
200             "</td><td>" + data[i].diagnostic +
201             "</td><td>" + data[i].records +
202             "</td><td>" + data[i].state + "</td></tr>";
203     }
204
205     table += '</tbody></table>';
206     targetDiv.innerHTML = table;
207 }
208
209 ////////////////////////////////////////////////////////////////////////////////
210 ////////////////////////////////////////////////////////////////////////////////
211
212 // wait until the DOM is ready
213 function domReady ()
214 {
215     document.search.onsubmit = onFormSubmitEventHandler;
216     document.search.query.value = '';
217     document.select.sort.onchange = onSelectDdChange;
218     document.select.perpage.onchange = onSelectDdChange;
219 }
220
221 // when search button pressed
222 function onFormSubmitEventHandler()
223 {
224     resetPage();
225     loadSelect();
226     triggerSearch();
227     submitted = true;
228     return false;
229 }
230
231 function onSelectDdChange()
232 {
233     if (!submitted) return false;
234     resetPage();
235     loadSelect();
236     my_paz.show(0, recPerPage, curSort);
237     return false;
238 }
239
240 function resetPage()
241 {
242     curPage = 1;
243     totalRec = 0;
244 }
245
246 function triggerSearch ()
247 {
248     my_paz.search(document.search.query.value, recPerPage, curSort, curFilter);
249 }
250
251 function loadSelect ()
252 {
253     curSort = document.select.sort.value;
254     recPerPage = document.select.perpage.value;
255 }
256
257 // limit the query after clicking the facet
258 function limitQuery (field, value)
259 {
260     document.search.query.value += ' and ' + field + '="' + value + '"';
261     onFormSubmitEventHandler();
262 }
263
264 // limit by target functions
265 function limitTarget (id, name)
266 {
267     var navi = document.getElementById('navi');
268     navi.innerHTML =
269         'Source: <a class="crossout" href="#" onclick="delimitTarget();return false;">'
270         + name + '</a>';
271     navi.innerHTML += '<hr/>';
272     curFilter = 'pz:id=' + id;
273     resetPage();
274     loadSelect();
275     triggerSearch();
276     return false;
277 }
278
279 function delimitTarget ()
280 {
281     var navi = document.getElementById('navi');
282     navi.innerHTML = '';
283     curFilter = null;
284     resetPage();
285     loadSelect();
286     triggerSearch();
287     return false;
288 }
289
290 function drawPager (pagerDiv)
291 {
292     //client indexes pages from 1 but pz2 from 0
293     var onsides = 6;
294     var pages = Math.ceil(totalRec / recPerPage);
295
296     var firstClkbl = ( curPage - onsides > 0 )
297         ? curPage - onsides
298         : 1;
299
300     var lastClkbl = firstClkbl + 2*onsides < pages
301         ? firstClkbl + 2*onsides
302         : pages;
303
304     var prev = '<span id="prev">&#60;&#60; ' + M('Prev') + '</span><b> | </b>';
305     if (curPage > 1)
306         prev = '<a href="#" id="prev" onclick="pagerPrev();">'
307         +'&#60;&#60; ' + M('Prev') + '</a><b> | </b>';
308
309     var middle = '';
310     for(var i = firstClkbl; i <= lastClkbl; i++) {
311         var numLabel = i;
312         if(i == curPage)
313             numLabel = '<b>' + i + '</b>';
314
315         middle += '<a href="#" onclick="showPage(' + i + ')"> '
316             + numLabel + ' </a>';
317     }
318
319     var next = '<b> | </b><span id="next">' + M('Next') + ' &#62;&#62;</span>';
320     if (pages - curPage > 0)
321         next = '<b> | </b><a href="#" id="next" onclick="pagerNext()">'
322         + M('Next') + ' &#62;&#62;</a>';
323
324     var predots = '';
325     if (firstClkbl > 1)
326         predots = '...';
327
328     var postdots = '';
329     if (lastClkbl < pages)
330         postdots = '...';
331
332     pagerDiv.innerHTML += '<div style="float: clear">'
333         + prev + predots + middle + postdots + next + '</div><hr/>';
334 }
335
336 function showPage (pageNum)
337 {
338     curPage = pageNum;
339     my_paz.showPage( curPage - 1 );
340 }
341
342 // simple paging functions
343
344 function pagerNext() {
345     if ( totalRec - recPerPage*curPage > 0) {
346         my_paz.showNext();
347         curPage++;
348     }
349 }
350
351 function pagerPrev() {
352     if ( my_paz.showPrev() != false )
353         curPage--;
354 }
355
356 // swithing view between targets and records
357
358 function switchView(view) {
359
360     var targets = document.getElementById('mkwsTargets');
361     var records = document.getElementById('mkwsRecords');
362
363     switch(view) {
364         case 'targets':
365             targets.style.display = "block";
366             records.style.display = "none";
367             break;
368         case 'records':
369             targets.style.display = "none";
370             records.style.display = "block";
371             break;
372         default:
373             alert('Unknown view.');
374     }
375 }
376
377 // detailed record drawing
378 function showDetails (prefixRecId) {
379     var recId = prefixRecId.replace('rec_', '');
380     var oldRecId = curDetRecId;
381     curDetRecId = recId;
382
383     // remove current detailed view if any
384     var detRecordDiv = document.getElementById('det_'+oldRecId);
385     // lovin DOM!
386     if (detRecordDiv)
387       detRecordDiv.parentNode.removeChild(detRecordDiv);
388
389     // if the same clicked, just hide
390     if (recId == oldRecId) {
391         curDetRecId = '';
392         curDetRecData = null;
393         return;
394     }
395     // request the record
396     my_paz.record(recId);
397 }
398
399 function replaceHtml(el, html) {
400   var oldEl = typeof el === "string" ? document.getElementById(el) : el;
401   /*@cc_on // Pure innerHTML is slightly faster in IE
402     oldEl.innerHTML = html;
403     return oldEl;
404     @*/
405   var newEl = oldEl.cloneNode(false);
406   newEl.innerHTML = html;
407   oldEl.parentNode.replaceChild(newEl, oldEl);
408   /* Since we just removed the old element from the DOM, return a reference
409      to the new element, which can be used to restore variable references. */
410   return newEl;
411 };
412
413 function renderDetails(data, marker)
414 {
415     var details = '<div class="details" id="det_'+data.recid+'"><table>';
416     if (marker) details += '<tr><td>'+ marker + '</td></tr>';
417     if (data["md-title"] != undefined) {
418         details += '<tr><td><b>' + M('Title') + '</b></td><td><b>:</b> '+data["md-title"];
419         if (data["md-title-remainder"] !== undefined) {
420               details += ' : <span>' + data["md-title-remainder"] + ' </span>';
421         }
422         if (data["md-title-responsibility"] !== undefined) {
423               details += ' <span><i>'+ data["md-title-responsibility"] +'</i></span>';
424         }
425           details += '</td></tr>';
426     }
427     if (data["md-date"] != undefined)
428         details += '<tr><td><b>' + M('Date') + '</b></td><td><b>:</b> ' + data["md-date"] + '</td></tr>';
429     if (data["md-author"] != undefined)
430         details += '<tr><td><b>' + M('Author') + '</b></td><td><b>:</b> ' + data["md-author"] + '</td></tr>';
431     if (data["md-electronic-url"] != undefined)
432         details += '<tr><td><b>URL</b></td><td><b>:</b> <a href="' + data["md-electronic-url"] + '" target="_blank">' + data["md-electronic-url"] + '</a>' + '</td></tr>';
433     if (data["location"][0]["md-subject"] != undefined)
434         details += '<tr><td><b>' + M('Subject') + '</b></td><td><b>:</b> ' + data["location"][0]["md-subject"] + '</td></tr>';
435     if (data["location"][0]["@name"] != undefined)
436         details += '<tr><td><b>' + M('Location') + '</b></td><td><b>:</b> ' + data["location"][0]["@name"] + " (" +data["location"][0]["@id"] + ")" + '</td></tr>';
437     details += '</table></div>';
438     return details;
439 }
440
441 /*
442  * All the HTML stuff to render the search forms and
443  * result pages.
444  */
445 function mkws_html_all(data) {
446
447     /* default config */
448     var config = {
449         sort: [["relevance"], ["title:1", "title"], ["date:0", "newest"], ["date:1", "oldest"]],
450         perpage: [10, 20, 30, 50],
451         sort_default: "relevance",
452         perpage_default: 20,
453         query_width: 50,
454         switch_menu: true,      /* show/hide Records|Targets menu */
455         lang_menu: true,        /* show/hide language menu */
456         lang_display: [],       /* display languages links for given languages, [] for all */
457         termlist_menu: true,    /* show/hide termlist */
458         debug: 0,     /* debug level for development: 0..2 */
459
460         dummy: "dummy"
461     };
462
463     /* set global debug flag early */
464     if (data.debug !== 'undefined') {
465         mkws_debug = data.debug;
466     } else if (config.debug !== 'undefined') {
467         mkws_debug = config.debug;
468     }
469     
470     /* override standard config values by function parameters */
471     for (var k in data) {
472         config[k] = data[k];
473         debug("Set config: " + k + ' => ' + data[k]);
474     }
475     if (mkws_config.query_width < 5 || mkws_config.query_width > 150) {
476         debug("Reset query width: " + mkws_config.query_width);
477         mkws_config.query_width = 50;
478     }
479    
480     mkws_set_lang(mkws_config); 
481     if (mkws_config.lang_menu)
482         mkws_html_lang(mkws_config); 
483
484     // For some reason, doing this programmatically results in
485     // document.search.query being undefined, hence the raw HTML.
486     debug("HTML search form");
487     $("#mkwsSearch").html('\
488     <form id="searchForm" name="search" action="" >\
489       <input id="query" type="text" size="' + mkws_config.query_width + '" />\
490       <input id="button" type="submit" value="' + M('Search') + '" />\
491     </form>');
492
493     debug("HTML records");
494     $("#mkwsRecords").html('\
495       <table width="100%" border="0" cellpadding="6" cellspacing="0">\
496         <tr>\
497           <td width="250" valign="top">\
498             <div id="termlist"></div>\
499           </td>\
500           <td valign="top">\
501             <div id="ranking">\
502               <form name="select" id="select" action="" >\
503         ' + M('Sort by') + mkws_html_sort(config) + '\
504         ' + M('and show') + ' ' + mkws_html_perpage(config) + '\
505         ' + M('per page') + '.\
506        </form>\
507             </div>\
508             <div id="pager"></div>\
509             <div id="navi"></div>\
510             <div id="results"></div>\
511           </td>\
512         </tr>\
513       </table>');
514
515     mkws_html_switch(config);
516     if (mkws_config.use_service_proxy)
517         mkws_service_proxy_auth(config.service_proxy_auth);
518
519     domReady();
520 }
521
522 function mkws_set_lang(mkws_config)  {
523     var lang = jQuery.parseQuerystring().lang || mkws_config.lang || "";
524     if (!lang || !mkws_locale_lang[lang]) {
525         mkws_config.lang = ""
526     } else {
527         mkws_config.lang = lang;
528     }
529     
530     debug("Locale language: " + (mkws_config.lang ? mkws_config.lang : "none"));
531     return mkws_config.lang;
532 }
533
534 function mkws_html_switch(config) {
535     debug("HTML switch");
536     
537     $("#mkwsSwitch").html($("<a/>", {
538         href: '#',
539         onclick: "switchView(\'records\')",
540         text: "Records"
541     }));
542     $("#mkwsSwitch").append($("<span/>", { text: " | " }));
543     $("#mkwsSwitch").append($("<a/>", {
544         href: '#',
545         onclick: "switchView(\'targets\')",
546         text: "Targets"
547     }));
548
549     debug("HTML targets");
550     $("#mkwsTargets").html('\
551       <div id="bytarget">\
552        No information available yet.\
553       </div>');
554     $("#mkwsTargets").css("display", "none");
555
556     if (!config.switch_menu) {
557         debug("disable switch menu");
558         $("#mkwsSwitch").css("display", "none");
559     }
560 }
561
562 function mkws_html_sort(config) {
563     debug("HTML sort");
564     var sort_html = '<select name="sort" id="sort">';
565
566     for(var i = 0; i < config.sort.length; i++) {
567         var key = config.sort[i][0];
568         var val = config.sort[i].length == 1 ? config.sort[i][0] : config.sort[i][1];
569
570         sort_html += '<option value="' + key + '"';
571         if (key == config.sort_default) {
572             sort_html += ' selected="selected"';
573         }
574         sort_html += '>' + val + '</option>';
575     }
576     sort_html += '</select>';
577
578     return sort_html;
579 }
580
581 function mkws_html_perpage(config) {
582     debug("HTML perpage");
583     var perpage_html = '<select name="perpage" id="perpage">';
584
585     for(var i = 0; i < config.perpage.length; i++) {
586         var key = config.perpage[i];
587
588         perpage_html += '<option value="' + key + '"';
589         if (key == config.perpage_default) {
590             perpage_html += ' selected="selected"';
591         }
592         perpage_html += '>' + key + '</option>';
593     }
594     perpage_html += '</select>';
595
596     return perpage_html;
597 }
598
599 /*
600  * Run service-proxy authentication in background (after page load).
601  * The username/password is configured in the apache config file
602  * for the site.
603  */
604 function mkws_service_proxy_auth(auth_url) {
605     if (!auth_url)
606         auth_url = "/service-proxy-auth";
607         
608     debug("Run service proxy auth URL: " + auth_url);
609
610     var jqxhr = jQuery.get(auth_url)
611         .fail(function() {
612             alert("service proxy authentication failed, give up!");
613         })
614         .success(function(data) {
615             if (!jQuery.isXMLDoc(data)) {
616                 alert("service proxy auth response document is not valid XML document, give up!");
617                 return;
618             }
619             var status = $(data).find("status");
620             if (status.text() != "OK") {
621                 alert("service proxy auth repsonse status: " + status.text() + ", give up!");
622                 return;
623             }
624         });
625 }
626
627 /* create locale language menu */
628 function mkws_html_lang(mkws_config) {
629     var lang_default = "en";
630     var lang = mkws_config.lang || lang_default;
631     var list = [];
632
633     /* display a list of configured languages, or all */
634     var lang_display = mkws_config.lang_display || [];
635     var hash = {};
636     for (var i = 0; i < lang_display.length; i++) {
637         hash[lang_display[i]] = 1;
638     }
639     
640     if (hash[lang_default] == 1)
641         list.push(lang_default);
642
643     for (var k in mkws_locale_lang) {
644         if (hash[k] == 1 || lang_display.length == 0)
645             list.push(k);
646     }
647     debug("Language menu for: " + list.join(", "));
648
649     /* the HTML part */
650     var data = "";    
651     for(var i = 0; i < list.length; i++) {
652         var l = list[i];
653         
654         if (data)
655             data += ' | ';
656             
657         if (lang == l) {
658             data += l;
659         } else {
660             data += ' <a href="?lang=' + l + '">' + l + '</a> '
661         }
662     }
663     
664     $("#mkwsLang").html(data);
665 }
666
667 /* locale */
668 function M(word) {
669     var lang = mkws_config.lang;
670
671     if (!lang || !mkws_locale_lang[lang])
672         return word;
673
674     return mkws_locale_lang[lang][word] ? mkws_locale_lang[lang][word] : word;
675 }
676
677 /* implement jQuery.parseQuerystring() for parsing URL parameters */
678 jQuery.extend({
679     parseQuerystring: function() {
680     var nvpair = {};
681     var qs = window.location.search.replace('?', '');
682     var pairs = qs.split('&');
683     $.each(pairs, function(i, v){
684         var pair = v.split('=');
685         nvpair[pair[0]] = pair[1];
686     });
687     return nvpair;
688 } });
689
690 function debug(string) {
691     if (!mkws_debug)
692         return;
693
694     if (typeof console === "undefined" || typeof console.log === "undefined") { /* ARGH!!! */
695         return;
696     }
697
698     // you need to disable use strict at the top of the file!!!
699     if (mkws_debug >= 3) {
700         console.log(arguments.callee.caller);
701     } else if (mkws_debug >= 2) {
702         console.log(">>> called from function " + arguments.callee.caller.name + ' <<<');
703     }
704     console.log(string);
705 }
706
707 /* magic */
708 $(document).ready(function() { mkws_html_all(mkws_config) });