Merge branch 'master' of ssh://git.indexdata.com:222/home/git/private/mkws
[mkws-moved-to-github.git] / tools / htdocs / mkws.js
index 84078ea..1292b5f 100644 (file)
@@ -65,13 +65,19 @@ Handlebars.registerHelper('commaList', function(items, options) {
 });
 
 
+Handlebars.registerHelper('index1', function(obj) {
+    return obj.data.index + 1;
+});
+
+
 
 // Set up global mkws object. Contains truly global state such as SP
 // authentication, and a hash of team objects, indexed by windowid.
 //
 var mkws = {
     authenticated: false,
-    debug_level: undefined, // will be initialised from mkws_config
+    debug_level: 1, // Will be overridden from mkws_config, but
+                   // initial value allows jQuery popup to use logging.
     paz: undefined, // will be set up during initialisation
     teams: {},
     locale_lang: {
@@ -136,12 +142,50 @@ var mkws = {
 };
 
 
+// The following PubSub code is modified from the jQuery manual:
+// https://api.jquery.com/jQuery.Callbacks/
+//
+// Use as:
+//     mkws.queue("eventName").subscribe(function(param1, param2 ...) { ... });
+//     mkws.queue("eventName").publish(arg1, arg2, ...);
+
+(function() {
+  var queues = {};
+  mkws.queue = function(id) {
+    if (!queues[id]) {
+      var callbacks = $.Callbacks();
+      queues[id] = {
+       publish: callbacks.fire,
+       subscribe: callbacks.add,
+       unsubscribe: callbacks.remove
+      };
+    }
+    return queues[id];
+  }
+}());
+
+
 // Define empty mkws_config for simple applications that don't define it.
 if (mkws_config == null || typeof mkws_config != 'object') {
     var mkws_config = {};
 }
 
 
+// Factory function for widget objects.
+function widget($, team, type, node) {
+    var that = {
+       team: team,
+       type: type,
+       node: node
+    };
+
+    // ### More to do here, surely: e.g. wiring into the team
+    mkws.debug("made widget(team=" + team + ", type=" + type + ", node=" + node);
+
+    return that;
+}
+
+
 // Factory function for team objects. As much as possible, this uses
 // only member variables (prefixed "m_") and inner functions with
 // private scope. Some functions are visibl as member-functions to be
@@ -168,6 +212,7 @@ function team($, teamName) {
        "last": $.now()
     };
     var m_paz; // will be initialised below
+    var m_template = {};
 
 
     var debug = function (s) {
@@ -253,7 +298,7 @@ function team($, teamName) {
        var html = [];
        for (var i = 0; i < data.hits.length; i++) {
             var hit = data.hits[i];
-           html.push('<div class="record" id="mkwsRecdiv_' + hit.recid + '" >',
+           html.push('<div class="record" id="mkwsRecdiv_' + teamName + '_' + hit.recid + '" >',
                      renderSummary(hit),
                      '</div>');
            if (hit.recid == m_curDetRecId) {
@@ -329,11 +374,13 @@ function team($, teamName) {
        for (var i = 0; i < data.length && i < max; i++) {
            acc.push('<div class="term">');
             acc.push('<a href="#" ');
-           var action;
+           var action = '';
            if (!pzIndex) {
                // Special case: target selection
                acc.push('target_id='+data[i].id+' ');
-               action = 'mkws.limitTarget(\'' + m_teamName + '\', this.getAttribute(\'target_id\'),this.firstChild.nodeValue)';
+               if (!target_filtered(data[i].id)) {
+                   action = 'mkws.limitTarget(\'' + m_teamName + '\', this.getAttribute(\'target_id\'),this.firstChild.nodeValue)';
+               }
            } else {
                action = 'mkws.limitQuery(\'' + m_teamName + '\', \'' + pzIndex + '\', this.firstChild.nodeValue)';
            }
@@ -345,15 +392,26 @@ function team($, teamName) {
     }
 
 
+    function target_filtered(id) {
+       for (var i = 0; i < m_filters.length; i++) {
+           if (m_filters[i].id === id ||
+               m_filters[i].id === 'pz:id=' + id) {
+               return true;
+           }
+       }
+       return false;
+    }
+
+
     function my_onrecord(data, args, teamName) {
-       debug("record");
+       debug("record: teamName=" + teamName + ", m_teamName=" + m_teamName);
        // FIXME: record is async!!
        clearTimeout(m_paz.recordTimer);
        // in case on_show was faster to redraw element
-       var detRecordDiv = document.getElementById('mkwsDet_'+data.recid);
+       var detRecordDiv = document.getElementById('mkwsDet_' + teamName + '_' + data.recid);
        if (detRecordDiv) return;
        m_curDetRecData = data;
-       var recordDiv = document.getElementById('mkwsRecdiv_'+m_curDetRecData.recid);
+       var recordDiv = document.getElementById('mkwsRecdiv_' + teamName + '_' + m_curDetRecData.recid);
        var html = renderDetails(m_curDetRecData);
        recordDiv.innerHTML += html;
     }
@@ -482,6 +540,8 @@ function team($, teamName) {
        }
        debug("triggerSearch(" + m_query + "): filters = " + $.toJSON(m_filters) + ", pp2filter = " + pp2filter + ", params = " + $.toJSON(params));
 
+       // We can use: params.torusquery = "udb=NAME"
+       // Note: that won't work when running against raw pazpar2
        m_paz.search(m_query, m_perpage, m_sort, pp2filter, undefined, params);
     }
 
@@ -716,7 +776,7 @@ function team($, teamName) {
        m_curDetRecId = recId;
 
        // remove current detailed view if any
-       var detRecordDiv = document.getElementById('mkwsDet_'+oldRecId);
+       var detRecordDiv = document.getElementById('mkwsDet_' + m_teamName + '_' + oldRecId);
        // lovin DOM!
        if (detRecordDiv)
            detRecordDiv.parentNode.removeChild(detRecordDiv);
@@ -737,23 +797,29 @@ function team($, teamName) {
     {
        var template = loadTemplate("Record");
        var details = template(data);
-       return '<div class="details" id="mkwsDet_' + data.recid + '">' + details + '</div>';
+       return '<div class="details" id="mkwsDet_' + m_teamName + '_' + data.recid + '">' + details + '</div>';
     }
 
 
     function loadTemplate(name)
     {
-       var template = mkws['template' + name];
+       var template = m_template[name];
 
        if (template === undefined) {
-           var source = $("#mkwsTemplate" + name).html();
+           // Fall back to generic template if there is no team-specific one
+           var node = $(".mkwsTemplate_" + name + ".mkwsTeam_" + m_teamName)
+           if (!node.length) {
+               node = $(".mkwsTemplate_" + name + ".mkwsTeam_ALL")
+           }
+
+           var source = node.html();
            if (!source) {
                source = defaultTemplate(name);
            }
 
            template = Handlebars.compile(source);
            debug("compiled template '" + name + "'");
-           mkws['template' + name] = template;
+           m_template[name] = template;
        }
 
        return template;
@@ -791,10 +857,10 @@ function team($, teamName) {
   {{/if}}\
   {{#if md-electronic-url}}\
   <tr>\
-    <th>{{translate "URL"}}</th>\
+    <th>{{translate "Links"}}</th>\
     <td>\
       {{#each md-electronic-url}}\
-       <a href="{{this}}">{{this}}</a><br/>\
+       <a href="{{this}}">Link{{index1}}</a>\
       {{/each}}\
     </td>\
   </tr>\
@@ -805,7 +871,8 @@ function team($, teamName) {
     <td>\
       {{#first location having="md-subject"}}\
        {{#if md-subject}}\
-         {{md-subject}}\
+         {{#commaList md-subject}}\
+           {{this}}{{/commaList}}\
        {{/if}}\
       {{/first}}\
     </td>\
@@ -849,19 +916,14 @@ function team($, teamName) {
        if (mkws_config.show_lang)
            mkws_html_lang();
 
-       // For some reason, doing this programmatically results in
-       // document.mkwsSearchForm.mkwsQuery being undefined, hence the raw HTML.
        debug("HTML search form");
-       // ### There is only one match here by design: fix not to bother looping
-       $('.mkwsSearch.mkwsTeam_' + m_teamName).each(function (i, obj) {
-           var node = this;
-           mkws.handle_node_with_team(node, function(tname) {
-               $(node).html('\
+       mkws.handle_node_with_team($('.mkwsSearch.mkwsTeam_' + m_teamName),
+                                  function(tname) {
+           this.html('\
 <form name="mkwsSearchForm" class="mkwsSearchForm mkwsTeam_' + tname + '" action="" >\
-  <input id="mkwsQuery" class="mkwsQuery mkwsTeam_' + tname + '" type="text" size="' + mkws_config.query_width + '" />\
-  <input id="mkwsButton" class="mkwsButton mkwsTeam_' + tname + '" type="submit" value="' + M('Search') + '" />\
+  <input class="mkwsQuery mkwsTeam_' + tname + '" type="text" size="' + mkws_config.query_width + '" />\
+  <input class="mkwsButton mkwsTeam_' + tname + '" type="submit" value="' + M('Search') + '" />\
 </form>');
-           });
        });
 
        debug("HTML records");
@@ -879,13 +941,13 @@ function team($, teamName) {
 <table width="100%" border="0" cellpadding="6" cellspacing="0">\
   <tr>\
     <td class="mkwsTermlistContainer1 mkwsTeam_' + m_teamName + '" width="250" valign="top">\
-      <div id="mkwsTermlists" class="mkwsTermlists mkwsTeam_' + m_teamName + '"></div>\
+      <div class="mkwsTermlists mkwsTeam_' + m_teamName + '"></div>\
     </td>\
     <td class="mkwsMOTDContainer mkwsTeam_' + m_teamName + '" valign="top">\
-      <div id="mkwsRanking" class="mkwsRanking mkwsTeam_' + m_teamName + '"></div>\
-      <div id="mkwsPager" class="mkwsPager mkwsTeam_' + m_teamName + '"></div>\
-      <div id="mkwsNavi" class="mkwsNavi mkwsTeam_' + m_teamName + '"></div>\
-      <div id="mkwsRecords" class="mkwsRecords mkwsTeam_' + m_teamName + '"></div>\
+      <div class="mkwsRanking mkwsTeam_' + m_teamName + '"></div>\
+      <div class="mkwsPager mkwsTeam_' + m_teamName + '"></div>\
+      <div class="mkwsNavi mkwsTeam_' + m_teamName + '"></div>\
+      <div class="mkwsRecords mkwsTeam_' + m_teamName + '"></div>\
     </td>\
   </tr>\
   <tr>\
@@ -922,15 +984,10 @@ function team($, teamName) {
            $(document).ready(function() { mkws.resize_page() });
        }
 
-       $('.mkwsSearchForm.mkwsTeam_' + m_teamName).each(function (i, obj) {
-           debug("adding search-forms for team '" + m_teamName + "'");
-           var node = this;
-           mkws.handle_node_with_team(node, function(tname) {
-               debug("adding search-form '" + tname + "' for team '" + m_teamName + "'");
-               $(node).submit(onFormSubmitEventHandler);
-           });
-       });
-
+       var node;
+       node = $('.mkwsSearchForm.mkwsTeam_' + m_teamName);
+       if (node.length)
+           node.submit(onFormSubmitEventHandler);
        node = $('.mkwsSort.mkwsTeam_' + m_teamName);
        if (node.length)
            node.change(onSelectDdChange);
@@ -941,7 +998,7 @@ function team($, teamName) {
        // on first page, hide the termlist
        $(document).ready(function() { $(".mkwsTermlists.mkwsTeam_" + m_teamName).hide(); });
        var motd = $(".mkwsMOTD.mkwsTeam_" + m_teamName);
-       var container = $(".mkwsMOTDContainer.mkwsTeam_" + m_teamName);
+        var container = $(".mkwsMOTDContainer.mkwsTeam_" + m_teamName);
        if (motd.length && container.length) {
            // Move the MOTD from the provided element down into the container
            motd.appendTo(container);
@@ -1076,6 +1133,53 @@ function team($, teamName) {
     }
 
 
+    that.run_auto_search = function() {
+       // ### should check mkwsTermlist as well, for facet-only teams
+       var node = $('.mkwsRecords.mkwsTeam_' + m_teamName);
+       var query = node.attr('autosearch');
+       if (!query)
+           return;
+
+       if (query.match(/^!param!/)) {
+           var param = query.replace(/^!param!/, '');
+           query = getParameterByName(param);
+           debug("obtained query '" + query + "' from param '" + param + "'");
+           if (!query) {
+               alert("This page has a MasterKey widget that needs a query specified by the '" + param + "' parameter");
+           }
+       } else if (query.match(/^!path!/)) {
+           var index = query.replace(/^!path!/, '');
+           var path = window.location.pathname.split('/');
+           query = path[path.length - index];
+           debug("obtained query '" + query + "' from path-component '" + index + "'");
+           if (!query) {
+               alert("This page has a MasterKey widget that needs a query specified by the path-component " + index);
+           }
+       }
+
+       debug("node=" + node + ", class='" + node.className + "', query=" + query);
+
+       var sort = node.attr('sort');
+       var targets = node.attr('targets');
+       var s = "running auto search: '" + query + "'";
+       if (sort) s += " sorted by '" + sort + "'";
+       if (targets) s += " in targets '" + targets + "'";
+       debug(s);
+
+       this.newSearch(query, sort, targets, m_teamName);
+    }
+
+
+    // This function is taken from a StackOverflow answer
+    // http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript/901144#901144
+    function getParameterByName(name) {
+       name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
+       var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
+           results = regex.exec(location.search);
+       return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
+    }
+
+
     /* locale */
     function M(word) {
        var lang = mkws_config.lang;
@@ -1106,141 +1210,6 @@ function team($, teamName) {
 };
 
 
-/*
- * implement jQuery plugin $.pazpar2({})
- */
-function _mkws_jquery_plugin ($) {
-    function debug (string) {
-       mkws.debug("jquery.pazpar2: " + string);
-    }
-
-    function init_popup(obj) {
-       var config = obj ? obj : {};
-
-       var height = config.height || 760;
-       var width = config.width || 880;
-       var id_button = config.id_button || "input#mkwsButton";
-       var id_popup = config.id_popup || "#mkwsPopup";
-
-       debug("popup height: " + height + ", width: " + width);
-
-       // make sure that jquery-ui was loaded afte jQuery core lib, e.g.:
-       // <script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
-       if (!$.ui) {
-           debug("Error: jquery-ui.js is missing, did you include it after jQuery core in the HTML file?");
-           return;
-       }
-
-       $(id_popup).dialog({
-           closeOnEscape: true,
-           autoOpen: false,
-           height: height,
-           width: width,
-           modal: true,
-           resizable: true,
-           buttons: {
-               Cancel: function() {
-                   $(this).dialog("close");
-               }
-           },
-           close: function() { }
-       });
-
-       $(id_button)
-           .button()
-           .click(function() {
-               $(id_popup).dialog("open");
-           });
-    };
-
-    $.extend({
-
-       // service-proxy or pazpar2
-       pazpar2: function(config) {
-           var id_popup = config.id_popup || "#mkwsPopup";
-           id_popup = id_popup.replace(/^#/, "");
-
-           // simple layout
-           var div = '\
-<div id="mkwsSwitch"></div>\
-<div id="mkwsLang"></div>\
-<div id="mkwsSearch"></div>\
-<div id="mkwsResults"></div>\
-<div id="mkwsTargets"></div>\
-<div id="mkwsStat"></div>';
-
-           // new table layout
-           var table = '\
-<style type="text/css">\
-  #mkwsTermlists div.facet {\
-  float:left;\
-  width: 30%;\
-  margin: 0.3em;\
-  }\
-  #mkwsStat {\
-  text-align: right;\
-  }\
-</style>\
-    \
-<table width="100%" border="0">\
-  <tr>\
-    <td>\
-      <div id="mkwsSwitch"></div>\
-      <div id="mkwsLang"></div>\
-      <div id="mkwsSearch"></div>\
-    </td>\
-  </tr>\
-  <tr>\
-    <td>\
-      <div style="height:500px; overflow: auto">\
-       <div id="mkwsPager"></div>\
-       <div id="mkwsNavi"></div>\
-       <div id="mkwsRecords"></div>\
-       <div id="mkwsTargets"></div>\
-       <div id="mkwsRanking"></div>\
-      </div>\
-    </td>\
-  </tr>\
-  <tr>\
-    <td>\
-      <div style="height:300px; overflow: hidden">\
-       <div id="mkwsTermlists"></div>\
-      </div>\
-    </td>\
-  </tr>\
-  <tr>\
-    <td>\
-      <div id="mkwsStat"></div>\
-    </td>\
-  </tr>\
-</table>';
-
-           var popup = '\
-<div id="mkwsSearch"></div>\
-<div id="' + id_popup + '">\
-  <div id="mkwsSwitch"></div>\
-  <div id="mkwsLang"></div>\
-  <div id="mkwsResults"></div>\
-  <div id="mkwsTargets"></div>\
-  <div id="mkwsStat"></div>\
-</div>'
-
-           if (config && config.layout == 'div') {
-               debug("jquery plugin layout: div");
-               document.write(div);
-           } else if (config && config.layout == 'popup') {
-               debug("jquery plugin layout: popup with id: " + id_popup);
-               document.write(popup);
-               $(document).ready(function() { init_popup(config); });
-           } else {
-               debug("jquery plugin layout: table");
-               document.write(table);
-           }
-       }
-    });
-};
-
-
 // wrapper to call team() after page load
 (function (j) {
     mkws.debug = function (string) {
@@ -1262,21 +1231,29 @@ function _mkws_jquery_plugin ($) {
     var debug = mkws.debug;
 
 
-    // enable before page load, so we could call it before mkws() runs
-    _mkws_jquery_plugin(j);
-
-
     mkws.handle_node_with_team = function(node, callback) {
-       var classes = node.className;
+       // First branch for DOM objects; second branch for jQuery objects
+       var classes = node.className || node.attr('class');
+       if (!classes) {
+           // For some reason, if we try to proceed when classes is
+           // undefined, we don't get an error message, but this
+           // function and its callers, up several stack level,
+           // silently return. What a crock.
+           mkws.debug("handle_node_with_team() called on node with no classes");
+           return;
+       }
        var list = classes.split(/\s+/)
-       var tname;
+       var teamName, type;
+
        for (var i = 0; i < list.length; i++) {
            var cname = list[i];
            if (cname.match(/^mkwsTeam_/)) {
-               tname = cname.replace(/^mkwsTeam_/, '');
+               teamName = cname.replace(/^mkwsTeam_/, '');
+           } else if (cname.match(/^mkws/)) {
+               type = cname.replace(/^mkws/, '');
            }
        }
-       callback(tname);
+       callback.call(node, teamName, type);
     }
 
 
@@ -1434,23 +1411,7 @@ function _mkws_jquery_plugin ($) {
        debug("running auto searches");
 
        for (var teamName in mkws.teams) {
-           // ### should check mkwsTermlist as well, for facet-only teams
-           var node = $('.mkwsRecords.mkwsTeam_' + teamName);
-           var query = node.attr('autosearch');
-           debug("teamName '" + teamName + "', node=" + node + ", class='" + node.className + "', query=" + query);
-
-           if (query) {
-               var sort = node.attr('sort');
-               var targets = node.attr('targets');
-               var s = "running auto search: '" + query + "'";
-               if (teamName) s += " [teamName '" + teamName + "']";
-               if (sort) s += " sorted by '" + sort + "'";
-               if (targets) s += " in targets '" + targets + "'";
-               debug(s);
-               var team = mkws.teams[teamName];
-               debug($.toJSON(team));
-               team.newSearch(query, sort, targets, teamName);
-           }
+           mkws.teams[teamName].run_auto_search();
        }
     }
 
@@ -1463,7 +1424,7 @@ function _mkws_jquery_plugin ($) {
        // elements that have the old magic IDs.
        var ids = [ "Switch", "Lang", "Search", "Pager", "Navi",
                    "Results", "Records", "Targets", "Ranking",
-                   "Termlists", "Stat" ];
+                   "Termlists", "Stat", "MOTD" ];
        for (var i = 0; i < ids.length; i++) {
            var id = 'mkws' + ids[i];
            var node = $('#' + id);
@@ -1484,15 +1445,19 @@ function _mkws_jquery_plugin ($) {
 
        // Find all nodes with an class, and determine their team from
        // the mkwsTeam_* class. Make all team objects.
+       var then = $.now();
        $('[class^="mkws"],[class*=" mkws"]').each(function () {
-           var node = this;
-           mkws.handle_node_with_team(node, function(tname) {
+           mkws.handle_node_with_team(this, function(tname, type) {
                if (!mkws.teams[tname]) {
                    mkws.teams[tname] = team(j, tname);
                    debug("Made MKWS team '" + tname + "'");
                }
+               var myTeam = mkws.teams[tname];
+               var myWidget = widget(j, myTeam, type, this);
            });
        });
+       var now = $.now();
+       debug("Walking MKWS nodes took " + (now-then) + " ms");
 
        if (mkws_config.use_service_proxy) {
            authenticate_session(mkws_config.service_proxy_auth,