Allow empty string for pazpar2path
[pazpar2-moved-to-github.git] / js / pz2.js
1 /*
2 ** $Id: pz2.js,v 1.53 2007-09-03 20:13:22 adam Exp $
3 ** pz2.js - pazpar2's javascript client library.
4 */
5
6 //since explorer is flawed
7 if (!window['Node']) {
8     window.Node = new Object();
9     Node.ELEMENT_NODE = 1;
10     Node.ATTRIBUTE_NODE = 2;
11     Node.TEXT_NODE = 3;
12     Node.CDATA_SECTION_NODE = 4;
13     Node.ENTITY_REFERENCE_NODE = 5;
14     Node.ENTITY_NODE = 6;
15     Node.PROCESSING_INSTRUCTION_NODE = 7;
16     Node.COMMENT_NODE = 8;
17     Node.DOCUMENT_NODE = 9;
18     Node.DOCUMENT_TYPE_NODE = 10;
19     Node.DOCUMENT_FRAGMENT_NODE = 11;
20     Node.NOTATION_NODE = 12;
21 }
22
23 // prevent execution of more than once
24 if(typeof window.pz2 == "undefined") {
25 window.undefined = window.undefined;
26
27 var pz2 = function ( paramArray )
28 {
29     
30     // at least one callback required
31     if ( !paramArray )
32         throw new Error("Pz2.js: An array with parameters has to be suplied when instantiating a class");
33     
34     //for convenience
35     __myself = this;
36
37     //supported pazpar2's protocol version
38     __myself.suppProtoVer = '1';
39     if (typeof paramArray.pazpar2path != "undefined")
40         __myself.pz2String = paramArray.pazpar2path;
41     else
42         __myself.pz2String = "/pazpar2/search.pz2";
43     __myself.useSessions = true;
44     
45     __myself.stylesheet = paramArray.detailstylesheet || null;
46     //load stylesheet if required in async mode
47     if( __myself.stylesheet ) {
48         var request = new pzHttpRequest( __myself.stylesheet );
49         request.get( {}, function ( doc ) { __myself.xslDoc = doc; } );
50     }
51     
52     __myself.errorHandler = paramArray.errorhandler || null;
53     
54     // function callbacks
55     __myself.statCallback = paramArray.onstat || null;
56     __myself.showCallback = paramArray.onshow || null;
57     __myself.termlistCallback = paramArray.onterm || null;
58     __myself.recordCallback = paramArray.onrecord || null;
59     __myself.bytargetCallback = paramArray.onbytarget || null;
60     __myself.resetCallback = paramArray.onreset || null;
61
62     // termlist keys
63     __myself.termKeys = paramArray.termlist || "subject";
64     
65     // some configurational stuff
66     __myself.keepAlive = 50000;
67     
68     if ( paramArray.keepAlive < __myself.keepAlive )
69         __myself.keepAlive = paramArray.keepAlive;
70
71     __myself.sessionID = null;
72     __myself.initStatusOK = false;
73     __myself.pingStatusOK = false;
74     __myself.searchStatusOK = false;
75     
76     // for sorting
77     __myself.currentSort = "relevance";
78
79     // where are we?
80     __myself.currentStart = 0;
81     __myself.currentNum = 20;
82
83     // last full record retrieved
84     __myself.currRecID = null;
85     
86     // current query
87     __myself.currQuery = null;
88
89     //timers
90     __myself.statTime = paramArray.stattime || 1000;
91     __myself.statTimer = null;
92     __myself.termTime = paramArray.termtime || 1000;
93     __myself.termTimer = null;
94     __myself.showTime = paramArray.showtime || 1000;
95     __myself.showTimer = null;
96     __myself.showFastCount = 4;
97     __myself.bytargetTime = paramArray.bytargettime || 1000;
98     __myself.bytargetTimer = null;
99
100     // counters for each command and applied delay
101     __myself.dumpFactor = 500;
102     __myself.showCounter = 0;
103     __myself.termCounter = 0;
104     __myself.statCounter = 0;
105     __myself.bytargetCounter = 0;
106
107     // active clients, updated by stat and show
108     // might be an issue since bytarget will poll accordingly
109     __myself.activeClients = 1;
110
111     // if in proxy mode no need to init
112     if (paramArray.usesessions != undefined) {
113          __myself.useSessions = paramArray.usesessions;
114         __myself.initStatusOK = true;
115     }
116     // else, auto init session or wait for a user init?
117     if (__myself.useSessions && paramArray.autoInit !== false) {
118         __myself.init();
119     }
120 };
121
122 pz2.prototype = 
123 {
124     // stop activity by clearing tiemouts 
125    stop: function ()
126    {
127        clearTimeout(__myself.statTimer);
128        clearTimeout(__myself.showTimer);
129        clearTimeout(__myself.termTimer);
130        clearTimeout(__myself.bytargetTimer);
131     },
132     
133     // reset status variables
134     reset: function ()
135     {   
136         if ( __myself.useSessions ) {
137             __myself.sessionID = null;
138             __myself.initStatusOK = false;
139             __myself.pingStatusOK = false;
140         }
141         __myself.searchStatusOK = false;
142         __myself.stop();
143             
144         if ( __myself.resetCallback )
145                 __myself.resetCallback();
146     },
147
148     init: function ( sessionId ) 
149     {
150         __myself.reset();
151         
152         // session id as a param
153         if ( sessionId != undefined && __myself.useSessions ) {
154             __myself.initStatusOK = true;
155             __myself.sessionID = sessionId;
156             __myself.ping();
157         // old school direct pazpar2 init
158         } else if (__myself.useSessions) {
159             var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
160             request.get(
161                 { "command": "init" },
162                 function(data) {
163                     if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
164                         if ( data.getElementsByTagName("protocol")[0].childNodes[0].nodeValue 
165                             != __myself.suppProtoVer )
166                             throw new Error("Server's protocol not supported by the client");
167                         __myself.initStatusOK = true;
168                         __myself.sessionID = data.getElementsByTagName("session")[0].childNodes[0].nodeValue;
169                         setTimeout("__myself.ping()", __myself.keepAlive);
170                     }
171                     else
172                         // if it gets here the http return code was 200 (pz2 errors are 417)
173                         // but the response was invalid, it should never occur
174                         setTimeout("__myself.init()", 1000);
175                 }
176             );
177         // when through proxy no need to init
178         } else {
179             __myself.initStatusOK = true;
180         }
181     },
182     // no need to ping explicitly
183     ping: function () 
184     {
185         // pinging only makes sense when using pazpar2 directly
186         if( !__myself.initStatusOK || !__myself.useSessions )
187             throw new Error('Pz2.js: Ping not allowed (proxy mode) or session not initialized.');
188             // session is not initialized code here
189         
190         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
191         request.get(
192             { "command": "ping", "session": __myself.sessionID },
193             function(data) {
194                 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
195                     __myself.pingStatusOK = true;
196                     setTimeout("__myself.ping()", __myself.keepAlive);
197                 }
198                 else
199                     // if it gets here the http return code was 200 (pz2 errors are 417)
200                     // but the response was invalid, it should never occur
201                     setTimeout("__myself.ping()", 1000);
202             }
203         );
204     },
205     search: function (query, num, sort, filter, showfrom)
206     {
207         clearTimeout(__myself.statTimer);
208         clearTimeout(__myself.showTimer);
209         clearTimeout(__myself.termTimer);
210         clearTimeout(__myself.bytargetTimer);
211         
212         __myself.showCounter = 0;
213         __myself.termCounter = 0;
214         __myself.bytargetCounter = 0;
215         __myself.statCounter = 0;
216         
217         // no proxy mode
218         if( !__myself.initStatusOK )
219             throw new Error('Pz2.js: session not initialized.');
220         
221         if( query !== undefined )
222             __myself.currQuery = query;
223         else
224             throw new Error("Pz2.js: no query supplied to the search command.");
225         
226         if ( showfrom !== undefined )
227             var start = showfrom;
228         else
229             var start = 0;
230
231         var searchParams = { "command": "search", "query": __myself.currQuery, "session": __myself.sessionID };
232         
233         if (filter !== undefined)
234             searchParams["filter"] = filter;
235         
236         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
237         request.get(
238             searchParams,
239             function(data) {
240                 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
241                     __myself.searchStatusOK = true;
242                     //piggyback search
243                     __myself.show(start, num, sort);
244                     if ( __myself.statCallback )
245                         __myself.stat();
246                         //__myself.statTimer = setTimeout("__myself.stat()", __myself.statTime / 4);
247                     if ( __myself.termlistCallback )
248                         __myself.termlist();
249                         //__myself.termTimer = setTimeout("__myself.termlist()", __myself.termTime / 4);
250                     if ( __myself.bytargetCallback )
251                         __myself.bytarget();
252                         //__myself.bytargetTimer = setTimeout("__myself.bytarget()", __myself.bytargetTime / 4);
253                 }
254                 else
255                     // if it gets here the http return code was 200 (pz2 errors are 417)
256                     // but the response was invalid, it should never occur
257                     setTimeout("__myself.search(__myself.currQuery)", 500);
258             }
259         );
260     },
261     stat: function()
262     {
263         if( !__myself.initStatusOK )
264             throw new Error('Pz2.js: session not initialized.');
265         
266         // if called explicitly takes precedence
267         clearTimeout(__myself.statTimer);
268         
269         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
270         request.get(
271             { "command": "stat", "session": __myself.sessionID },
272             function(data) {
273                 if ( data.getElementsByTagName("stat") ) {
274                     var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
275                     __myself.activeClients = activeClients;
276                     var stat = {
277                     "activeclients": activeClients,
278                     "hits": Number( data.getElementsByTagName("hits")[0].childNodes[0].nodeValue ),
279                     "records": Number( data.getElementsByTagName("records")[0].childNodes[0].nodeValue ),
280                     "clients": Number( data.getElementsByTagName("clients")[0].childNodes[0].nodeValue ),
281                     "initializing": Number( data.getElementsByTagName("initializing")[0].childNodes[0].nodeValue ),
282                     "searching": Number( data.getElementsByTagName("searching")[0].childNodes[0].nodeValue ),
283                     "presenting": Number( data.getElementsByTagName("presenting")[0].childNodes[0].nodeValue ),
284                     "idle": Number( data.getElementsByTagName("idle")[0].childNodes[0].nodeValue ),
285                     "failed": Number( data.getElementsByTagName("failed")[0].childNodes[0].nodeValue ),
286                     "error": Number( data.getElementsByTagName("error")[0].childNodes[0].nodeValue )
287                     };
288                     
289                     __myself.statCounter++;
290                     var delay = __myself.statTime + __myself.statCounter * __myself.dumpFactor;
291                     if ( activeClients > 0 )
292                         __myself.statTimer = setTimeout("__myself.stat()", delay);
293                     __myself.statCallback(stat);
294                 }
295                 else
296                     // if it gets here the http return code was 200 (pz2 errors are 417)
297                     // but the response was invalid, it should never occur
298                     __myself.statTimer = setTimeout("__myself.stat()", __myself.statTime / 4);
299             }
300         );
301     },
302     show: function(start, num, sort)
303     {
304         if( !__myself.searchStatusOK && __myself.useSessions )
305             throw new Error('Pz2.js: show command has to be preceded with a search command.');
306         
307         // if called explicitly takes precedence
308         clearTimeout(__myself.showTimer);
309         
310         if( sort !== undefined )
311             __myself.currentSort = sort;
312         if( start !== undefined )
313             __myself.currentStart = Number( start );
314         if( num !== undefined )
315             __myself.currentNum = Number( num );
316
317         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
318         var context = this;
319         request.get(
320             { "command": "show", "session": __myself.sessionID, "start": __myself.currentStart,
321               "num": __myself.currentNum, "sort": __myself.currentSort, "block": 1 },
322             function(data) {
323                 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
324                     // first parse the status data send along with records
325                     // this is strictly bound to the format
326                     var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
327                     __myself.activeClients = activeClients; 
328                     var show = {
329                     "activeclients": activeClients,
330                     "merged": Number( data.getElementsByTagName("merged")[0].childNodes[0].nodeValue ),
331                     "total": Number( data.getElementsByTagName("total")[0].childNodes[0].nodeValue ),
332                     "start": Number( data.getElementsByTagName("start")[0].childNodes[0].nodeValue ),
333                     "num": Number( data.getElementsByTagName("num")[0].childNodes[0].nodeValue ),
334                     "hits": []
335                     };
336                     // parse all the first-level nodes for all <hit> tags
337                     var hits = data.getElementsByTagName("hit");
338                     var hit = new Array();
339                     for (i = 0; i < hits.length; i++) {
340                         show.hits[i] = new Array();
341                         show.hits[i]['location'] = new Array();
342                         for ( j = 0; j < hits[i].childNodes.length; j++) {
343                             var locCount = 0;
344                             if ( hits[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
345                                 if (hits[i].childNodes[j].nodeName == 'location') {
346                                     var locNode = hits[i].childNodes[j];
347                                     var id = locNode.getAttribute('id');
348                                     show.hits[i]['location'][id] = {
349                                         "id": locNode.getAttribute("id"),
350                                         "name": locNode.getAttribute("name")
351                                     };
352                                 }
353                                 else {
354                                     var nodeName = hits[i].childNodes[j].nodeName;
355                                     var nodeText = 'ERROR'
356                                     if ( hits[i].childNodes[j].firstChild )
357                                         nodeText = hits[i].childNodes[j].firstChild.nodeValue;
358                                     show.hits[i][nodeName] = nodeText;
359                                 }
360                             }
361                         }
362                     }
363                     __myself.showCounter++;
364                     var delay = __myself.showTime;
365                     if (__myself.showCounter > __myself.showFastCount)
366                             delay += __myself.showCounter * __myself.dumpFactor;
367                     if ( activeClients > 0 )
368                         __myself.showTimer = setTimeout("__myself.show()", delay);
369
370                     __myself.showCallback(show);
371                 }
372                 else
373                     // if it gets here the http return code was 200 (pz2 errors are 417)
374                     // but the response was invalid, it should never occur
375                     __myself.showTimer = setTimeout("__myself.show()", __myself.showTime / 4);
376             }
377         );
378     },
379     record: function(id, offset, params)
380     {
381         // we may call record with no previous search if in proxy mode
382         if( !__myself.searchStatusOK && __myself.useSessions)
383            throw new Error('Pz2.js: record command has to be preceded with a search command.'); 
384
385         if ( params == undefined )
386             params = {};
387
388         if ( params.callback != undefined ) {
389             callback = params.callback;
390         } else {
391             callback = __myself.recordCallback;
392         }
393         
394         // what is that?
395         if ( params['handle'] == undefined )
396             handle = {};
397         else
398             handle = params['handle'];
399
400         if( id !== undefined )
401             __myself.currRecID = id;
402         
403         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
404
405         var recordParams = { "command": "record", 
406                             "session": __myself.sessionID,
407                             "id": __myself.currRecID };
408         
409         if (offset !== undefined) {
410             recordParams["offset"] = offset;
411         }
412
413         if (params.syntax != undefined) {
414             recordParams['syntax'] = params.syntax;
415         }
416
417         __myself.currRecOffset = offset;
418
419         request.get(
420             recordParams,
421             function(data) {
422                 var recordNode;
423                 var record = new Array();
424                 record['xmlDoc'] = data;
425                 if (__myself.currRecOffset !== undefined) {
426                     record['offset'] = __myself.currRecOffset;
427                     callback(record, handle);
428                 } else if ( recordNode = data.getElementsByTagName("record")[0] ) {
429                     // if stylesheet was fetched do not parse the response
430                     if ( __myself.xslDoc ) {
431                         record['recid'] = recordNode.getElementsByTagName("recid")[0].firstChild.nodeValue;
432                         record['xslDoc'] = __myself.xslDoc;
433                     } else {
434                         for ( i = 0; i < recordNode.childNodes.length; i++) {
435                             if ( recordNode.childNodes[i].nodeType == Node.ELEMENT_NODE
436                                     && recordNode.childNodes[i].nodeName != 'location' ) {
437                                 var nodeName = recordNode.childNodes[i].nodeName;
438                                 var nodeText = recordNode.childNodes[i].firstChild.nodeValue;
439                                 record[nodeName] = nodeText;                            
440                             }
441                         }
442                         // the location might be empty!!
443                         var locationNodes = recordNode.getElementsByTagName("location");
444                         record["location"] = new Array();
445                         for ( i = 0; i < locationNodes.length; i++ ) {
446                             record["location"][i] = {
447                                 "id": locationNodes[i].getAttribute("id"),
448                                 "name": locationNodes[i].getAttribute("name")
449                             };
450                             
451                             for ( j = 0; j < locationNodes[i].childNodes.length; j++) {
452                                 if ( locationNodes[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
453                                     var nodeName = locationNodes[i].childNodes[j].nodeName;
454                                     var nodeText = '';
455                                     if (locationNodes[i].childNodes[j].firstChild)
456                                             nodeText = locationNodes[i].childNodes[j].firstChild.nodeValue;
457                                     record["location"][i][nodeName] = nodeText;                            
458                                 }
459                             }
460                         }
461                     }
462                     
463                     callback(record, handle);
464                 }
465                 else
466                     // if it gets here the http return code was 200 (pz2 errors are 417)
467                     // but the response was invalid, it should never occur
468                     setTimeout("__myself.record(__myself.currRecID)", 500);
469             }
470         );
471     },
472
473     termlist: function()
474     {
475         if( !__myself.searchStatusOK && __myself.useSessions )
476             throw new Error('Pz2.js: termlist command has to be preceded with a search command.');
477
478         // if called explicitly takes precedence
479         clearTimeout(__myself.termTimer);
480         
481         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
482         request.get(
483             { "command": "termlist", "session": __myself.sessionID, "name": __myself.termKeys },
484             function(data) {
485                 if ( data.getElementsByTagName("termlist") ) {
486                     var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
487                     __myself.activeClients = activeClients;
488                     var termList = { "activeclients":  activeClients };
489                     var termLists = data.getElementsByTagName("list");
490                     //for each termlist
491                     for (i = 0; i < termLists.length; i++) {
492                         var listName = termLists[i].getAttribute('name');
493                         termList[listName] = new Array();
494                         var terms = termLists[i].getElementsByTagName('term');
495                         //for each term in the list
496                         for (j = 0; j < terms.length; j++) { 
497                             var term = {
498                                 "name": (terms[j].getElementsByTagName("name")[0].childNodes.length 
499                                                 ? terms[j].getElementsByTagName("name")[0].childNodes[0].nodeValue
500                                                 : 'ERROR'),
501                                 "freq": terms[j].getElementsByTagName("frequency")[0].childNodes[0].nodeValue || 'ERROR'
502                             };
503
504                             var termIdNode = terms[j].getElementsByTagName("id");
505                             if(terms[j].getElementsByTagName("id").length)
506                                 term["id"] = termIdNode[0].childNodes[0].nodeValue;
507
508                             termList[listName][j] = term;
509                         }
510                     }
511
512                     __myself.termCounter++;
513                     var delay = __myself.termTime + __myself.termCounter * __myself.dumpFactor;
514                     if ( activeClients > 0 )
515                         __myself.termTimer = setTimeout("__myself.termlist()", delay);
516                    
517                    __myself.termlistCallback(termList);
518                 }
519                 else
520                     // if it gets here the http return code was 200 (pz2 errors are 417)
521                     // but the response was invalid, it should never occur
522                     __myself.termTimer = setTimeout("__myself.termlist()", __myself.termTime / 4); 
523             }
524         );
525
526     },
527     bytarget: function()
528     {
529         if( !__myself.initStatusOK && __myself.useSessions )
530             throw new Error('Pz2.js: bytarget command has to be preceded with a search command.');
531         
532         // no need to continue
533         if( !__myself.searchStatusOK )
534             return;
535
536         // if called explicitly takes precedence
537         clearTimeout(__myself.bytargetTimer);
538         
539         var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
540         request.get(
541             { "command": "bytarget", "session": __myself.sessionID },
542             function(data) {
543                 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
544                     var targetNodes = data.getElementsByTagName("target");
545                     var bytarget = new Array();
546                     for ( i = 0; i < targetNodes.length; i++) {
547                         bytarget[i] = new Array();
548                         for( j = 0; j < targetNodes[i].childNodes.length; j++ ) {
549                             if ( targetNodes[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
550                                 var nodeName = targetNodes[i].childNodes[j].nodeName;
551                                 var nodeText = targetNodes[i].childNodes[j].firstChild.nodeValue;
552                                 bytarget[i][nodeName] = nodeText;
553                             }
554                         }
555                     }
556                     
557                     __myself.bytargetCounter++;
558                     var delay = __myself.bytargetTime + __myself.bytargetCounter * __myself.dumpFactor;
559                     if ( __myself.activeClients > 0 )
560                         __myself.bytargetTimer = setTimeout("__myself.bytarget()", delay);
561
562                     __myself.bytargetCallback(bytarget);
563                 }
564                 else
565                     // if it gets here the http return code was 200 (pz2 errors are 417)
566                     // but the response was invalid, it should never occur
567                     __myself.bytargetTimer = setTimeout("__myself.bytarget()", __myself.bytargetTime / 4);
568             }
569         );
570     },
571     
572     // just for testing, probably shouldn't be here
573     showNext: function(page)
574     {
575         var step = page || 1;
576         __myself.show( ( step * __myself.currentNum ) + __myself.currentStart );     
577     },
578
579     showPrev: function(page)
580     {
581         if (__myself.currentStart == 0 )
582             return false;
583         var step = page || 1;
584         var newStart = __myself.currentStart - (step * __myself.currentNum );
585         __myself.show( newStart > 0 ? newStart : 0 );
586     },
587
588     showPage: function(pageNum)
589     {
590         //var page = pageNum || 1;
591         __myself.show(pageNum * __myself.currentNum);
592     }
593 };
594
595 /*
596 *********************************************************************************
597 ** AJAX HELPER CLASS ************************************************************
598 *********************************************************************************
599 */
600 var pzHttpRequest = function ( url, errorHandler ) {
601         this.request = null;
602         this.url = url;
603         this.errorHandler = errorHandler || null;
604         this.async = true;
605         
606         if ( window.XMLHttpRequest ) {
607             this.request = new XMLHttpRequest();
608         } else if ( window.ActiveXObject ) {
609             try {
610                 this.request = new ActiveXObject( 'Msxml2.XMLHTTP' );
611             } catch (err) {
612                 this.request = new ActiveXObject( 'Microsoft.XMLHTTP' );
613             }
614         }
615 };
616
617 pzHttpRequest.prototype = 
618 {
619     get: function ( params, callback ) 
620     {
621         this._send( 'GET', params, '', callback );
622     },
623
624     post: function ( params, data, callback )
625     {
626         this._send( 'POST', params, data, callback );
627     },
628
629     load: function ()
630     {
631         this.async = false;
632         this.request.open( 'GET', this.url, this.async );
633         this.request.send('');
634         if ( this.request.status == 200 )
635             return this.request.responseXML;
636     },
637
638     _send: function ( type, params, data, callback )
639     {
640         this.callback = callback;
641         var context = this;
642         this.async = true;
643         this.request.open( type, this._urlAppendParams(params), this.async );
644         this.request.onreadystatechange = function () {
645             context._handleResponse();
646         }
647         this.request.send(data);
648     },
649
650     _urlAppendParams: function (params)
651     {
652         var getUrl = this.url;
653
654         var sep = '?';
655         var el = params;
656         for (var key in el) {
657             if (el[key] != null) {
658                 getUrl += sep + key + '=' + encodeURI(el[key]);
659                 sep = '&';
660             }
661         }
662         return getUrl;
663     },
664
665     _handleResponse: function ()
666     {
667         if ( this.request.readyState == 4 ) { 
668             // pick up pazpr2 errors first
669             if ( this.request.responseXML 
670                 && this.request.responseXML.documentElement.nodeName == 'error'
671                 && this.request.responseXML.getElementsByTagName("error").length ) {
672                 var errAddInfo = this.request.responseXML.getElementsByTagName("error")[0].childNodes[0].nodeValue;
673                 var errMsg = this.request.responseXML.getElementsByTagName("error")[0].getAttribute("msg");
674                 var errCode = this.request.responseXML.getElementsByTagName("error")[0].getAttribute("code");
675             
676                 var err = new Error(errMsg + ': ' + errAddInfo);
677                 err.code = errCode;
678             
679                 if (this.errorHandler) {
680                     this.errorHandler(err);
681                 }
682                 else {
683                     throw err;
684                 }
685             } else if ( this.request.status == 200 ) {
686                 this.callback( this.request.responseXML );
687             } else {
688                 var err = new Error("Pz2.js: HTTP request error (AJAX). Code: " 
689                             + this.request.status + " Info: " 
690                             + this.request.statusText );
691                 err.code = 'HTTP';
692                 
693                 if (this.errorHandler) {
694                     this.errorHandler(err);
695                 }
696                 else {
697                     throw err;
698                 }
699             }
700         }
701     }
702 };
703
704 /*
705 *********************************************************************************
706 ** XML HELPER CLASS ************************************************************
707 *********************************************************************************
708 */
709
710 // DOMDocument
711
712 if ( window.ActiveXObject) {
713     var DOMDoc = document;
714 } else {
715     var DOMDoc = Document.prototype;
716 }
717
718 DOMDoc.newXmlDoc = function ( root )
719 {
720     var doc;
721
722     if (document.implementation && document.implementation.createDocument) {
723         doc = document.implementation.createDocument('', root, null);
724     } else if ( window.ActiveXObject ) {
725         doc = new ActiveXObject("MSXML2.DOMDocument");
726         doc.loadXML('<' + root + '/>');
727     } else {
728         throw new Error ('No XML support in this browser');
729     }
730
731     return doc;
732 }
733
734    
735 DOMDoc.parseXmlFromString = function ( xmlString ) 
736 {
737     var doc;
738
739     if ( window.DOMParser ) {
740         var parser = new DOMParser();
741         doc = parser.parseFromString( xmlString, "text/xml");
742     } else if ( window.ActiveXObject ) {
743         doc = new ActiveXObject("MSXML2.DOMDocument");
744         doc.loadXML( xmlString );
745     } else {
746         throw new Error ("No XML parsing support in this browser.");
747     }
748
749     return doc;
750 }
751
752 // DOMElement
753
754 Element_removeFromDoc = function (DOM_Element)
755 {
756     DOM_Element.parentNode.removeChild(DOM_Element);
757 }
758
759 Element_emptyChildren = function (DOM_Element)
760 {
761     while( DOM_Element.firstChild ) {
762         DOM_Element.removeChild( DOM_Element.firstChild )
763     }
764 }
765
766 Element_appendTransformResult = function ( DOM_Element, xmlDoc, xslDoc )
767 {
768     if ( window.XSLTProcessor ) {
769         var proc = new XSLTProcessor();
770         proc.importStylesheet( xslDoc );
771         var docFrag = false;
772         docFrag = proc.transformToFragment( xmlDoc, DOM_Element.ownerDocument );
773         DOM_Element.appendChild(docFrag);
774     } else if ( window.ActiveXObject ) {
775         DOM_Element.innerHTML = xmlDoc.transformNode( xslDoc );
776     } else {
777         alert( 'Unable to perform XSLT transformation in this browser' );
778     }
779 }
780  
781 Element_appendTextNode = function (DOM_Element, tagName, textContent )
782 {
783     var node = DOM_Element.ownerDocument.createElement(tagName);
784     var text = DOM_Element.ownerDocument.createTextNode(textContent);
785
786     DOM_Element.appendChild(node);
787     node.appendChild(text);
788
789     return node;
790 }
791
792 Element_setTextContent = function ( DOM_Element, textContent )
793 {
794     if (typeof DOM_Element.textContent !== "undefined") {
795         DOM_Element.textContent = textContent;
796     } else if (typeof DOM_Element.innerText !== "undefined" ) {
797         DOM_Element.innerText = textContent;
798     } else {
799         throw new Error("Cannot set text content of the node, no such method.");
800     }
801 }
802
803 Element_getTextContent = function (DOM_Element)
804 {
805     if ( typeof DOM_Element.textContent != 'undefined' ) {
806         return DOM_Element.textContent;
807     } else if (DOM_Element.text ) {
808         return DOM_Element.text;
809     } else {
810         throw new Error("Cannot get text content of the node, no such method.");
811     }
812 }
813
814 /*
815 *********************************************************************************
816 ** QUERY CLASS ******************************************************************
817 *********************************************************************************
818 */
819 var pzQuery = function()
820 {
821     this.simpleQuery = '';
822     this.singleFilter = null;
823     this.advTerms = new Array();
824     this.filterHash = new Array();
825     this.numTerms = 0;
826     this.filterNums = 0;
827 };
828 pzQuery.prototype = {
829     reset: function()
830     {
831         this.simpleQuery = '';
832         this.advTerms = new Array();
833         this.simpleFilter = null;
834         this.numTerms = 0;
835     },
836     clearSimpleQuery: function()
837     {
838         this.simpleQuery = '';
839     },
840     addTerm: function(field, value)
841     {
842         var term = {"field": field, "value": value};
843         this.advTerms[this.numTerms] = term;
844         this.numTerms++;
845     },
846     getTermValueByIdx: function(index)
847     {
848         return this.advTerms[index].value;
849     },
850     getTermFieldByIdx: function(index)
851     {
852         return this.advTerms[index].field;
853     },
854     /* semicolon separated list of terms for given field*/
855     getTermsByField: function(field)
856     {
857         var terms = '';
858         for(var i = 0; i < this.advTerms.length; i++)
859         {
860             if( this.advTerms[i].field == field )
861                 terms = terms + this.queryHas[i].value + ';';
862         }
863         return terms;
864     },
865     addTermsFromList: function(inputString, field)
866     {
867         var inputArr = inputString.split(';');
868         for(var i=0; i < inputArr.length; i++)
869         {
870             if(inputArr[i].length < 3) continue;
871             this.advTerms[this.numTerms] = {"field": field, "value": inputArr[i] };
872             this.numTerms++;
873         }
874     },
875     removeTermByIdx: function(index)
876     {
877         this.advTerms.splice(index, 1);
878         this.numTerms--;
879     },
880     toCCL: function()
881     {   
882         var ccl = '';
883         if( this.simpleQuery != '')
884             ccl = this.simpleQuery;
885         for(var i = 0; i < this.advTerms.length; i++)
886         {
887             if (ccl != '') ccl = ccl + ' and ';
888             ccl = ccl + this.advTerms[i].field+'="'+this.advTerms[i].value+'"';
889         }
890         return ccl;
891     },
892     addFilter: function(name, value)
893     {
894         var filter = {"name": name, "id": value };
895         this.filterHash[this.filterHash.length] = filter;
896         this.filterNums++
897         return  this.filterHash.length - 1;
898     },
899     setFilter: function(name, value)
900     {
901         this.filterHash = new Array();
902         this.filterNums = 0;
903         this.addFilter(name, value);
904     },
905     getFilter: function(index)
906     {
907         return this.filterHash[index].id;
908     },
909     getFilterName: function(index)
910     {
911         return this.filterHash[index].name;
912     },
913     removeFilter: function(index)
914     {
915         delete this.filterHash[index];
916         this.filterNums--;
917     },
918     clearFilter: function()
919     {
920         this.filterHash = new Array();
921         this.filterNums = 0;
922     },
923     getFilterString: function()
924     {
925         //temporary
926         if( this.singleFilter != null ) {
927             return 'pz:id='+this.singleFilter.id;
928         } 
929         else if( this.filterNums <= 0 ) {
930             return undefined;
931         }
932
933         var filter = 'pz:id=';
934         for(var i = 0; i < this.filterHash.length; i++)
935         {
936             if (this.filterHash[i] == undefined) continue;
937             if (filter > 'pz:id=') filter = filter + '|';            
938             filter += this.filterHash[i].id; 
939         }
940         return filter;
941     },
942     totalLength: function()
943     {
944         var simpleLength = this.simpleQuery != '' ? 1 : 0;
945         return this.advTerms.length + simpleLength;
946     },
947     clearSingleFilter: function()
948     {
949         this.singleFilter = null;
950     },
951     setSingleFilter: function(name, value)
952     {
953         this.singleFilter = {"name": name, "id": value };
954     },
955     getSingleFilterName: function()
956     {
957         return this.singleFilter.name;
958     }
959 }
960
961 }