Move mkws.local_lang up into the main mkws object definition.
[mkws-moved-to-github.git] / tools / htdocs / mkws.js
1 /*! MKWS, the MasterKey Widget Set. Copyright (C) 2013-2014, Index Data */
2
3 "use strict"; // HTML5: disable for debug_level >= 2
4
5 // Some functions are visible to be called from outside code, namely
6 // generated HTML: mkws.switchView(), showDetails(), limitTarget(),
7 // limitQuery(), delimitTarget(), delimitQuery(), pagerPrev(),
8 // pagerNext(), showPage()
9
10 // Set up global mkws object. Contains a hash of session objects,
11 // indexed by windowid.
12 var mkws = {
13     authenticated: false,
14     init: false,
15     debug_function: undefined, // will be set during initialisation
16     debug_level: undefined, // will be initialised from mkws_config
17     sessions: {},
18     locale_lang: {
19         "de": {
20             "Authors": "Autoren",
21             "Subjects": "Schlagwörter",
22             "Sources": "Daten und Quellen",
23             "Termlists": "Termlisten",
24             "Next": "Weiter",
25             "Prev": "Zurü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             "Records": "Datensätze",
40             "Targets": "Datenbanken",
41
42             "dummy": "dummy"
43         },
44
45         "da": {
46             "Authors": "Forfattere",
47             "Subjects": "Emner",
48             "Sources": "Kilder",
49             "Termlists": "Termlists",
50             "Next": "Næste",
51             "Prev": "Forrige",
52             "Search": "Søg",
53             "Sort by": "Sorter efter",
54             "and show": "og vis",
55             "per page": "per side",
56             "Displaying": "Viser",
57             "to": "til",
58             "of": "ud af",
59             "found": "fandt",
60             "Title": "Title",
61             "Author": "Forfatter",
62             "Date": "Dato",
63             "Subject": "Emneord",
64             "Location": "Lokation",
65             "Records": "Poster",
66             "Targets": "Baser",
67
68             "dummy": "dummy"
69         }
70     }
71 };
72
73 // Define empty mkws_config for simple applications that don't define it.
74 if (mkws_config == null || typeof mkws_config != 'object') {
75     var mkws_config = {};
76 }
77
78 // wrapper for jQuery lib
79 function _make_mkws_team($, teamName) {
80     if (console && console.log)
81         console.log("run _make_mkws_team(" + (teamName ? teamName : "") + ")");
82
83     // call this function only once
84     if (mkws.init) {
85         alert("_make_mkws_team() called twice: how did that happen?!");
86         return;
87     }
88
89     var m_sort = 'relevance';
90     var m_filters = [];
91
92     // keep time state for debugging
93     var m_debug_time = {
94         "start": $.now(),
95         "last": $.now()
96     };
97
98     mkws.debug_function = function (string) {
99         if (!mkws.debug_level)
100             return;
101
102         if (typeof console === "undefined" || typeof console.log === "undefined") { /* ARGH!!! old IE */
103             return;
104         }
105
106         var now = $.now();
107         var timestamp = ((now - m_debug_time.start)/1000).toFixed(3) + " (+" + ((now - m_debug_time.last)/1000).toFixed(3) + ") "
108         m_debug_time.last = now;
109
110         // you need to disable use strict at the top of the file!!!
111         if (mkws.debug_level >= 3) {
112             console.log(timestamp + arguments.callee.caller);
113         } else if (mkws.debug_level >= 2) {
114             console.log(timestamp + ">>> called from function " + arguments.callee.caller.name + ' <<<');
115         }
116         console.log(timestamp + string);
117     }
118     var debug = mkws.debug_function; // local alias
119     debug("start running MKWS");
120
121
122     Handlebars.registerHelper('json', function(obj) {
123         return $.toJSON(obj);
124     });
125
126
127     Handlebars.registerHelper('translate', function(s) {
128         debug("translating '" + s + "'");
129         return M(s);
130     });
131
132
133     // We need {{attr '@name'}} because Handlebars can't parse {{@name}}
134     Handlebars.registerHelper('attr', function(attrName) {
135         return this[attrName];
136     });
137
138
139     /*
140      * Use as follows: {{#if-any NAME1 having="NAME2"}}
141      * Applicable when NAME1 is the name of an array
142      * The guarded code runs only if at least one element of the NAME1
143      * array has a subelement called NAME2.
144      */
145     Handlebars.registerHelper('if-any', function(items, options) {
146         var having = options.hash.having;
147         for (var i in items) {
148             var item = items[i]
149             if (!having || item[having]) {
150                 return options.fn(this);
151             }
152         }
153         return "";
154     });
155
156
157     Handlebars.registerHelper('first', function(items, options) {
158         var having = options.hash.having;
159         for (var i in items) {
160             var item = items[i]
161             if (!having || item[having]) {
162                 return options.fn(item);
163             }
164         }
165         return "";
166     });
167
168
169     Handlebars.registerHelper('commaList', function(items, options) {
170         var out = "";
171
172         for (var i in items) {
173             if (i > 0) out += ", ";
174             out += options.fn(items[i])
175         }
176
177         return out;
178     });
179
180
181     {
182
183         /* default mkws config */
184         var config_default = {
185             use_service_proxy: true,
186             pazpar2_url: "http://mkws.indexdata.com/service-proxy/",
187             service_proxy_auth: "http://mkws.indexdata.com/service-proxy-auth",
188             lang: "",
189             sort_options: [["relevance"], ["title:1", "title"], ["date:0", "newest"], ["date:1", "oldest"]],
190             perpage_options: [10, 20, 30, 50],
191             sort_default: "relevance",
192             perpage_default: 20,
193             query_width: 50,
194             show_lang: true,    /* show/hide language menu */
195             show_sort: true,    /* show/hide sort menu */
196             show_perpage: true,         /* show/hide perpage menu */
197             lang_options: [],   /* display languages links for given languages, [] for all */
198             facets: ["sources", "subjects", "authors"], /* display facets, in this order, [] for none */
199             responsive_design_width: undefined, /* a page with less pixel width considered as narrow */
200             debug_level: 1,     /* debug level for development: 0..2 */
201
202             dummy: "dummy"
203         };
204
205         /* set global debug_level flag early */
206         if (typeof mkws_config.debug_level !== 'undefined') {
207             mkws.debug_level = mkws_config.debug_level;
208         } else if (typeof config_default.debug_level !== 'undefined') {
209             mkws.debug_level = config_default.debug_level;
210         }
211
212         // make sure the mkws_config is a valid hash
213         if (!$.isPlainObject(mkws_config)) {
214             debug("ERROR: mkws_config is not an JS object, ignore it....");
215             mkws_config = {};
216         }
217
218         /* override standard config values by function parameters */
219         for (var k in config_default) {
220             if (typeof mkws_config[k] === 'undefined')
221                 mkws_config[k] = config_default[k];
222             debug("Set config: " + k + ' => ' + mkws_config[k]);
223         }
224     }
225
226
227     m_sort = mkws_config.sort_default;
228     debug("copied mkws_config.sort_default '" + mkws_config.sort_default + "' to m_sort");
229
230     if (mkws_config.query_width < 5 || mkws_config.query_width > 150) {
231         debug("Reset query width: " + mkws_config.query_width);
232         mkws_config.query_width = 50;
233     }
234
235     for (var key in mkws_config) {
236         if (mkws_config.hasOwnProperty(key)) {
237             if (key.match(/^language_/)) {
238                 var lang = key.replace(/^language_/, "");
239                 // Copy custom languages into list
240                 mkws.locale_lang[lang] = mkws_config[key];
241                 debug("Added locally configured language '" + lang + "'");
242             }
243         }
244     }
245
246     // protocol independend link for pazpar2: "//mkws/sp" -> "https://mkws/sp"
247     if (mkws_config.pazpar2_url.match(/^\/\//)) {
248         mkws_config.pazpar2_url = document.location.protocol + mkws_config.pazpar2_url;
249         debug("adjust protocol independend links: " + mkws_config.pazpar2_url);
250     }
251
252     debug("Create main pz2 object");
253     // create a parameters array and pass it to the pz2's constructor
254     // then register the form submit event with the pz2.search function
255     // autoInit is set to true on default
256     var m_paz = new pz2( { "onshow": my_onshow,
257                            "showtime": 500,            //each timer (show, stat, term, bytarget) can be specified this way
258                            "pazpar2path": mkws_config.pazpar2_url,
259                            "oninit": my_oninit,
260                            "onstat": my_onstat,
261                            "onterm": my_onterm,
262                            "termlist": "xtargets,subject,author",
263                            "onbytarget": my_onbytarget,
264                            "usesessions" : mkws_config.use_service_proxy ? false : true,
265                            "showResponseType": '', // or "json" (for debugging?)
266                            "onrecord": my_onrecord } );
267
268     // some state vars
269     var curPage = 1;
270     var recPerPage = 20;
271     var totalRec = 0;
272     var curDetRecId = '';
273     var curDetRecData = null;
274     var submitted = false;
275     var SourceMax = 16;
276     var SubjectMax = 10;
277     var AuthorMax = 10;
278     var m_query; // initially undefined
279
280     if (!isNaN(parseInt(mkws_config.perpage_default))) {
281         recPerPage = parseInt(mkws_config.perpage_default);
282     }
283
284     //
285     // pz2.js event handlers:
286     //
287     function my_oninit() {
288         m_paz.stat();
289         m_paz.bytarget();
290     }
291
292     function my_onshow(data) {
293         totalRec = data.merged;
294         // move it out
295         var pager = document.getElementById("mkwsPager");
296         if (pager) {
297             pager.innerHTML = "";
298             pager.innerHTML +='<div style="float: right">' + M('Displaying') + ': '
299                 + (data.start + 1) + ' ' + M('to') + ' ' + (data.start + data.num) +
300                 ' ' + M('of') + ' ' + data.merged + ' (' + M('found') + ': '
301                 + data.total + ')</div>';
302             drawPager(pager);
303         }
304
305         // navi
306         var results = document.getElementById("mkwsRecords");
307
308         var html = [];
309         for (var i = 0; i < data.hits.length; i++) {
310             var hit = data.hits[i];
311             html.push('<div class="record" id="mkwsRecdiv_' + hit.recid + '" >',
312                       renderSummary(hit),
313                       '</div>');
314             if (hit.recid == curDetRecId) {
315                 html.push(renderDetails(curDetRecData));
316             }
317         }
318         replaceHtml(results, html.join(''));
319     }
320
321
322     function renderSummary(hit)
323     {
324         var template = loadTemplate("Summary");
325         hit._id = "mkwsRec_" + hit.recid;
326         hit._onclick = "mkws.showDetails(this.id);return false;"
327         return template(hit);
328     }
329
330
331     function my_onstat(data) {
332         var stat = document.getElementById("mkwsStat");
333         if (stat == null)
334             return;
335
336         stat.innerHTML = '<span class="head">' + M('Status info') + '</span>' +
337             ' -- ' +
338             '<span class="clients">' + M('Active clients') + ': ' + data.activeclients + '/' + data.clients + '</span>' +
339             ' -- ' +
340             '<span class="records">' + M('Retrieved records') + ': ' + data.records + '/' + data.hits + '</span>';
341     }
342
343     function my_onterm(data) {
344         // no facets
345         if (!mkws_config.facets || mkws_config.facets.length == 0) {
346             $("#mkwsTermlists").hide();
347             return;
348         }
349
350         // display if we first got results
351         $("#mkwsTermlists").show();
352
353         var acc = [];
354         acc.push('<div class="title">' + M('Termlists') + '</div>');
355         var facets = mkws_config.facets;
356
357         for(var i = 0; i < facets.length; i++) {
358             if (facets[i] == "sources") {
359                 add_single_facet(acc, "Sources",  data.xtargets, SourceMax, null);
360             } else if (facets[i] == "subjects") {
361                 add_single_facet(acc, "Subjects", data.subject,  SubjectMax, "subject");
362             } else if (facets[i] == "authors") {
363                 add_single_facet(acc, "Authors",  data.author,   AuthorMax, "author");
364             } else {
365                 alert("bad facet configuration: '" + facets[i] + "'");
366             }
367         }
368
369         var termlist = document.getElementById("mkwsTermlists");
370         if (termlist)
371             replaceHtml(termlist, acc.join(''));
372     }
373
374     function add_single_facet(acc, caption, data, max, pzIndex) {
375         acc.push('<div class="facet" id="mkwsFacet' + caption + '">');
376         acc.push('<div class="termtitle">' + M(caption) + '</div>');
377         for (var i = 0; i < data.length && i < max; i++ ) {
378             acc.push('<div class="term">');
379             acc.push('<a href="#" ');
380             var action;
381             if (!pzIndex) {
382                 // Special case: target selection
383                 acc.push('target_id='+data[i].id+' ');
384                 action = 'mkws.limitTarget(this.getAttribute(\'target_id\'),this.firstChild.nodeValue)';
385             } else {
386                 action = 'mkws.limitQuery(\'' + pzIndex + '\', this.firstChild.nodeValue)';
387             }
388             acc.push('onclick="' + action + ';return false;">' + data[i].name + '</a>'
389                      + ' <span>' + data[i].freq + '</span>');
390             acc.push('</div>');
391         }
392         acc.push('</div>');
393     }
394
395     function my_onrecord(data) {
396         // FIXME: record is async!!
397         clearTimeout(m_paz.recordTimer);
398         // in case on_show was faster to redraw element
399         var detRecordDiv = document.getElementById('mkwsDet_'+data.recid);
400         if (detRecordDiv) return;
401         curDetRecData = data;
402         var recordDiv = document.getElementById('mkwsRecdiv_'+curDetRecData.recid);
403         var html = renderDetails(curDetRecData);
404         recordDiv.innerHTML += html;
405     }
406
407     function my_onbytarget(data) {
408         var targetDiv = document.getElementById("mkwsBytarget");
409         if (!targetDiv) {
410             // No mkwsTargets div.
411             return;
412         }
413
414         var table ='<table><thead><tr>' +
415             '<td>' + M('Target ID') + '</td>' +
416             '<td>' + M('Hits') + '</td>' +
417             '<td>' + M('Diags') + '</td>' +
418             '<td>' + M('Records') + '</td>' +
419             '<td>' + M('State') + '</td>' +
420             '</tr></thead><tbody>';
421
422         for (var i = 0; i < data.length; i++ ) {
423             table += "<tr><td>" + data[i].id +
424                 "</td><td>" + data[i].hits +
425                 "</td><td>" + data[i].diagnostic +
426                 "</td><td>" + data[i].records +
427                 "</td><td>" + data[i].state + "</td></tr>";
428         }
429
430         table += '</tbody></table>';
431         targetDiv.innerHTML = table;
432     }
433
434     ////////////////////////////////////////////////////////////////////////////////
435     ////////////////////////////////////////////////////////////////////////////////
436
437     // wait until the DOM is ready
438     function domReady ()
439     {
440         document.mkwsSearchForm.onsubmit = onFormSubmitEventHandler;
441         document.mkwsSearchForm.mkwsQuery.value = '';
442         if (document.mkwsSelect) {
443             if (document.mkwsSelect.mkwsSort)
444                 document.mkwsSelect.mkwsSort.onchange = onSelectDdChange;
445             if (document.mkwsSelect.mkwsPerpage)
446                 document.mkwsSelect.mkwsPerpage.onchange = onSelectDdChange;
447         }
448     }
449
450     // when search button pressed
451     function onFormSubmitEventHandler()
452     {
453         newSearch(document.mkwsSearchForm.mkwsQuery.value);
454         return false;
455     }
456
457     function newSearch(query, sort, targets, windowid)
458     {
459         debug("newSearch: " + query);
460
461         if (mkws_config.use_service_proxy && !mkws.authenticated) {
462             alert("searching before authentication");
463             return;
464         }
465
466         m_filters = []
467         redraw_navi(); // ### should use windowid
468         resetPage(); // ### the globals it resents should be indexed by windowid
469         loadSelect(); // ### should use windowid
470         triggerSearch(query, sort, targets, windowid);
471         mkws.switchView('records'); // In case it's configured to start off as hidden
472         submitted = true;
473     }
474
475     function onSelectDdChange()
476     {
477         if (!submitted) return false;
478         resetPage();
479         loadSelect();
480         m_paz.show(0, recPerPage, m_sort);
481         return false;
482     }
483
484     function resetPage()
485     {
486         curPage = 1;
487         totalRec = 0;
488     }
489
490     function triggerSearch (query, sort, targets, windowid)
491     {
492         var pp2filter = "";
493         var pp2limit = "";
494
495         // Re-use previous query/sort if new ones are not specified
496         if (query) {
497             m_query = query;
498         }
499         if (sort) {
500             m_sort = sort;
501         }
502         if (targets) {
503             // ### should support multiple |-separated targets
504             m_filters.push({ id: targets, name: targets });
505         }
506
507         for (var i in m_filters) {
508             var filter = m_filters[i];
509             if (filter.id) {
510                 if (pp2filter)
511                     pp2filter += ",";
512                 if (filter.id.match(/^[a-z:]+[=~]/)) {
513                     debug("filter '" + filter.id + "' already begins with SETTING OP");
514                 } else {
515                     filter.id = 'pz:id=' + filter.id;
516                 }
517                 pp2filter += filter.id;
518             } else {
519                 if (pp2limit)
520                     pp2limit += ",";
521                 pp2limit += filter.field + "=" + filter.value.replace(/[\\|,]/g, '\\$&');
522             }
523         }
524
525         var params = {};
526         if (pp2limit) {
527             params.limit = pp2limit;
528         }
529         if (windowid) {
530             params.windowid = windowid;
531         }
532         debug("triggerSearch(" + m_query + "): filters = " + $.toJSON(m_filters) + ", pp2filter = " + pp2filter + ", params = " + $.toJSON(params));
533
534         m_paz.search(m_query, recPerPage, m_sort, pp2filter, undefined, params);
535     }
536
537     function loadSelect ()
538     {
539         if (document.mkwsSelect) {
540             if (document.mkwsSelect.mkwsSort)
541                 m_sort = document.mkwsSelect.mkwsSort.value;
542             if (document.mkwsSelect.mkwsPerpage)
543                 recPerPage = document.mkwsSelect.mkwsPerpage.value;
544         }
545     }
546
547     // limit the query after clicking the facet
548     mkws.limitQuery = function (field, value)
549     {
550         debug("limitQuery(field=" + field + ", value=" + value + ")");
551         m_filters.push({ field: field, value: value });
552         redraw_navi();
553         resetPage();
554         loadSelect();
555         triggerSearch();
556         return false;
557     }
558
559     // limit by target functions
560     mkws.limitTarget  = function (id, name)
561     {
562         debug("limitTarget(id=" + id + ", name=" + name + ")");
563         m_filters.push({ id: id, name: name });
564         redraw_navi();
565         resetPage();
566         loadSelect();
567         triggerSearch();
568         return false;
569     }
570
571     mkws.delimitQuery = function (field, value)
572     {
573         debug("delimitQuery(field=" + field + ", value=" + value + ")");
574         var newFilters = [];
575         for (var i in m_filters) {
576             var filter = m_filters[i];
577             if (filter.field &&
578                 field == filter.field &&
579                 value == filter.value) {
580                 debug("delimitTarget() removing filter " + $.toJSON(filter));
581             } else {
582                 debug("delimitTarget() keeping filter " + $.toJSON(filter));
583                 newFilters.push(filter);
584             }
585         }
586         m_filters = newFilters;
587
588         redraw_navi();
589         resetPage();
590         loadSelect();
591         triggerSearch();
592         return false;
593     }
594
595
596     mkws.delimitTarget = function (id)
597     {
598         debug("delimitTarget(id=" + id + ")");
599         var newFilters = [];
600         for (var i in m_filters) {
601             var filter = m_filters[i];
602             if (filter.id) {
603                 debug("delimitTarget() removing filter " + $.toJSON(filter));
604             } else {
605                 debug("delimitTarget() keeping filter " + $.toJSON(filter));
606                 newFilters.push(filter);
607             }
608         }
609         m_filters = newFilters;
610
611         redraw_navi();
612         resetPage();
613         loadSelect();
614         triggerSearch();
615         return false;
616     }
617
618
619     function redraw_navi ()
620     {
621         var navi = document.getElementById('mkwsNavi');
622         if (!navi) return;
623
624         var text = "";
625         for (var i in m_filters) {
626             if (text) {
627                 text += " | ";
628             }
629             var filter = m_filters[i];
630             if (filter.id) {
631                 text += 'Source: <a class="crossout" href="#" onclick="mkws.delimitTarget(' +
632                     "'" + filter.id + "'" + ');return false;">' + filter.name + '</a>';
633             } else {
634                 text += filter.field + ': <a class="crossout" href="#" onclick="mkws.delimitQuery(' +
635                     "'" + filter.field + "', '" + filter.value + "'" +
636                     ');return false;">' + filter.value + '</a>';
637             }
638         }
639
640         navi.innerHTML = text;
641     }
642
643
644     function drawPager (pagerDiv)
645     {
646         //client indexes pages from 1 but pz2 from 0
647         var onsides = 6;
648         var pages = Math.ceil(totalRec / recPerPage);
649
650         var firstClkbl = ( curPage - onsides > 0 )
651             ? curPage - onsides
652             : 1;
653
654         var lastClkbl = firstClkbl + 2*onsides < pages
655             ? firstClkbl + 2*onsides
656             : pages;
657
658         var prev = '<span id="mkwsPrev">&#60;&#60; ' + M('Prev') + '</span><b> | </b>';
659         if (curPage > 1)
660             prev = '<a href="#" id="mkwsPrev" onclick="mkws.pagerPrev();">'
661             +'&#60;&#60; ' + M('Prev') + '</a><b> | </b>';
662
663         var middle = '';
664         for(var i = firstClkbl; i <= lastClkbl; i++) {
665             var numLabel = i;
666             if(i == curPage)
667                 numLabel = '<b>' + i + '</b>';
668
669             middle += '<a href="#" onclick="mkws.showPage(' + i + ')"> '
670                 + numLabel + ' </a>';
671         }
672
673         var next = '<b> | </b><span id="mkwsNext">' + M('Next') + ' &#62;&#62;</span>';
674         if (pages - curPage > 0)
675             next = '<b> | </b><a href="#" id="mkwsNext" onclick="mkws.pagerNext()">'
676             + M('Next') + ' &#62;&#62;</a>';
677
678         var predots = '';
679         if (firstClkbl > 1)
680             predots = '...';
681
682         var postdots = '';
683         if (lastClkbl < pages)
684             postdots = '...';
685
686         pagerDiv.innerHTML += '<div style="float: clear">'
687             + prev + predots + middle + postdots + next + '</div>';
688     }
689
690     mkws.showPage = function (pageNum)
691     {
692         curPage = pageNum;
693         m_paz.showPage( curPage - 1 );
694     }
695
696     // simple paging functions
697
698     mkws.pagerNext = function () {
699         if ( totalRec - recPerPage*curPage > 0) {
700             m_paz.showNext();
701             curPage++;
702         }
703     }
704
705     mkws.pagerPrev = function () {
706         if ( m_paz.showPrev() != false )
707             curPage--;
708     }
709
710     // switching view between targets and records
711
712     mkws.switchView = function(view) {
713         debug("switchView: " + view);
714
715         var targets = document.getElementById('mkwsTargets');
716         var results = document.getElementById('mkwsResults') ||
717             document.getElementById('mkwsRecords');
718         var blanket = document.getElementById('mkwsBlanket');
719         var motd    = document.getElementById('mkwsMOTD');
720
721         switch(view) {
722         case 'targets':
723             if (targets) targets.style.display = "block";
724             if (results) results.style.display = "none";
725             if (blanket) blanket.style.display = "none";
726             if (motd) motd.style.display = "none";
727             break;
728         case 'records':
729             if (targets) targets.style.display = "none";
730             if (results) results.style.display = "block";
731             if (blanket) blanket.style.display = "block";
732             if (motd) motd.style.display = "none";
733             break;
734         case 'none':
735             if (targets) targets.style.display = "none";
736             if (results) results.style.display = "none";
737             if (blanket) blanket.style.display = "none";
738             if (motd) motd.style.display = "none";
739             break;
740         default:
741             alert("Unknown view '" + view + "'");
742         }
743     }
744
745     // detailed record drawing
746     mkws.showDetails = function (prefixRecId) {
747         var recId = prefixRecId.replace('mkwsRec_', '');
748         var oldRecId = curDetRecId;
749         curDetRecId = recId;
750
751         // remove current detailed view if any
752         var detRecordDiv = document.getElementById('mkwsDet_'+oldRecId);
753         // lovin DOM!
754         if (detRecordDiv)
755             detRecordDiv.parentNode.removeChild(detRecordDiv);
756
757         // if the same clicked, just hide
758         if (recId == oldRecId) {
759             curDetRecId = '';
760             curDetRecData = null;
761             return;
762         }
763         // request the record
764         m_paz.record(recId);
765     }
766
767     function replaceHtml(el, html) {
768         var oldEl = typeof el === "string" ? document.getElementById(el) : el;
769         /*@cc_on // Pure innerHTML is slightly faster in IE
770           oldEl.innerHTML = html;
771           return oldEl;
772           @*/
773         var newEl = oldEl.cloneNode(false);
774         newEl.innerHTML = html;
775         oldEl.parentNode.replaceChild(newEl, oldEl);
776         /* Since we just removed the old element from the DOM, return a reference
777            to the new element, which can be used to restore variable references. */
778         return newEl;
779     };
780
781     function renderDetails(data, marker)
782     {
783         var template = loadTemplate("Record");
784         var details = template(data);
785         return '<div class="details" id="mkwsDet_' + data.recid + '">' + details + '</div>';
786     }
787
788
789     function loadTemplate(name)
790     {
791         var template = mkws['template' + name];
792
793         if (template === undefined) {
794             var source = $("#mkwsTemplate" + name).html();
795             if (!source) {
796                 source = defaultTemplate(name);
797             }
798
799             template = Handlebars.compile(source);
800             debug("compiled template '" + name + "'");
801             mkws['template' + name] = template;
802         }
803
804         return template;
805     }
806
807
808     function defaultTemplate(name)
809     {
810         if (name === 'Record') {
811             return '\
812 <table>\
813   <tr>\
814     <th>{{translate "Title"}}</th>\
815     <td>\
816       {{md-title}}\
817       {{#if md-title-remainder}}\
818         ({{md-title-remainder}})\
819       {{/if}}\
820       {{#if md-title-responsibility}}\
821         <i>{{md-title-responsibility}}</i>\
822       {{/if}}\
823     </td>\
824   </tr>\
825   {{#if md-date}}\
826   <tr>\
827     <th>{{translate "Date"}}</th>\
828     <td>{{md-date}}</td>\
829   </tr>\
830   {{/if}}\
831   {{#if md-author}}\
832   <tr>\
833     <th>{{translate "Author"}}</th>\
834     <td>{{md-author}}</td>\
835   </tr>\
836   {{/if}}\
837   {{#if md-electronic-url}}\
838   <tr>\
839     <th>{{translate "URL"}}</th>\
840     <td>\
841       {{#each md-electronic-url}}\
842         <a href="{{this}}">{{this}}</a><br/>\
843       {{/each}}\
844     </td>\
845   </tr>\
846   {{/if}}\
847   {{#if-any location having="md-subject"}}\
848   <tr>\
849     <th>{{translate "Subject"}}</th>\
850     <td>\
851       {{#first location having="md-subject"}}\
852         {{#if md-subject}}\
853           {{md-subject}}\
854         {{/if}}\
855       {{/first}}\
856     </td>\
857   </tr>\
858   {{/if-any}}\
859   <tr>\
860     <th>{{translate "Locations"}}</th>\
861     <td>\
862       {{#commaList location}}\
863         {{attr "@name"}}{{/commaList}}\
864     </td>\
865   </tr>\
866 </table>\
867 ';
868         } else if (name === "Summary") {
869             return '\
870 <a href="#" id="{{_id}}" onclick="{{_onclick}}">\
871   <b>{{md-title}}</b>\
872 </a>\
873 {{#if md-title-remainder}}\
874   <span>{{md-title-remainder}}</span>\
875 {{/if}}\
876 {{#if md-title-responsibility}}\
877   <span><i>{{md-title-responsibility}}</i></span>\
878 {{/if}}\
879 ';
880         }
881
882         var s = "There is no default '" + name +"' template!";
883         alert(s);
884         return s;
885     }
886
887
888     /*
889      * All the HTML stuff to render the search forms and
890      * result pages.
891      */
892     function mkws_html_all() {
893         mkws_set_lang();
894         if (mkws_config.show_lang)
895             mkws_html_lang();
896
897         // For some reason, doing this programmatically results in
898         // document.mkwsSearchForm.mkwsQuery being undefined, hence the raw HTML.
899         debug("HTML search form");
900         $("#mkwsSearch").html('\
901 <form name="mkwsSearchForm" action="" >\
902   <input id="mkwsQuery" type="text" size="' + mkws_config.query_width + '" />\
903   <input id="mkwsButton" type="submit" value="' + M('Search') + '" />\
904 </form>');
905
906         debug("HTML records");
907         // If the application has an #mkwsResults, populate it in the
908         // usual way. If not, assume that it's a smarter application that
909         // defines its own subcomponents:
910         //      #mkwsTermlists
911         //      #mkwsRanking
912         //      #mkwsPager
913         //      #mkwsNavi
914         //      #mkwsRecords
915         if ($("#mkwsResults").length) {
916             $("#mkwsResults").html('\
917 <table width="100%" border="0" cellpadding="6" cellspacing="0">\
918   <tr>\
919     <td id="mkwsTermlistContainer1" width="250" valign="top">\
920       <div id="mkwsTermlists"></div>\
921     </td>\
922     <td id="mkwsMOTDContainer" valign="top">\
923       <div id="mkwsRanking"></div>\
924       <div id="mkwsPager"></div>\
925       <div id="mkwsNavi"></div>\
926       <div id="mkwsRecords"></div>\
927     </td>\
928   </tr>\
929   <tr>\
930     <td colspan="2">\
931       <div id="mkwsTermlistContainer2"></div>\
932     </td>\
933   </tr>\
934 </table>');
935         }
936
937         if ($("#mkwsRanking").length) {
938             var ranking_data = '';
939             ranking_data += '<form name="mkwsSelect" id="mkwsSelect" action="" >';
940             if (mkws_config.show_sort) {
941                 ranking_data +=  M('Sort by') + ' ' + mkws_html_sort() + ' ';
942             }
943             if (mkws_config.show_perpage) {
944                 ranking_data += M('and show') + ' ' + mkws_html_perpage() + ' ' + M('per page') + '.';
945             }
946             ranking_data += '</form>';
947
948             $("#mkwsRanking").html(ranking_data);
949         }
950
951         mkws_html_switch();
952
953         if (mkws_config.use_service_proxy) {
954             mkws_service_proxy_auth(mkws_config.service_proxy_auth,
955                                     mkws_config.service_proxy_auth_domain,
956                                     mkws_config.pazpar2_url);
957         } else {
958             // raw pp2
959             run_auto_searches();
960         }
961
962         if (mkws_config.responsive_design_width) {
963             // Responsive web design - change layout on the fly based on
964             // current screen width. Required for mobile devices.
965             $(window).resize( function(e) { mkws_resize_page() });
966             // initial check after page load
967             $(document).ready(function() { mkws_resize_page() });
968         }
969
970         domReady();
971
972         // on first page, hide the termlist
973         $(document).ready(function() { $("#mkwsTermlists").hide(); } );
974         var motd = document.getElementById("mkwsMOTD");
975         var container = document.getElementById("mkwsMOTDContainer");
976         if (motd && container) {
977             // Move the MOTD from the provided element down into the container
978             motd.parentNode.removeChild(motd);
979             container.appendChild(motd);
980         }
981     }
982
983
984     function run_auto_searches() {
985         debug("running auto searches");
986
987         $('[id^="mkwsRecords"]').each(function () {
988             var node = $(this);
989             var query = node.attr('autosearch');
990
991             if (query) {
992                 var windowid = undefined;
993                 var id = node.attr('id');
994                 if (id.match(/^mkwsRecords_/, '')) {
995                     windowid = id.replace(/^mkwsRecords_/, '');
996                 }
997
998                 var sort = node.attr('sort');
999                 var targets = node.attr('targets');
1000                 var s = "running auto search: '" + query + "'";
1001                 if (windowid) s += " [windowid '" + windowid + "']";
1002                 if (sort) s += " sorted by '" + sort + "'";
1003                 if (targets) s += " in targets '" + targets + "'";
1004                 debug(s);
1005                 newSearch(query, sort, targets, windowid);
1006             }
1007         });
1008     }
1009
1010
1011     // implement $.parseQuerystring() for parsing URL parameters
1012     function parseQuerystring() {
1013         var nvpair = {};
1014         var qs = window.location.search.replace('?', '');
1015         var pairs = qs.split('&');
1016         $.each(pairs, function(i, v){
1017             var pair = v.split('=');
1018             nvpair[pair[0]] = pair[1];
1019         });
1020         return nvpair;
1021     }
1022
1023     function mkws_set_lang()  {
1024         var lang = parseQuerystring().lang || mkws_config.lang;
1025         if (!lang || !mkws.locale_lang[lang]) {
1026             mkws_config.lang = ""
1027         } else {
1028             mkws_config.lang = lang;
1029         }
1030
1031         debug("Locale language: " + (mkws_config.lang ? mkws_config.lang : "none"));
1032         return mkws_config.lang;
1033     }
1034
1035     function mkws_html_switch() {
1036         debug("HTML switch");
1037
1038         $("#mkwsSwitch").append($('<a href="#" id="mkwsSwitch_records" onclick="mkws.switchView(\'records\')">' + M('Records') + '</a>'));
1039         $("#mkwsSwitch").append($("<span/>", { text: " | " }));
1040         $("#mkwsSwitch").append($('<a href="#" id="mkwsSwitch_targets" onclick="mkws.switchView(\'targets\')">' + M('Targets') + '</a>'));
1041
1042         debug("HTML targets");
1043         $("#mkwsTargets").html('\
1044 <div id="mkwsBytarget">\
1045   No information available yet.\
1046 </div>');
1047         $("#mkwsTargets").css("display", "none");
1048     }
1049
1050     function mkws_html_sort() {
1051         debug("HTML sort, m_sort = '" + m_sort + "'");
1052         var sort_html = '<select name="mkwsSort" id="mkwsSort">';
1053
1054         for(var i = 0; i < mkws_config.sort_options.length; i++) {
1055             var opt = mkws_config.sort_options[i];
1056             var key = opt[0];
1057             var val = opt.length == 1 ? opt[0] : opt[1];
1058
1059             sort_html += '<option value="' + key + '"';
1060             if (m_sort == key || m_sort == val) {
1061                 sort_html += ' selected="selected"';
1062             }
1063             sort_html += '>' + M(val) + '</option>';
1064         }
1065         sort_html += '</select>';
1066
1067         return sort_html;
1068     }
1069
1070     function mkws_html_perpage() {
1071         debug("HTML perpage");
1072         var perpage_html = '<select name="mkwsPerpage" id="mkwsPerpage">';
1073
1074         for(var i = 0; i < mkws_config.perpage_options.length; i++) {
1075             var key = mkws_config.perpage_options[i];
1076
1077             perpage_html += '<option value="' + key + '"';
1078             if (key == mkws_config.perpage_default) {
1079                 perpage_html += ' selected="selected"';
1080             }
1081             perpage_html += '>' + key + '</option>';
1082         }
1083         perpage_html += '</select>';
1084
1085         return perpage_html;
1086     }
1087
1088     /*
1089      * Run service-proxy authentication in background (after page load).
1090      * The username/password is configured in the apache config file
1091      * for the site.
1092      */
1093     function mkws_service_proxy_auth(auth_url, auth_domain, pp2_url) {
1094         debug("Run service proxy auth URL: " + auth_url);
1095
1096         if (!auth_domain) {
1097             auth_domain = pp2_url.replace(/^(https?:)?\/\/(.*?)\/.*/, '$2');
1098             debug("guessed auth_domain '" + auth_domain + "' from pp2_url '" + pp2_url + "'");
1099         }
1100
1101         var request = new pzHttpRequest(auth_url, function(err) {
1102             alert("HTTP call for authentication failed: " + err)
1103             return;
1104         }, auth_domain);
1105
1106         request.get(null, function(data) {
1107             if (!$.isXMLDoc(data)) {
1108                 alert("service proxy auth response document is not valid XML document, give up!");
1109                 return;
1110             }
1111             var status = $(data).find("status");
1112             if (status.text() != "OK") {
1113                 alert("service proxy auth repsonse status: " + status.text() + ", give up!");
1114                 return;
1115             }
1116
1117             debug("Service proxy auth successfully done");
1118             mkws.authenticated = true;
1119             run_auto_searches();
1120         });
1121     }
1122
1123     /* create locale language menu */
1124     function mkws_html_lang() {
1125         var lang_default = "en";
1126         var lang = mkws_config.lang || lang_default;
1127         var list = [];
1128
1129         /* display a list of configured languages, or all */
1130         var lang_options = mkws_config.lang_options || [];
1131         var hash = {};
1132         for (var i = 0; i < lang_options.length; i++) {
1133             hash[lang_options[i]] = 1;
1134         }
1135
1136         for (var k in mkws.locale_lang) {
1137             if (hash[k] == 1 || lang_options.length == 0)
1138                 list.push(k);
1139         }
1140
1141         // add english link
1142         if (lang_options.length == 0 || hash[lang_default] == 1)
1143             list.push(lang_default);
1144
1145         debug("Language menu for: " + list.join(", "));
1146
1147         /* the HTML part */
1148         var data = "";
1149         for(var i = 0; i < list.length; i++) {
1150             var l = list[i];
1151
1152             if (data)
1153                 data += ' | ';
1154
1155             if (lang == l) {
1156                 data += ' <span>' + l + '</span> ';
1157             } else {
1158                 data += ' <a href="?lang=' + l + '">' + l + '</a> '
1159             }
1160         }
1161
1162         $("#mkwsLang").html(data);
1163     }
1164
1165     function mkws_resize_page () {
1166         var list = ["mkwsSwitch"];
1167
1168         var width = mkws_config.responsive_design_width;
1169         var parentId = $("#mkwsTermlists").parent().attr('id');
1170
1171         if ($(window).width() <= width &&
1172             parentId === "mkwsTermlistContainer1") {
1173             debug("changing from wide to narrow: " + $(window).width());
1174             $("#mkwsTermlists").appendTo($("#mkwsTermlistContainer2"));
1175             $("#mkwsTermlistContainer1").hide();
1176             $("#mkwsTermlistContainer2").show();
1177             for(var i = 0; i < list.length; i++) {
1178                 $("#" + list[i]).hide();
1179             }
1180         } else if ($(window).width() > width &&
1181                    parentId === "mkwsTermlistContainer2") {
1182             debug("changing from narrow to wide: " + $(window).width());
1183             $("#mkwsTermlists").appendTo($("#mkwsTermlistContainer1"));
1184             $("#mkwsTermlistContainer1").show();
1185             $("#mkwsTermlistContainer2").hide();
1186             for(var i = 0; i < list.length; i++) {
1187                 $("#" + list[i]).show();
1188             }
1189         }
1190     };
1191
1192     /* locale */
1193     function M(word) {
1194         var lang = mkws_config.lang;
1195
1196         if (!lang || !mkws.locale_lang[lang])
1197             return word;
1198
1199         return mkws.locale_lang[lang][word] || word;
1200     }
1201
1202     // main
1203     (function() {
1204         try {
1205             mkws_html_all()
1206         }
1207
1208         catch (e) {
1209             mkws_config.error = e.message;
1210             // alert(e.message);
1211         }
1212     })();
1213
1214     // done
1215     mkws.init = true;
1216 };
1217
1218
1219 /*
1220  * implement jQuery plugin $.pazpar2({})
1221  */
1222 function _mkws_jquery_plugin ($) {
1223     var debug_level = 1;
1224
1225     function debug (string) {
1226         if (!debug_level)
1227             return;
1228
1229         if (typeof console === "undefined" || typeof console.log === "undefined")
1230             return;
1231
1232         console.log("jquery.pazpar2: " + string);
1233     }
1234
1235     function init_popup(obj) {
1236         var config = obj ? obj : {};
1237
1238         var height = config.height || 760;
1239         var width = config.width || 880;
1240         var id_button = config.id_button || "input#mkwsButton";
1241         var id_popup = config.id_popup || "#mkwsPopup";
1242
1243         debug("popup height: " + height + ", width: " + width);
1244
1245         // make sure that jquery-ui was loaded afte jQuery core lib, e.g.:
1246         // <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
1247         if (!$.ui) {
1248             debug("Error: jquery-ui.js is missing, did you include it after jQuery core in the HTML file?");
1249             return;
1250         }
1251
1252         $(id_popup).dialog({
1253             closeOnEscape: true,
1254             autoOpen: false,
1255             height: height,
1256             width: width,
1257             modal: true,
1258             resizable: true,
1259             buttons: {
1260                 Cancel: function() {
1261                     $(this).dialog("close");
1262                 }
1263             },
1264             close: function() { }
1265         });
1266
1267         $(id_button)
1268             .button()
1269             .click(function() {
1270                 $(id_popup).dialog("open");
1271             });
1272     };
1273
1274     $.extend({
1275
1276         // service-proxy or pazpar2
1277         pazpar2: function(config) {
1278             var id_popup = config.id_popup || "#mkwsPopup";
1279             id_popup = id_popup.replace(/^#/, "");
1280
1281             // simple layout
1282             var div = '\
1283 <div id="mkwsSwitch"></div>\
1284 <div id="mkwsLang"></div>\
1285 <div id="mkwsSearch"></div>\
1286 <div id="mkwsResults"></div>\
1287 <div id="mkwsTargets"></div>\
1288 <div id="mkwsStat"></div>';
1289
1290             // new table layout
1291             var table = '\
1292 <style type="text/css">\
1293   #mkwsTermlists div.facet {\
1294   float:left;\
1295   width: 30%;\
1296   margin: 0.3em;\
1297   }\
1298   #mkwsStat {\
1299   text-align: right;\
1300   }\
1301 </style>\
1302     \
1303 <table width="100%" border="0">\
1304   <tr>\
1305     <td>\
1306       <div id="mkwsSwitch"></div>\
1307       <div id="mkwsLang"></div>\
1308       <div id="mkwsSearch"></div>\
1309     </td>\
1310   </tr>\
1311   <tr>\
1312     <td>\
1313       <div style="height:500px; overflow: auto">\
1314         <div id="mkwsPager"></div>\
1315         <div id="mkwsNavi"></div>\
1316         <div id="mkwsRecords"></div>\
1317         <div id="mkwsTargets"></div>\
1318         <div id="mkwsRanking"></div>\
1319       </div>\
1320     </td>\
1321   </tr>\
1322   <tr>\
1323     <td>\
1324       <div style="height:300px; overflow: hidden">\
1325         <div id="mkwsTermlists"></div>\
1326       </div>\
1327     </td>\
1328   </tr>\
1329   <tr>\
1330     <td>\
1331       <div id="mkwsStat"></div>\
1332     </td>\
1333   </tr>\
1334 </table>';
1335
1336             var popup = '\
1337 <div id="mkwsSearch"></div>\
1338 <div id="' + id_popup + '">\
1339   <div id="mkwsSwitch"></div>\
1340   <div id="mkwsLang"></div>\
1341   <div id="mkwsResults"></div>\
1342   <div id="mkwsTargets"></div>\
1343   <div id="mkwsStat"></div>\
1344 </div>'
1345
1346             if (config && config.layout == 'div') {
1347                 debug("jquery plugin layout: div");
1348                 document.write(div);
1349             } else if (config && config.layout == 'popup') {
1350                 debug("jquery plugin layout: popup with id: " + id_popup);
1351                 document.write(popup);
1352                 $(document).ready( function() { init_popup(config); } );
1353             } else {
1354                 debug("jquery plugin layout: table");
1355                 document.write(table);
1356             }
1357         }
1358     });
1359 };
1360
1361
1362 // wrapper to call _make_mkws_team() after page load
1363 (function (j) {
1364     // enable before page load, so we could call it before mkws() runs
1365     _mkws_jquery_plugin(j);
1366
1367     $(document).ready(function() {
1368         // if (console && console.log) console.log("on load ready");
1369         _make_mkws_team(j, null);
1370     });
1371 })(jQuery);