Handle settings passed in as JSON from new data model.
[mkdru-moved-to-drupal.org.git] / mkdru.client.js
1 // Set up namespace and some state.
2 var mkdru = {
3   settings: JSON.parse(Drupal.settings.mkdru.settings),
4   // Variables
5   active: false,
6   pz2: null,
7   totalRec: 0,
8   pagerRange: 6,
9   pazpar2Path: Drupal.settings.mkdru.pz2_path,
10   facetContainerSelector: Drupal.settings.mkdru.facetContainerSelector,
11   // State; Keys limit_{facet key} will also be initialised
12   defaultState: {
13     page: 1,
14     perpage: 20,
15     sort: 'relevance',
16     query:'',
17     recid:null
18   },
19   state: {},
20   realm: ''
21 };
22 // Convenient references
23 mkdru.facets = mkdru.settings.facets;
24
25 // Wrapper for jQuery
26 (function ($) {
27
28 // So we can use jQuery BBQ with Drupal 6 and its 1.2.6 jQuery
29 if (!$.isArray) $.isArray = function(obj) {
30   return Object.prototype.toString.call(obj) === "[object Array]";
31 };
32
33 // BBQ has no handy way to remove params without changing the hash.
34 // This takes an object to add and an array of keys to delete.
35 mkdru.hashAddDelMany = function (add, del) {
36   var newHash = $.deparam.fragment();
37   if (typeof(add) === 'object')
38     $.extend(newHash, add);
39   if ($.isArray(del))
40     for (var i=0; i < del.length; i++)
41       if (newHash[del[i]] !== 'undefined')
42         delete newHash[del[i]];
43   return $.param.fragment("#", newHash);
44 }
45
46 // It's sometimes cumbersome that object literals can't take variable keys.
47 mkdru.hashAddDelOne = function (key, value, del) {
48   var toAdd;
49   var toDel;
50   if (key && value) {
51     var toAdd = {};
52     toAdd[key] = value;
53   }
54   if (del) {
55     var toDel = [];
56     toDel.push(del);
57   }
58   return mkdru.hashAddDelMany(toAdd, toDel);
59 }
60
61
62
63 // pz2.js event handlers:
64 mkdru.pz2Init = function () {
65   if (mkdru.state.query) {
66     mkdru.search();
67   }
68   mkdru.pz2.stat();
69 };
70
71 mkdru.pz2Show = function (data) {
72   mkdru.totalRec = data.merged;
73   $('.mkdru-pager').html(mkdru.generatePager());
74   $('.mkdru-counts').html(Drupal.theme('mkdruCounts', data.start + 1,
75                                       data.num, data.merged, data.total));
76   var html = "";
77   for (var i = 0; i < data.hits.length; i++) {
78     html += Drupal.theme('mkdruResult', data.hits[i], 
79       i + 1 + mkdru.state.perpage * (mkdru.state.page - 1),
80       "#" + $.param.fragment($.param.fragment(
81         window.location.href, {recid: data.hits[i].recid})) + "\n"
82     );
83   }
84   $('.mkdru-result-list').html(html);
85   if (mkdru.state.recid) {
86     mkdru.pz2.record(mkdru.state.recid);
87   }
88   else {
89     $('.mkdru-results').show();
90   }
91 };
92
93 mkdru.pz2Status = function (data) {
94   $('.mkdru-status').html(Drupal.theme('mkdruStatus', data.activeclients, data.clients));
95 };
96
97 mkdru.contains = function (hash, key, value) {
98   if (hash[key]) {
99     if (hash[key].length) {
100       for (var i=0; i<hash[key].length; i++) {
101         if (hash[key][i] === value) {
102           return true;
103         }
104       }
105     } else {
106       return hash[key] === value;
107     }
108   }
109   return false;
110 }
111
112 mkdru.pz2Term = function (data) {
113   // map all facets against selected, for simple rendering 
114   var hash = $.deparam.fragment();
115   for (var key in hash) {
116     if (key.indexOf('limit') == 0 && hash[key]) 
117       //always wrap in array
118       hash[key.substr(6)] = hash[key].split(/;+/);
119     delete hash[key];
120   }
121   for (var facet in mkdru.facets) {
122     var terms = data[mkdru.facets[facet].pz2Name];
123     for (var i=0; i<terms.length; i++) {
124       var term = terms[i];
125       var value = facet == "source" ? term.id : term.name;
126       if (mkdru.contains(hash, facet, value)) { //enabled
127         term.toggleLink = mkdru.removeLimit(facet, value);
128         term.selected = true;
129       } else { //disabled
130         term.toggleLink = mkdru.addLimit(facet, value);
131         term.selected = false;
132       }
133     }
134     $('.mkdru-facet-' + facet).html(
135         Drupal.theme('mkdruFacet', terms, facet, mkdru.facets[facet].max, 
136           hash[facet]));
137   }
138 };
139
140 mkdru.pz2ByTarget = function (data) {
141   
142 };
143
144 mkdru.pz2Record = function (data) {
145   clearTimeout(mkdru.pz2.showTimer);
146   $('.mkdru-results').hide();
147   $('.mkdru-detail').html(Drupal.theme('mkdruDetail', data,
148                                        mkdru.hashAddDelOne(null, null, 'recid')));
149   $('.mkdru-detail').show();
150   clearTimeout(mkdru.pz2.recordTimer);
151 };
152
153
154
155 // State and URL handling 
156
157 // populate state from an object and fill in the blanks with defaults
158 mkdru.stateFromObject = function (obj) {
159   mkdru.state = $.extend({}, mkdru.defaultState);
160   for (var key in mkdru.defaultState)
161     if (typeof(obj[key]) != "undefined")
162       mkdru.state[key] = obj[key];
163 };
164
165 // populate state from current window's hash string
166 mkdru.stateFromHash = function () {
167   mkdru.stateFromObject($.deparam.fragment());
168 };
169
170 // set current window's hash string from state
171 mkdru.hashFromState = function () {
172   // only include non-default settings in the URL
173   var alteredState = {};
174   for (var key in mkdru.defaultState) {
175     if (mkdru.state[key] != mkdru.defaultState[key]) {
176       alteredState[key] = mkdru.state[key];
177     }
178   }
179   $.bbq.pushState(alteredState, 2);
180 };
181
182 // update mkdru_form theme's ui to match state
183 mkdru.uiFromState = function () {
184   for (var key in mkdru.state) {
185     switch(key) {
186     case 'query':
187       $('.mkdru-search input:text').attr('value', mkdru.state[key]);
188       break;
189     case 'perpage':
190       $('.mkdru-perpage').attr('value', mkdru.state[key]);
191       break;
192     case 'sort':
193       $('.mkdru-sort').attr('value', mkdru.state[key]);
194       break;
195     }
196   }
197 };
198
199 mkdru.hashChange = function () {
200   // do we need to restart the search?
201   var searchTrigger = false;
202   // shallow copy of state so we can see what changed.
203   var oldState = $.extend({}, mkdru.state);
204   mkdru.stateFromHash();
205   // only have to compare values since all keys are initialised
206   for (key in mkdru.state) {
207     var changed = (mkdru.state[key] != oldState[key]);
208     if (key.substring(0,5) === 'limit' && changed)
209       searchTrigger = true;
210     if (key === 'page' && changed)
211       mkdru.pz2.showPage(mkdru.state.page-1);
212     if (key === 'query' && changed)
213       searchTrigger = true;
214   }
215   if (searchTrigger)
216     mkdru.search();
217   // request for record detail
218   if (mkdru.state.recid && (mkdru.state.recid != oldState.recid)) {
219     mkdru.pz2.record(mkdru.state.recid);
220   }
221   else {
222     $('.mkdru-detail').hide();
223     $('.mkdru-results').show();
224   }
225 };
226
227 // return link to limit facet
228 mkdru.addLimit = function (facet, limit) {
229   var newHash = $.deparam.fragment();
230   delete newHash['page'];
231   if ((typeof(newHash['limit_' + facet]) === 'undefined')
232        || !mkdru.facets[facet].multiLimit) {
233     newHash['limit_' + facet] = limit;
234   }
235   else {
236     newHash['limit_' + facet] += ';' + limit;
237   }
238   return $.param.fragment("#", newHash);
239 };
240
241 // return link to remove limit from facet
242 mkdru.removeLimit = function (facet, limit) {
243   var newHash = $.deparam.fragment();
244   delete newHash['page'];
245   if (!newHash['limit_' + facet].indexOf(';')
246       || !mkdru.facets[facet].multiLimit) {
247     delete newHash['limit_' + facet];
248   }
249   else {
250     var limits = newHash['limit_' + facet].split(';');
251     for (var i = 0; i < limits.length; i++) {
252       if (limits[i] == limit) {
253         limits.splice(i, 1);
254         if (limits.length < 1)
255           delete newHash['limit_' + facet];
256         else
257           newHash['limit_' + facet] = limits.join(';');
258         break;
259       }
260     }
261   }
262   return $.param.fragment("#", newHash);
263 };
264
265
266
267 // form submit handler
268 mkdru.submitQuery = function () {
269   // new query, back to defaults (shallow copy)
270   mkdru.state = $.extend({}, mkdru.defaultState);
271   mkdru.state.query = $('.mkdru-search input:text').attr('value');
272   mkdru.pollDropDowns();
273   mkdru.hashFromState();
274   mkdru.search();
275   mkdru.active = true;
276   return false;
277 };
278
279 // criteria drop-down (perpage, sort) handler
280 mkdru.submitCriteria = function () {
281   mkdru.pollDropDowns();
282   //search is not ON, do nothing
283   if (!mkdru.active) return false;
284   // pages mean different things now
285   mkdru.state.page = 1;
286   mkdru.hashFromState();
287   mkdru.pz2.show(0, mkdru.state.perpage, mkdru.state.sort);
288   return false;
289 }
290
291 mkdru.search = function () {
292   var filter = null;
293   var query = mkdru.state.query;
294
295   // facet limit implementation
296   for (var facet in mkdru.facets) {
297     // facet is limited
298     if (mkdru.state['limit_' + facet]) {
299       if (facet == "source") {
300         filter = 'pz:id=' + mkdru.state.limit_source;
301       }
302       else {
303         var limits = mkdru.state['limit_' + facet].split(/;+/);
304         for (var i = 0; i < limits.length; i++) {
305           // ex. query + and au="{limit_author}"
306           if (limits[i]) 
307             query += ' and ' + mkdru.facets[facet]['limiter'] + '="'
308                   + limits[i] + '"';
309         }
310       }
311     }
312   }
313
314   mkdru.pz2.search(query, mkdru.state.perpage, mkdru.state.sort, filter);
315   mkdru.active = true;
316 };
317
318 mkdru.pollDropDowns = function () {
319   mkdru.state.perpage = $('.mkdru-perpage').attr('value');
320   mkdru.state.sort = $('.mkdru-sort').attr('value');
321 };
322
323 mkdru.generatePager = function () {
324   // cast page parameter to numeric so we can add to it
325   if (typeof mkdru.state.page == "string") {
326     mkdru.state.page = Number(mkdru.state.page);
327   }
328   var total = Math.ceil(mkdru.totalRec / mkdru.state.perpage);
329   var first = (mkdru.state.page - mkdru.pagerRange > 0)
330       ? mkdru.state.page - mkdru.pagerRange : 1;
331   var last = first + 2 * mkdru.pagerRange < total
332       ? first + 2 * mkdru.pagerRange : total;
333   var prev = null;
334   var next = null;
335   var pages = [];
336
337   if ((mkdru.state.page - 1) >= first) {
338     prev = "#" + $.param.fragment($.param.fragment(
339                window.location.href, {page: mkdru.state.page - 1}))
340   }
341   if ((mkdru.state.page + 1) <= total) {
342     next = "#" + $.param.fragment($.param.fragment(
343                window.location.href, {page: mkdru.state.page + 1}))
344   }
345
346   for (var i = first; i <= last; i++) {
347     pages.push("#" + $.param.fragment($.param.fragment(
348                window.location.href, {page: i})));
349   }
350
351   return Drupal.theme('mkdruPager', pages, first, mkdru.state.page,
352                       total, prev, next);
353 };
354
355
356
357 // wait until the DOM is ready, bind events
358 // and instantiate pz2 library
359 $(document).ready(function () {
360   $(window).bind( 'hashchange', mkdru.hashChange);
361   $('.mkdru-search').bind('submit', mkdru.submitQuery);
362   $('.mkdru-search input:text').attr('value', '');
363   $('.mkdru-perpage').bind('change', mkdru.submitCriteria);
364   $('.mkdru-sort').bind('change', mkdru.submitCriteria);
365
366   // generate termlist for pz2.js and populate facet limit state
367   var termlist = [];
368   for (var key in mkdru.facets) {
369     termlist.push(mkdru.facets[key].pz2Name);
370     mkdru.defaultState['limit_' + key] = null;
371   }
372
373   // if signaled, prepare facet container client-side
374   if (mkdru.facetContainerSelector) {
375     $(mkdru.facetContainerSelector).html(Drupal.theme('mkdruFacetContainer',
376       mkdru.facets));
377   }
378
379   mkdru.pz2 = new pz2( { "onshow": mkdru.pz2Show,
380               "showtime": 500, //each timer (show, stat, term, bytarget) can be specified this way
381               "pazpar2path": mkdru.pazpar2Path,
382               "oninit": mkdru.pz2Init,
383               "onstat": mkdru.pz2Status,
384               "onterm": mkdru.pz2Term,
385               "termlist": termlist.join(','),
386               "usesessions" : mkdru.settings.useSessions,
387               "showResponseType": mkdru.showResponseType,
388               "onrecord": mkdru.pz2Record,
389               "autoInit": false } );
390   mkdru.pz2.showFastCount = 1;
391
392   // initialise state to hash string or defaults
393   mkdru.stateFromHash();
394   // and update UI to match
395   mkdru.uiFromState();
396
397   // ting thing
398   if (typeof(Drupal.settings.mkdru.query) !== "undefined") {
399     mkdru.state.query = Drupal.settings.mkdru.query
400     //force the hash to contain query
401     mkdru.hashFromState();
402   }
403
404   //not running against SP? init, otherwise authenticate
405   if (mkdru.settings.use_sessions) {
406     mkdru.pz2.init();
407   } else {
408     //runnin against SP
409     var user = Drupal.settings.mkdru.sp_user;
410     var pass = Drupal.settings.mkdru.sp_pass;
411     var params = {};
412     params['command'] = 'auth';
413     if (user && pass) {
414       params['action'] = 'login';
415       params['username'] = user;
416       params['password'] = pass;
417     } else {
418       params['action'] = 'ipauth';
419     }
420     var authReq = new pzHttpRequest(mkdru.pazpar2Path, 
421       function (err) {
422         alert("Authentication against metasearch gateway failed: " +err);
423       }
424     );
425     authReq.get(params,
426       function (data) {
427         var s = data.getElementsByTagName('status');
428         if (s.length && Element_getTextContent(s[0]) == "OK") {
429           mkdru.realm = data.getElementsByTagName('realm');
430           mkdru.pz2Init();
431         } else {
432           alert("Malformed response when authenticating against the metasearch"
433             + " gateway");
434         }
435       }
436     );
437   }
438 });
439 })(jQuery);