Merge branch '7.x' of ssh://git.indexdata.com/home/git/pub/mkdru into 7.x
[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.pz2ByTarget = function (data) {
139   
140 };
141
142 mkdru.pz2Record = function (data) {
143   clearTimeout(mkdru.pz2.showTimer);
144   $('.mkdru-results').hide();
145   $('.mkdru-detail').html(Drupal.theme('mkdruDetail', data,
146                                        mkdru.hashAddDelOne(null, null, 'recid')));
147   $('.mkdru-detail').show();
148   clearTimeout(mkdru.pz2.recordTimer);
149 };
150
151
152
153 // State and URL handling 
154
155 // populate state from an object and fill in the blanks with defaults
156 mkdru.stateFromObject = function (obj) {
157   mkdru.state = $.extend({}, mkdru.defaultState);
158   for (var key in mkdru.defaultState)
159     if (typeof(obj[key]) != "undefined")
160       mkdru.state[key] = obj[key];
161 };
162
163 // populate state from current window's hash string
164 mkdru.stateFromHash = function () {
165   mkdru.stateFromObject($.deparam.fragment());
166 };
167
168 // set current window's hash string from state
169 mkdru.hashFromState = function () {
170   // only include non-default settings in the URL
171   var alteredState = {};
172   for (var key in mkdru.defaultState) {
173     if (mkdru.state[key] != mkdru.defaultState[key]) {
174       alteredState[key] = mkdru.state[key];
175     }
176   }
177   $.bbq.pushState(alteredState, 2);
178 };
179
180 // update mkdru_form theme's ui to match state
181 mkdru.uiFromState = function () {
182   for (var key in mkdru.state) {
183     switch(key) {
184     case 'query':
185       $('.mkdru-search input:text').attr('value', mkdru.state[key]);
186       break;
187     case 'perpage':
188       $('.mkdru-perpage').attr('value', mkdru.state[key]);
189       break;
190     case 'sort':
191       $('.mkdru-sort').attr('value', mkdru.state[key]);
192       break;
193     }
194   }
195 };
196
197 mkdru.hashChange = function () {
198   // do we need to restart the search?
199   var searchTrigger = false;
200   // shallow copy of state so we can see what changed.
201   var oldState = $.extend({}, mkdru.state);
202   mkdru.stateFromHash();
203   // only have to compare values since all keys are initialised
204   for (key in mkdru.state) {
205     var changed = (mkdru.state[key] != oldState[key]);
206     if (key.substring(0,5) === 'limit' && changed)
207       searchTrigger = true;
208     if (key === 'page' && changed)
209       mkdru.pz2.showPage(mkdru.state.page-1);
210     if (key === 'query' && changed)
211       searchTrigger = true;
212   }
213   if (searchTrigger)
214     mkdru.search();
215   // request for record detail
216   if (mkdru.state.recid && (mkdru.state.recid != oldState.recid)) {
217     mkdru.pz2.record(mkdru.state.recid);
218   }
219   else {
220     $('.mkdru-detail').hide();
221     $('.mkdru-results').show();
222   }
223 };
224
225 // return link to limit facet
226 mkdru.addLimit = function (facet, limit) {
227   var newHash = $.deparam.fragment();
228   delete newHash['page'];
229   if ((typeof(newHash['limit_' + facet]) === 'undefined')
230        || !mkdru.facets[facet].multiLimit) {
231     newHash['limit_' + facet] = limit;
232   }
233   else {
234     newHash['limit_' + facet] += ';' + limit;
235   }
236   return $.param.fragment("#", newHash);
237 };
238
239 // return link to remove limit from facet
240 mkdru.removeLimit = function (facet, limit) {
241   var newHash = $.deparam.fragment();
242   delete newHash['page'];
243   if (!newHash['limit_' + facet].indexOf(';')
244       || !mkdru.facets[facet].multiLimit) {
245     delete newHash['limit_' + facet];
246   }
247   else {
248     var limits = newHash['limit_' + facet].split(';');
249     for (var i = 0; i < limits.length; i++) {
250       if (limits[i] == limit) {
251         limits.splice(i, 1);
252         if (limits.length < 1)
253           delete newHash['limit_' + facet];
254         else
255           newHash['limit_' + facet] = limits.join(';');
256         break;
257       }
258     }
259   }
260   return $.param.fragment("#", newHash);
261 };
262
263
264
265 // form submit handler
266 mkdru.submitQuery = function () {
267   // new query, back to defaults (shallow copy)
268   mkdru.state = $.extend({}, mkdru.defaultState);
269   mkdru.state.query = $('.mkdru-search input:text').attr('value');
270   mkdru.pollDropDowns();
271   mkdru.hashFromState();
272   mkdru.search();
273   mkdru.active = true;
274   return false;
275 };
276
277 // criteria drop-down (perpage, sort) handler
278 mkdru.submitCriteria = function () {
279   mkdru.pollDropDowns();
280   //search is not ON, do nothing
281   if (!mkdru.active) return false;
282   // pages mean different things now
283   mkdru.state.page = 1;
284   mkdru.hashFromState();
285   mkdru.pz2.show(0, mkdru.state.perpage, mkdru.state.sort);
286   return false;
287 }
288
289 mkdru.search = function () {
290   var filter = null;
291   var query = mkdru.state.query;
292
293   // facet limit implementation
294   for (var facet in mkdru.facets) {
295     // facet is limited
296     if (mkdru.state['limit_' + facet]) {
297       if (facet == "source") {
298         filter = 'pz:id=' + mkdru.state.limit_source;
299       }
300       else {
301         var limits = mkdru.state['limit_' + facet].split(/;+/);
302         for (var i = 0; i < limits.length; i++) {
303           // ex. query + and au="{limit_author}"
304           if (limits[i]) 
305             query += ' and ' + mkdru.facets[facet]['limiter'] + '="'
306                   + limits[i] + '"';
307         }
308       }
309     }
310   }
311
312   mkdru.pz2.search(query, mkdru.state.perpage, mkdru.state.sort, filter);
313   mkdru.active = true;
314 };
315
316 mkdru.pollDropDowns = function () {
317   mkdru.state.perpage = $('.mkdru-perpage').attr('value');
318   mkdru.state.sort = $('.mkdru-sort').attr('value');
319 };
320
321 mkdru.generatePager = function () {
322   // cast page parameter to numeric so we can add to it
323   if (typeof mkdru.state.page == "string") {
324     mkdru.state.page = Number(mkdru.state.page);
325   }
326   var total = Math.ceil(mkdru.totalRec / mkdru.state.perpage);
327   var first = (mkdru.state.page - mkdru.pagerRange > 0)
328       ? mkdru.state.page - mkdru.pagerRange : 1;
329   var last = first + 2 * mkdru.pagerRange < total
330       ? first + 2 * mkdru.pagerRange : total;
331   var prev = null;
332   var next = null;
333   var pages = [];
334
335   if ((mkdru.state.page - 1) >= first) {
336     prev = "#" + $.param.fragment($.param.fragment(
337                window.location.href, {page: mkdru.state.page - 1}))
338   }
339   if ((mkdru.state.page + 1) <= total) {
340     next = "#" + $.param.fragment($.param.fragment(
341                window.location.href, {page: mkdru.state.page + 1}))
342   }
343
344   for (var i = first; i <= last; i++) {
345     pages.push("#" + $.param.fragment($.param.fragment(
346                window.location.href, {page: i})));
347   }
348
349   return Drupal.theme('mkdruPager', pages, first, mkdru.state.page,
350                       total, prev, next);
351 };
352
353
354
355 // wait until the DOM is ready, bind events
356 // and instantiate pz2 library
357 $(document).ready(function () {
358   $(window).bind( 'hashchange', mkdru.hashChange);
359   $('.mkdru-search').bind('submit', mkdru.submitQuery);
360   $('.mkdru-search input:text').attr('value', '');
361   $('.mkdru-perpage').bind('change', mkdru.submitCriteria);
362   $('.mkdru-sort').bind('change', mkdru.submitCriteria);
363
364   // generate termlist for pz2.js and populate facet limit state
365   var termlist = [];
366   for (var key in mkdru.facets) {
367     termlist.push(mkdru.facets[key].pz2Name);
368     mkdru.defaultState['limit_' + key] = null;
369   }
370
371   // if signaled, prepare facet container client-side
372   if (mkdru.facetContainerSelector) {
373     $(mkdru.facetContainerSelector).html(Drupal.theme('mkdruFacetContainer',
374       mkdru.facets));
375   }
376
377   mkdru.pz2 = new pz2( { "onshow": mkdru.pz2Show,
378               "showtime": 500, //each timer (show, stat, term, bytarget) can be specified this way
379               "pazpar2path": mkdru.settings.pz2_path,
380               "oninit": mkdru.pz2Init,
381               "onstat": mkdru.pz2Status,
382               "onterm": mkdru.pz2Term,
383               "termlist": termlist.join(','),
384               "usesessions" : mkdru.settings.use_sessions,
385               "showResponseType": mkdru.showResponseType,
386               "onrecord": mkdru.pz2Record,
387               "autoInit": false } );
388   mkdru.pz2.showFastCount = 1;
389
390   // initialise state to hash string or defaults
391   mkdru.stateFromHash();
392   // and update UI to match
393   mkdru.uiFromState();
394
395   // ting thing
396   if (typeof(Drupal.settings.mkdru.query) !== "undefined") {
397     mkdru.state.query = Drupal.settings.mkdru.query
398     //force the hash to contain query
399     mkdru.hashFromState();
400   }
401
402   //not running against SP? init, otherwise authenticate
403   if (mkdru.settings.use_sessions) {
404     mkdru.pz2.init();
405   } else {
406     //runnin against SP
407     var user = mkdru.settings.sp.user;
408     var pass = mkdru.settings.sp.pass;
409     var params = {};
410     params['command'] = 'auth';
411     if (user && pass) {
412       params['action'] = 'login';
413       params['username'] = user;
414       params['password'] = pass;
415     } else {
416       params['action'] = 'ipauth';
417     }
418     var authReq = new pzHttpRequest(mkdru.settings.pz2_path, 
419       function (err) {
420         alert("Authentication against metasearch gateway failed: " +err);
421       }
422     );
423     authReq.get(params,
424       function (data) {
425         var s = data.getElementsByTagName('status');
426         if (s.length && Element_getTextContent(s[0]) == "OK") {
427           mkdru.realm = data.getElementsByTagName('realm');
428           mkdru.pz2Init();
429         } else {
430           alert("Malformed response when authenticating against the metasearch"
431             + " gateway");
432         }
433       }
434     );
435   }
436 });
437 })(jQuery);