daa95b0e4f320374fcb8e382f89411a44d4e7203
[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.pz2Record = function (data) {
141   clearTimeout(mkdru.pz2.showTimer);
142   $('.mkdru-results').hide();
143   $('.mkdru-detail').html(Drupal.theme('mkdruDetail', data,
144                                        mkdru.hashAddDelOne(null, null, 'recid')));
145   $('.mkdru-detail').show();
146   clearTimeout(mkdru.pz2.recordTimer);
147 };
148
149
150
151 // State and URL handling 
152
153 // populate state from an object and fill in the blanks with defaults
154 mkdru.stateFromObject = function (obj) {
155   mkdru.state = $.extend({}, mkdru.defaultState);
156   for (var key in mkdru.defaultState)
157     if (typeof(obj[key]) != "undefined")
158       mkdru.state[key] = obj[key];
159 };
160
161 // populate state from current window's hash string
162 mkdru.stateFromHash = function () {
163   mkdru.stateFromObject($.deparam.fragment());
164 };
165
166 // set current window's hash string from state
167 mkdru.hashFromState = function () {
168   // only include non-default settings in the URL
169   var alteredState = {};
170   for (var key in mkdru.defaultState) {
171     if (mkdru.state[key] != mkdru.defaultState[key]) {
172       alteredState[key] = mkdru.state[key];
173     }
174   }
175   $.bbq.pushState(alteredState, 2);
176 };
177
178 // update mkdru_form theme's ui to match state
179 mkdru.uiFromState = function () {
180   for (var key in mkdru.state) {
181     switch(key) {
182     case 'query':
183       $('.mkdru-search input:text').attr('value', mkdru.state[key]);
184       break;
185     case 'perpage':
186       $('.mkdru-perpage').attr('value', mkdru.state[key]);
187       break;
188     case 'sort':
189       $('.mkdru-sort').attr('value', mkdru.state[key]);
190       break;
191     }
192   }
193 };
194
195 mkdru.hashChange = function () {
196   // TING hack, switching tabs resets the hash but does not re-load the page
197   // simply ignore new hash and set it to the old state
198   var hash = $.param.fragment();
199   if (hash.indexOf("-result") != -1 || hash.indexOf("facets=") != -1) {
200     mkdru.hashFromState();
201     return;
202   }
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.pazpar2Path,
385               "oninit": mkdru.pz2Init,
386               "onstat": mkdru.pz2Status,
387               "onterm": mkdru.pz2Term,
388               "termlist": termlist.join(','),
389               "usesessions" : mkdru.settings.useSessions,
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 = Drupal.settings.mkdru.sp_user;
413     var pass = Drupal.settings.mkdru.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.pazpar2Path, 
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);