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