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