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