a799b4090884e0deafd23a932e2d3ee1caac9f71
[pazpar2-moved-to-github.git] / js / pz2.js
1 /*
2  * $Id$
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: Array with parameters has to be supplied."); 
33
34     //supported pazpar2's protocol version
35     this.windowid = paramArray.windowid || window.name;
36     this.suppProtoVer = '1';
37     if (typeof paramArray.pazpar2path != "undefined")
38         this.pz2String = paramArray.pazpar2path;
39     else
40         this.pz2String = "/pazpar2/search.pz2";
41     this.useSessions = true;
42     
43     this.stylesheet = paramArray.detailstylesheet || null;
44     //load stylesheet if required in async mode
45     if( this.stylesheet ) {
46         var context = this;
47         var request = new pzHttpRequest( this.stylesheet );
48         request.get( {}, function ( doc ) { context.xslDoc = doc; } );
49     }
50     
51     this.errorHandler = paramArray.errorhandler || null;
52     this.showResponseType = paramArray.showResponseType || "xml";
53     
54     // function callbacks
55     this.initCallback = paramArray.oninit || null;
56     this.statCallback = paramArray.onstat || null;
57     this.showCallback = paramArray.onshow || null;
58     this.termlistCallback = paramArray.onterm || null;
59     this.recordCallback = paramArray.onrecord || null;
60     this.bytargetCallback = paramArray.onbytarget || null;
61     this.resetCallback = paramArray.onreset || null;
62
63     // termlist keys
64     this.termKeys = paramArray.termlist || "subject";
65     
66     // some configurational stuff
67     this.keepAlive = 50000;
68     
69     if ( paramArray.keepAlive < this.keepAlive )
70         this.keepAlive = paramArray.keepAlive;
71
72     this.sessionID = null;
73     this.serviceId = paramArray.serviceId || null;
74     this.initStatusOK = false;
75     this.pingStatusOK = false;
76     this.searchStatusOK = false;
77     this.mergekey = paramArray.mergekey || null;
78     this.rank = paramArray.rank || null;
79     
80     // for sorting
81     this.currentSort = "relevance";
82
83     // where are we?
84     this.currentStart = 0;
85     // currentNum can be overwritten in show 
86     this.currentNum = 20;
87
88     // last full record retrieved
89     this.currRecID = null;
90     
91     // current query
92     this.currQuery = null;
93
94     //current raw record offset
95     this.currRecOffset = null;
96
97     //timers
98     this.pingTimer = null;
99     this.statTime = paramArray.stattime || 1000;
100     this.statTimer = null;
101     this.termTime = paramArray.termtime || 1000;
102     this.termTimer = null;
103     this.showTime = paramArray.showtime || 1000;
104     this.showTimer = null;
105     this.showFastCount = 4;
106     this.bytargetTime = paramArray.bytargettime || 1000;
107     this.bytargetTimer = null;
108     this.recordTime = paramArray.recordtime || 500;
109     this.recordTimer = null;
110
111     // counters for each command and applied delay
112     this.dumpFactor = 500;
113     this.showCounter = 0;
114     this.termCounter = 0;
115     this.statCounter = 0;
116     this.bytargetCounter = 0;
117     this.recordCounter = 0;
118
119     // active clients, updated by stat and show
120     // might be an issue since bytarget will poll accordingly
121     this.activeClients = 1;
122
123     // if in proxy mode no need to init
124     if (paramArray.usesessions != undefined) {
125          this.useSessions = paramArray.usesessions;
126         this.initStatusOK = true;
127     }
128     // else, auto init session or wait for a user init?
129     if (this.useSessions && paramArray.autoInit !== false) {
130         this.init(this.sessionID, this.serviceId);
131     }
132     // Version parameter
133     this.version = paramArray.version || null;
134 };
135
136 pz2.prototype = 
137 {
138     //error handler for async error throws
139    throwError: function (errMsg, errCode)
140    {
141         var err = new Error(errMsg);
142         if (errCode) err.code = errCode;
143                 
144         if (this.errorHandler) {
145             this.errorHandler(err);
146         }
147         else {
148             throw err;
149         }
150    },
151
152     // stop activity by clearing tiemouts 
153    stop: function ()
154    {
155        clearTimeout(this.statTimer);
156        clearTimeout(this.showTimer);
157        clearTimeout(this.termTimer);
158        clearTimeout(this.bytargetTimer);
159     },
160     
161     // reset status variables
162     reset: function ()
163     {   
164         if ( this.useSessions ) {
165             this.sessionID = null;
166             this.initStatusOK = false;
167             this.pingStatusOK = false;
168             clearTimeout(this.pingTimer);
169         }
170         this.searchStatusOK = false;
171         this.stop();
172             
173         if ( this.resetCallback )
174                 this.resetCallback(this.windowid);
175     },
176
177     init: function (sessionId, serviceId) 
178     {
179         this.reset();
180         
181         // session id as a param
182         if (sessionId && this.useSessions ) {
183             this.initStatusOK = true;
184             this.sessionID = sessionId;
185             this.ping();
186         // old school direct pazpar2 init
187         } else if (this.useSessions) {
188             var context = this;
189             var request = new pzHttpRequest(this.pz2String, this.errorHandler);
190             var opts = {'command' : 'init'};
191             if (serviceId) opts.service = serviceId;
192             request.safeGet(
193                 opts,
194                 function(data) {
195                     if ( data.getElementsByTagName("status")[0]
196                             .childNodes[0].nodeValue == "OK" ) {
197                         if ( data.getElementsByTagName("protocol")[0]
198                                 .childNodes[0].nodeValue 
199                             != context.suppProtoVer )
200                             throw new Error(
201                                 "Server's protocol not supported by the client"
202                             );
203                         context.initStatusOK = true;
204                         context.sessionID = 
205                             data.getElementsByTagName("session")[0]
206                                 .childNodes[0].nodeValue;
207                         if (data.getElementsByTagName("keepAlive").length > 0) {
208                             context.keepAlive = data.getElementsByTagName("keepAlive")[0].childNodes[0].nodeValue;
209                         }
210                         context.pingTimer =
211                             setTimeout(
212                                 function () {
213                                     context.ping();
214                                 },
215                                 context.keepAlive
216                             );
217                         if ( context.initCallback )
218                             context.initCallback(this.windowid);
219                     }
220                     else
221                         context.throwError('Init failed. Malformed WS resonse.',
222                                             110);
223                 }
224             );
225         // when through proxy no need to init
226         } else {
227             this.initStatusOK = true;
228         }
229     },
230     // no need to ping explicitly
231     ping: function () 
232     {
233         // pinging only makes sense when using pazpar2 directly
234         if( !this.initStatusOK || !this.useSessions )
235             throw new Error(
236             'Pz2.js: Ping not allowed (proxy mode) or session not initialized.'
237             );
238         var context = this;
239
240         clearTimeout(context.pingTimer);
241
242         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
243         request.safeGet(
244             { "command": "ping", "session": this.sessionID, "windowid" : this.windowid },
245             function(data) {
246                 if ( data.getElementsByTagName("status")[0]
247                         .childNodes[0].nodeValue == "OK" ) {
248                     context.pingStatusOK = true;
249                     context.pingTimer =
250                         setTimeout(
251                             function () {
252                                 context.ping();
253                             },
254                             context.keepAlive
255                         );
256                 }
257                 else
258                     context.throwError('Ping failed. Malformed WS resonse.',
259                                         111);
260             }
261         );
262     },
263     search: function (query, num, sort, filter, showfrom, addParamsArr)
264     {
265         clearTimeout(this.statTimer);
266         clearTimeout(this.showTimer);
267         clearTimeout(this.termTimer);
268         clearTimeout(this.bytargetTimer);
269         
270         this.showCounter = 0;
271         this.termCounter = 0;
272         this.bytargetCounter = 0;
273         this.statCounter = 0;
274         this.activeClients = 1;
275         
276         // no proxy mode
277         if( !this.initStatusOK )
278             throw new Error('Pz2.js: session not initialized.');
279         
280         if( query !== undefined )
281             this.currQuery = query;
282         else
283             throw new Error("Pz2.js: no query supplied to the search command.");
284         
285         if ( showfrom !== undefined )
286             var start = showfrom;
287         else
288             var start = 0;
289
290         var searchParams = { 
291           "command": "search",
292           "query": this.currQuery, 
293           "session": this.sessionID,
294           "windowid" : this.windowid
295         };
296         
297         if( sort !== undefined ) {
298             this.currentSort = sort;
299             searchParams["sort"] = sort;
300         }
301         if (filter !== undefined) searchParams["filter"] = filter;
302         if (this.mergekey) searchParams["mergekey"] = this.mergekey;
303         if (this.rank) searchParams["rank"] = this.rank;
304
305         // copy additional parmeters, do not overwrite
306         if (addParamsArr != undefined) {
307             for (var prop in addParamsArr) {
308                 if (!searchParams.hasOwnProperty(prop))
309                     searchParams[prop] = addParamsArr[prop];
310             }
311         }
312         
313         var context = this;
314         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
315         request.safeGet(
316             searchParams,
317             function(data) {
318                 if ( data.getElementsByTagName("status")[0]
319                         .childNodes[0].nodeValue == "OK" ) {
320                     context.searchStatusOK = true;
321                     //piggyback search
322                     context.show(start, num, sort);
323                     if (context.statCallback)
324                         context.stat();
325                     if (context.termlistCallback)
326                         context.termlist();
327                     if (context.bytargetCallback)
328                         context.bytarget();
329                 }
330                 else
331                     context.throwError('Search failed. Malformed WS resonse.',
332                                         112);
333             }
334         );
335     },
336     stat: function()
337     {
338         if( !this.initStatusOK )
339             throw new Error('Pz2.js: session not initialized.');
340         
341         // if called explicitly takes precedence
342         clearTimeout(this.statTimer);
343         
344         var context = this;
345         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
346         request.safeGet(
347             { "command": "stat", "session": this.sessionID, "windowid" : this.windowid },
348             function(data) {
349                 if ( data.getElementsByTagName("stat") ) {
350                     var activeClients = 
351                         Number( data.getElementsByTagName("activeclients")[0]
352                                     .childNodes[0].nodeValue );
353                     context.activeClients = activeClients;
354
355                     var stat = Element_parseChildNodes(data.documentElement);
356
357                     context.statCounter++;
358                     var delay = context.statTime 
359                         + context.statCounter * context.dumpFactor;
360                     
361                     if ( activeClients > 0 )
362                         context.statTimer = 
363                             setTimeout( 
364                                 function () {
365                                     context.stat();
366                                 },
367                                 delay
368                             );
369                     context.statCallback(stat, this.windowid);
370                 }
371                 else
372                     context.throwError('Stat failed. Malformed WS resonse.',
373                                         113);
374             }
375         );
376     },
377     show: function(start, num, sort, query_state)
378     {
379         if( !this.searchStatusOK && this.useSessions )
380             throw new Error(
381                 'Pz2.js: show command has to be preceded with a search command.'
382             );
383         
384         // if called explicitly takes precedence
385         clearTimeout(this.showTimer);
386         
387         if( sort !== undefined )
388             this.currentSort = sort;
389         if( start !== undefined )
390             this.currentStart = Number( start );
391         if( num !== undefined )
392             this.currentNum = Number( num );
393
394         var context = this;
395         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
396         var requestParameters = 
397           {
398               "command": "show", 
399               "session": this.sessionID, 
400               "start": this.currentStart,
401               "num": this.currentNum, 
402               "sort": this.currentSort, 
403               "block": 1,
404               "type": this.showResponseType,
405               "windowid" : this.windowid
406           };
407         if (query_state)
408           requestParameters["query-state"] = query_state;
409         if (this.version && this.version > 0)
410             requestParameters["version"] = this.version;
411         request.safeGet(
412           requestParameters,
413           function(data, type) {
414             var show = null;
415             var activeClients = 0;
416             if (type === "json") {
417               show = {};
418               activeClients = Number(data.activeclients[0]);
419               show.activeclients = activeClients;
420               show.merged = Number(data.merged[0]);
421               show.total = Number(data.total[0]);
422               show.start = Number(data.start[0]);
423               show.num = Number(data.num[0]);
424               show.hits = data.hit;
425             } else if (data.getElementsByTagName("status")[0]
426                   .childNodes[0].nodeValue == "OK") {
427                 // first parse the status data send along with records
428                 // this is strictly bound to the format
429                 activeClients = 
430                   Number(data.getElementsByTagName("activeclients")[0]
431                       .childNodes[0].nodeValue);
432                 show = {
433                   "activeclients": activeClients,
434                   "merged": 
435                     Number( data.getElementsByTagName("merged")[0]
436                         .childNodes[0].nodeValue ),
437                   "total": 
438                     Number( data.getElementsByTagName("total")[0]
439                         .childNodes[0].nodeValue ),
440                   "start": 
441                     Number( data.getElementsByTagName("start")[0]
442                         .childNodes[0].nodeValue ),
443                   "num": 
444                     Number( data.getElementsByTagName("num")[0]
445                         .childNodes[0].nodeValue ),
446                   "hits": []
447                 };
448                 // parse all the first-level nodes for all <hit> tags
449                 var hits = data.getElementsByTagName("hit");
450                 for (i = 0; i < hits.length; i++)
451                   show.hits[i] = Element_parseChildNodes(hits[i]);
452             } else {
453               context.throwError('Show failed. Malformed WS resonse.',
454                   114);
455             };
456             
457             var approxNode = data.getElementsByTagName("approximation");
458             if (approxNode && approxNode[0] && approxNode[0].childNodes[0] && approxNode[0].childNodes[0].nodeValue)
459                 show['approximation'] = 
460                   Number( approxNode[0].childNodes[0].nodeValue);
461               
462
463               data.getElementsByTagName("")
464             context.activeClients = activeClients; 
465             context.showCounter++;
466             var delay = context.showTime;
467             if (context.showCounter > context.showFastCount)
468               delay += context.showCounter * context.dumpFactor;
469             if ( activeClients > 0 )
470               context.showTimer = setTimeout(
471                 function () {
472                   context.show();
473                 }, 
474                 delay);
475               context.showCallback(show, this.windowid);
476           }
477         );
478     },
479     record: function(id, offset, syntax, handler)
480     {
481         // we may call record with no previous search if in proxy mode
482         if(!this.searchStatusOK && this.useSessions)
483            throw new Error(
484             'Pz2.js: record command has to be preceded with a search command.'
485             );
486         
487         if( id !== undefined )
488             this.currRecID = id;
489         
490         var recordParams = { 
491             "command": "record", 
492             "session": this.sessionID,
493             "id": this.currRecID,
494             "windowid" : this.windowid
495         };
496         
497         this.currRecOffset = null;
498         if (offset != undefined) {
499             recordParams["offset"] = offset;
500             this.currRecOffset = offset;
501         }
502
503         if (syntax != undefined)
504             recordParams['syntax'] = syntax;
505
506         //overwrite default callback id needed
507         var callback = this.recordCallback;
508         var args = undefined;
509         if (handler != undefined) {
510             callback = handler['callback'];
511             args = handler['args'];
512         }
513         
514         var context = this;
515         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
516
517         request.safeGet(
518             recordParams,
519             function(data) {
520                 var recordNode;
521                 var record;                                
522                 //raw record
523                 if (context.currRecOffset !== null) {
524                     record = new Array();
525                     record['xmlDoc'] = data;
526                     record['offset'] = context.currRecOffset;
527                     callback(record, args, this.windowid);
528                 //pz2 record
529                 } else if ( recordNode = 
530                     data.getElementsByTagName("record")[0] ) {
531                     // if stylesheet was fetched do not parse the response
532                     if ( context.xslDoc ) {
533                         record = new Array();
534                         record['xmlDoc'] = data;
535                         record['xslDoc'] = context.xslDoc;
536                         record['recid'] = 
537                             recordNode.getElementsByTagName("recid")[0]
538                                 .firstChild.nodeValue;
539                     //parse record
540                     } else {
541                         record = Element_parseChildNodes(recordNode);
542                     }    
543                     var activeClients = 
544                        Number( data.getElementsByTagName("activeclients")[0]
545                                 .childNodes[0].nodeValue );
546                     context.activeClients = activeClients; 
547                     context.recordCounter++;
548                     var delay = context.recordTime + context.recordCounter * context.dumpFactor;
549                     if ( activeClients > 0 )
550                         context.recordTimer = 
551                            setTimeout ( 
552                                function() {
553                                   context.record(id, offset, syntax, handler);
554                                   },
555                                   delay
556                                );                                    
557                     callback(record, args, this.windowid);
558                 }
559                 else
560                     context.throwError('Record failed. Malformed WS resonse.',
561                                         115);
562             }
563         );
564     },
565
566     termlist: function()
567     {
568         if( !this.searchStatusOK && this.useSessions )
569             throw new Error(
570             'Pz2.js: termlist command has to be preceded with a search command.'
571             );
572
573         // if called explicitly takes precedence
574         clearTimeout(this.termTimer);
575         
576         var context = this;
577         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
578         request.safeGet(
579             { 
580                 "command": "termlist", 
581                 "session": this.sessionID, 
582                 "name": this.termKeys,
583                 "windowid" : this.windowid, 
584                 "version" : this.version
585         
586             },
587             function(data) {
588                 if ( data.getElementsByTagName("termlist") ) {
589                     var activeClients = 
590                         Number( data.getElementsByTagName("activeclients")[0]
591                                     .childNodes[0].nodeValue );
592                     context.activeClients = activeClients;
593                     var termList = { "activeclients":  activeClients };
594                     var termLists = data.getElementsByTagName("list");
595                     //for each termlist
596                     for (i = 0; i < termLists.length; i++) {
597                         var listName = termLists[i].getAttribute('name');
598                         termList[listName] = new Array();
599                         var terms = termLists[i].getElementsByTagName('term');
600                         //for each term in the list
601                         for (j = 0; j < terms.length; j++) { 
602                             var term = {
603                                 "name": 
604                                     (terms[j].getElementsByTagName("name")[0]
605                                         .childNodes.length 
606                                     ? terms[j].getElementsByTagName("name")[0]
607                                         .childNodes[0].nodeValue
608                                     : 'ERROR'),
609                                 "freq": 
610                                     terms[j]
611                                     .getElementsByTagName("frequency")[0]
612                                     .childNodes[0].nodeValue || 'ERROR'
613                             };
614
615                             // Only for xtargets: id, records, filtered
616                             var termIdNode = 
617                                 terms[j].getElementsByTagName("id");
618                             if(terms[j].getElementsByTagName("id").length)
619                                 term["id"] = 
620                                     termIdNode[0].childNodes[0].nodeValue;
621                             termList[listName][j] = term;
622
623                             var recordsNode  = terms[j].getElementsByTagName("records");
624                             if (recordsNode && recordsNode.length)
625                                 term["records"] = recordsNode[0].childNodes[0].nodeValue;
626                               
627                             var filteredNode  = terms[j].getElementsByTagName("filtered");
628                             if (filteredNode && filteredNode.length)
629                                 term["filtered"] = filteredNode[0].childNodes[0].nodeValue;
630                               
631                         }
632                     }
633
634                     context.termCounter++;
635                     var delay = context.termTime 
636                         + context.termCounter * context.dumpFactor;
637                     if ( activeClients > 0 )
638                         context.termTimer = 
639                             setTimeout(
640                                 function () {
641                                     context.termlist();
642                                 }, 
643                                 delay
644                             );
645                    
646                     context.termlistCallback(termList, this.windowid);
647                 }
648                 else
649                     context.throwError('Termlist failed. Malformed WS resonse.',
650                                         116);
651             }
652         );
653
654     },
655     bytarget: function()
656     {
657         if( !this.initStatusOK && this.useSessions )
658             throw new Error(
659             'Pz2.js: bytarget command has to be preceded with a search command.'
660             );
661         
662         // no need to continue
663         if( !this.searchStatusOK )
664             return;
665
666         // if called explicitly takes precedence
667         clearTimeout(this.bytargetTimer);
668         
669         var context = this;
670         var request = new pzHttpRequest(this.pz2String, this.errorHandler);
671         request.safeGet(
672             { 
673                 "command": "bytarget", 
674                 "session": this.sessionID, 
675                 "block": 1,
676                 "windowid" : this.windowid,
677                 "version" : this.version
678             },
679             function(data) {
680                 if ( data.getElementsByTagName("status")[0]
681                         .childNodes[0].nodeValue == "OK" ) {
682                     var targetNodes = data.getElementsByTagName("target");
683                     var bytarget = new Array();
684                     for ( i = 0; i < targetNodes.length; i++) {
685                         bytarget[i] = new Array();
686                         for( j = 0; j < targetNodes[i].childNodes.length; j++ ) {
687                             if ( targetNodes[i].childNodes[j].nodeType 
688                                 == Node.ELEMENT_NODE ) {
689                                 var nodeName = 
690                                     targetNodes[i].childNodes[j].nodeName;
691                                 if (targetNodes[i].childNodes[j].firstChild != null) 
692                                 {
693                                     var nodeText = targetNodes[i].childNodes[j]
694                                         .firstChild.nodeValue;
695                                     bytarget[i][nodeName] = nodeText;
696                                 }
697                                 else { 
698                                     bytarget[i][nodeName] = "";  
699                                 }
700
701
702                             }
703                         }
704                         if (bytarget[i]["state"]=="Client_Disconnected") {
705                           bytarget[i]["hits"] = "Error";
706                         } else if (bytarget[i]["state"]=="Client_Error") {
707                           bytarget[i]["hits"] = "Error";                          
708                         } else if (bytarget[i]["state"]=="Client_Working") {
709                           bytarget[i]["hits"] = "...";
710                         }
711                         if (bytarget[i].diagnostic == "1") {
712                           bytarget[i].diagnostic = "Permanent system error";
713                         } else if (bytarget[i].diagnostic == "2") {
714                           bytarget[i].diagnostic = "Temporary system error";
715                         } 
716                         var targetsSuggestions = targetNodes[i].getElementsByTagName("suggestions");
717                         if (targetsSuggestions != undefined && targetsSuggestions.length>0) {
718                           var suggestions = targetsSuggestions[0];
719                           bytarget[i]["suggestions"] = Element_parseChildNodes(suggestions);
720                         }
721                     }
722                     
723                     context.bytargetCounter++;
724                     var delay = context.bytargetTime 
725                         + context.bytargetCounter * context.dumpFactor;
726                     if ( context.activeClients > 0 )
727                         context.bytargetTimer = 
728                             setTimeout(
729                                 function () {
730                                     context.bytarget();
731                                 }, 
732                                 delay
733                             );
734
735                     context.bytargetCallback(bytarget, this.windowid);
736                 }
737                 else
738                     context.throwError('Bytarget failed. Malformed WS resonse.',
739                                         117);
740             }
741         );
742     },
743     
744     // just for testing, probably shouldn't be here
745     showNext: function(page)
746     {
747         var step = page || 1;
748         this.show( ( step * this.currentNum ) + this.currentStart );     
749     },
750
751     showPrev: function(page)
752     {
753         if (this.currentStart == 0 )
754             return false;
755         var step = page || 1;
756         var newStart = this.currentStart - (step * this.currentNum );
757         this.show( newStart > 0 ? newStart : 0 );
758     },
759
760     showPage: function(pageNum)
761     {
762         //var page = pageNum || 1;
763         this.show(pageNum * this.currentNum);
764     }
765 };
766
767 /*
768 ********************************************************************************
769 ** AJAX HELPER CLASS ***********************************************************
770 ********************************************************************************
771 */
772 var pzHttpRequest = function (url, errorHandler, cookieDomain) {
773         this.maxUrlLength = 2048;
774         this.request = null;
775         this.url = url;
776         this.errorHandler = errorHandler || null;
777         this.async = true;
778         this.requestHeaders = {};
779         this.isXDR = false;
780         this.domainRegex = /https?:\/\/([^:/]+).*/;
781         this.cookieDomain = cookieDomain || null;
782
783         var xhr = new XMLHttpRequest();
784         var domain = this._getDomainFromUrl(url);
785         if ("withCredentials" in xhr) {
786           // XHR for Chrome/Firefox/Opera/Safari.
787         } else if (domain && this._isCrossDomain(domain) &&
788             typeof XDomainRequest != "undefined") {
789           // use XDR (IE7/8) when no other way
790           xhr = new XDomainRequest();
791           this.isXDR = true;
792         } else {
793           // CORS not supported.
794         }
795         this.request = xhr;
796 };
797
798
799 pzHttpRequest.prototype = 
800 {
801     safeGet: function ( params, callback )
802     {
803         var encodedParams =  this.encodeParams(params);
804         var url = this._urlAppendParams(encodedParams);
805         if (url.length >= this.maxUrlLength) {
806             this.requestHeaders["Content-Type"]
807                 = "application/x-www-form-urlencoded";
808             this._send( 'POST', this.url, encodedParams, callback );
809         } else {
810             this._send( 'GET', url, '', callback );
811         }
812     },
813
814     get: function ( params, callback ) 
815     {
816         this._send( 'GET', this._urlAppendParams(this.encodeParams(params)), 
817             '', callback );
818     },
819
820     post: function ( params, data, callback )
821     {
822         this._send( 'POST', this._urlAppendParams(this.encodeParams(params)), 
823             data, callback );
824     },
825
826     load: function ()
827     {
828         this.async = false;
829         this.request.open( 'GET', this.url, this.async );
830         this.request.send('');
831         if ( this.request.status == 200 )
832             return this.request.responseXML;
833     },
834
835     encodeParams: function (params)
836     {
837         var sep = "";
838         var encoded = "";
839         for (var key in params) {
840             if (params[key] != null) {
841                 encoded += sep + key + '=' + encodeURIComponent(params[key]);
842                 sep = '&';
843             }
844         }
845         return encoded;
846     },
847
848     _getDomainFromUrl: function (url)
849     {
850       if (this.cookieDomain) return this.cookieDomain; //explicit cookie domain
851       var m = this.domainRegex.exec(url);
852       return (m && m.length > 1) ? m[1] : null;
853     },
854
855     _strEndsWith: function (str, suffix) 
856     {
857       return str.indexOf(suffix, str.length - suffix.length) !== -1;
858     },
859
860     _isCrossDomain: function (domain)
861     {
862       if (this.cookieDomain) return true; //assume xdomain is cookie domain set
863       return !this._strEndsWith(domain, document.domain); 
864     },
865
866     getCookie: function (sKey) {
867       return decodeURI(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" 
868         + encodeURI(sKey).replace(/[\-\.\+\*]/g, "\\$&") 
869         + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
870     },
871
872     setCookie: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
873       if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { 
874         return false; 
875       }
876       var sExpires = "";
877       if (vEnd) {
878         switch (vEnd.constructor) {
879           case Number:
880             sExpires = vEnd === Infinity 
881               ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" 
882               : "; max-age=" + vEnd;
883             break;
884           case String:
885             sExpires = "; expires=" + vEnd;
886             break;
887           case Date:
888             sExpires = "; expires=" + vEnd.toGMTString();
889             break;
890         }
891       }
892       document.cookie = encodeURI(sKey) + "=" + encodeURI(sValue) 
893         + sExpires 
894         + (sDomain ? "; domain=" + sDomain : "") 
895         + (sPath ? "; path=" + sPath : "") 
896         + (bSecure ? "; secure" : "");
897       return true;
898     },
899     
900     _send: function ( type, url, data, callback)
901     {
902         var context = this;
903         this.callback = callback;
904         this.async = true;
905         //we never do withCredentials, so if it's CORS and we have
906         //session cookie, resend it
907         var domain = this._getDomainFromUrl(url);
908         if (domain && this._isCrossDomain(domain) &&
909             this.getCookie(domain+":SESSID")) {
910           //rewrite the URL
911           var sessparam = ';jsessionid=' + this.getCookie(domain+":SESSID");
912           var q = url.indexOf('?');
913           if (q == -1) {
914             url += sessparam;            
915           } else {
916             url = url.substring(0, q) + sessparam + url.substring(q);
917           }
918         }
919         this.request.open( type, url, this.async );
920         if (!this.isXDR) {
921           //setting headers is only allowed with XHR
922           for (var key in this.requestHeaders)
923             this.request.setRequestHeader(key, this.requestHeaders[key]);
924         }
925         if (this.isXDR) {
926           this.request.onload = function () {
927             //fake XHR props
928             context.request.status = 200;
929             context.request.readyState = 4;
930             //handle
931             context._handleResponse(url);
932           }
933           this.request.onerror = function () {
934             //fake XHR props
935             context.request.status = 417; //not really, but what can we do
936             context.request.readyState = 4;
937             //handle
938             context._handleResponse(url);
939           }
940         } else {
941           this.request.onreadystatechange = function () {
942             context._handleResponse(url); /// url used ONLY for error reporting
943           }
944         }
945         this.request.send(data);
946     },
947
948     _urlAppendParams: function (encodedParams)
949     {
950         if (encodedParams)
951             return this.url + "?" + encodedParams;
952         else
953             return this.url;
954     },
955
956     _handleResponse: function (requestUrl)
957     {
958         if ( this.request.readyState == 4 ) { 
959             // pick up appplication errors first
960             var errNode = null;
961             // xdomainreq does not have responseXML
962             if (this.isXDR) {
963               if (this.request.contentType.match(/\/xml/)){                
964                 var dom = new ActiveXObject('Microsoft.XMLDOM');
965                 dom.async = false;                
966                 dom.loadXML(this.request.responseText);
967                 this.request.responseXML = dom;
968               } else {
969                 this.request.responseXML = null;
970               }
971             }
972             if (this.request.responseXML &&
973                 (errNode = this.request.responseXML.documentElement)
974                 && errNode.nodeName == 'error') {
975                 var errMsg = errNode.getAttribute("msg");
976                 var errCode = errNode.getAttribute("code");
977                 var errAddInfo = '';
978                 if (errNode.childNodes.length)
979                     errAddInfo = ': ' + errNode.childNodes[0].nodeValue;
980                            
981                 var err = new Error(errMsg + errAddInfo);
982                 err.code = errCode;
983             
984                 if (this.errorHandler) {
985                     this.errorHandler(err);
986                 }
987                 else {
988                     throw err;
989                 }
990             } 
991             else if (this.request.status == 200 && 
992                      this.request.responseXML === null) {
993               if (this.request.responseText !== null) {
994                 //assume JSON
995                         var json = null; 
996                         var text = this.request.responseText;
997                         if (typeof window.JSON == "undefined") {
998                           json = eval("(" + text + ")");
999                 } else { 
1000                           try {
1001                             json = JSON.parse(text);
1002                           } catch (e) {
1003                   }
1004                         } 
1005                         this.callback(json, "json");
1006               } else {
1007                 var err = new Error("XML/Text response is empty but no error " +
1008                                     "for " + requestUrl);
1009                 err.code = -1;
1010                 if (this.errorHandler) {
1011                     this.errorHandler(err);
1012                 } else {
1013                     throw err;
1014                 }
1015               }
1016             } else if (this.request.status == 200) {
1017                 //set cookie manually only if cross-domain
1018                 var domain = this._getDomainFromUrl(requestUrl);
1019                 if (domain && this._isCrossDomain(domain)) {
1020                   var jsessionId = this.request.responseXML
1021                     .documentElement.getAttribute('jsessionId');
1022                   if (jsessionId)                  
1023                     this.setCookie(domain+":SESSID", jsessionId);
1024                 }
1025                 this.callback(this.request.responseXML);
1026             } else {
1027                 var err = new Error("HTTP response not OK: " 
1028                             + this.request.status + " - " 
1029                             + this.request.statusText );
1030                 err.code = '00' + this.request.status;        
1031                 if (this.errorHandler) {
1032                     this.errorHandler(err);
1033                 }
1034                 else {
1035                     throw err;
1036                 }
1037             }
1038         }
1039     }
1040 };
1041
1042 /*
1043 ********************************************************************************
1044 ** XML HELPER FUNCTIONS ********************************************************
1045 ********************************************************************************
1046 */
1047
1048 // DOMDocument
1049
1050 if ( window.ActiveXObject) {
1051     var DOMDoc = document;
1052 } else {
1053     var DOMDoc = Document.prototype;
1054 }
1055
1056 DOMDoc.newXmlDoc = function ( root )
1057 {
1058     var doc;
1059
1060     if (document.implementation && document.implementation.createDocument) {
1061         doc = document.implementation.createDocument('', root, null);
1062     } else if ( window.ActiveXObject ) {
1063         doc = new ActiveXObject("MSXML2.DOMDocument");
1064         doc.loadXML('<' + root + '/>');
1065     } else {
1066         throw new Error ('No XML support in this browser');
1067     }
1068
1069     return doc;
1070 }
1071
1072    
1073 DOMDoc.parseXmlFromString = function ( xmlString ) 
1074 {
1075     var doc;
1076
1077     if ( window.DOMParser ) {
1078         var parser = new DOMParser();
1079         doc = parser.parseFromString( xmlString, "text/xml");
1080     } else if ( window.ActiveXObject ) {
1081         doc = new ActiveXObject("MSXML2.DOMDocument");
1082         doc.loadXML( xmlString );
1083     } else {
1084         throw new Error ("No XML parsing support in this browser.");
1085     }
1086
1087     return doc;
1088 }
1089
1090 DOMDoc.transformToDoc = function (xmlDoc, xslDoc)
1091 {
1092     if ( window.XSLTProcessor ) {
1093         var proc = new XSLTProcessor();
1094         proc.importStylesheet( xslDoc );
1095         return proc.transformToDocument(xmlDoc);
1096     } else if ( window.ActiveXObject ) {
1097         return document.parseXmlFromString(xmlDoc.transformNode(xslDoc));
1098     } else {
1099         alert( 'Unable to perform XSLT transformation in this browser' );
1100     }
1101 }
1102  
1103 // DOMElement
1104
1105 Element_removeFromDoc = function (DOM_Element)
1106 {
1107     DOM_Element.parentNode.removeChild(DOM_Element);
1108 }
1109
1110 Element_emptyChildren = function (DOM_Element)
1111 {
1112     while( DOM_Element.firstChild ) {
1113         DOM_Element.removeChild( DOM_Element.firstChild )
1114     }
1115 }
1116
1117 Element_appendTransformResult = function ( DOM_Element, xmlDoc, xslDoc )
1118 {
1119     if ( window.XSLTProcessor ) {
1120         var proc = new XSLTProcessor();
1121         proc.importStylesheet( xslDoc );
1122         var docFrag = false;
1123         docFrag = proc.transformToFragment( xmlDoc, DOM_Element.ownerDocument );
1124         DOM_Element.appendChild(docFrag);
1125     } else if ( window.ActiveXObject ) {
1126         DOM_Element.innerHTML = xmlDoc.transformNode( xslDoc );
1127     } else {
1128         alert( 'Unable to perform XSLT transformation in this browser' );
1129     }
1130 }
1131  
1132 Element_appendTextNode = function (DOM_Element, tagName, textContent )
1133 {
1134     var node = DOM_Element.ownerDocument.createElement(tagName);
1135     var text = DOM_Element.ownerDocument.createTextNode(textContent);
1136
1137     DOM_Element.appendChild(node);
1138     node.appendChild(text);
1139
1140     return node;
1141 }
1142
1143 Element_setTextContent = function ( DOM_Element, textContent )
1144 {
1145     if (typeof DOM_Element.textContent !== "undefined") {
1146         DOM_Element.textContent = textContent;
1147     } else if (typeof DOM_Element.innerText !== "undefined" ) {
1148         DOM_Element.innerText = textContent;
1149     } else {
1150         throw new Error("Cannot set text content of the node, no such method.");
1151     }
1152 }
1153
1154 Element_getTextContent = function (DOM_Element)
1155 {
1156     if ( typeof DOM_Element.textContent != 'undefined' ) {
1157         return DOM_Element.textContent;
1158     } else if (typeof DOM_Element.text != 'undefined') {
1159         return DOM_Element.text;
1160     } else {
1161         throw new Error("Cannot get text content of the node, no such method.");
1162     }
1163 }
1164
1165 Element_parseChildNodes = function (node)
1166 {
1167     var parsed = {};
1168     var hasChildElems = false;
1169     var textContent = '';
1170
1171     if (node.hasChildNodes()) {
1172         var children = node.childNodes;
1173         for (var i = 0; i < children.length; i++) {
1174             var child = children[i];
1175             switch (child.nodeType) {
1176               case Node.ELEMENT_NODE:
1177                 hasChildElems = true;
1178                 var nodeName = child.nodeName; 
1179                 if (!(nodeName in parsed))
1180                     parsed[nodeName] = [];
1181                 parsed[nodeName].push(Element_parseChildNodes(child));
1182                 break;
1183               case Node.TEXT_NODE:
1184                 textContent += child.nodeValue;
1185                 break;
1186               case Node.CDATA_SECTION_NODE:
1187                 textContent += child.nodeValue;
1188                 break;
1189             }
1190         }
1191     }
1192
1193     var attrs = node.attributes;
1194     for (var i = 0; i < attrs.length; i++) {
1195         hasChildElems = true;
1196         var attrName = '@' + attrs[i].nodeName;
1197         var attrValue = attrs[i].nodeValue;
1198         parsed[attrName] = attrValue;
1199     }
1200
1201     // if no nested elements/attrs set value to text
1202     if (hasChildElems)
1203       parsed['#text'] = textContent;
1204     else
1205       parsed = textContent;
1206     
1207     return parsed;
1208 }
1209
1210 /* do not remove trailing bracket */
1211 }