2 ** $Id: pz2.js,v 1.41 2007-07-02 10:16:46 jakub Exp $
3 ** pz2.js - pazpar2's javascript client library.
6 //since explorer is flawed
8 window.Node = new Object();
10 Node.ATTRIBUTE_NODE = 2;
12 Node.CDATA_SECTION_NODE = 4;
13 Node.ENTITY_REFERENCE_NODE = 5;
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;
23 // prevent execution of more than once
24 if(typeof window.pz2 == "undefined") {
25 window.undefined = window.undefined;
27 var pz2 = function(paramArray) {
31 //supported pazpar2's protocol version
32 __myself.suppProtoVer = '1';
33 __myself.pz2String = paramArray.pazpar2path || "search.pz2";
34 __myself.stylesheet = paramArray.detailstylesheet || null;
35 __myself.useSessions = true;
36 if (paramArray.usesessions != undefined) {
37 __myself.useSessions = paramArray.usesessions;
40 //load stylesheet if required in async mode
41 if( __myself.stylesheet ) {
42 var request = new pzHttpRequest( __myself.stylesheet );
43 request.async = false;
47 __myself.xslDoc = doc;
52 // at least one callback required
54 throw new Error("An array with parameters has to be suplied when instantiating a class");
56 __myself.errorHandler = paramArray.errorhandler || null;
59 __myself.statCallback = paramArray.onstat || null;
60 __myself.showCallback = paramArray.onshow || null;
61 __myself.termlistCallback = paramArray.onterm || null;
62 __myself.recordCallback = paramArray.onrecord || null;
63 __myself.bytargetCallback = paramArray.onbytarget || null;
64 __myself.resetCallback = paramArray.onreset || null;
67 __myself.termKeys = paramArray.termlist || "subject";
69 // some configurational stuff
70 __myself.keepAlive = 50000;
72 __myself.sessionID = null;
73 __myself.initStatusOK = false;
74 __myself.pingStatusOK = false;
75 __myself.searchStatusOK = false;
77 if ( paramArray.keepAlive < __myself.keepAlive )
78 __myself.keepAlive = paramArray.keepAlive;
81 __myself.currentSort = "relevance";
83 __myself.currentStart = 0;
84 __myself.currentNum = 20;
86 // last full record retrieved
87 __myself.currRecID = null;
89 __myself.currQuery = null;
92 __myself.statTime = paramArray.stattime || 1000;
93 __myself.statTimer = null;
94 __myself.termTime = paramArray.termtime || 1000;
95 __myself.termTimer = null;
96 __myself.showTime = paramArray.showtime || 1000;
97 __myself.showTimer = null;
98 __myself.showFastCount = 4;
99 __myself.bytargetTime = paramArray.bytargettime || 1000;
100 __myself.bytargetTimer = null;
102 // counters for each command and applied delay
103 __myself.dumpFactor = 500;
104 __myself.showCounter = 0;
105 __myself.termCounter = 0;
106 __myself.statCounter = 0;
107 __myself.bytargetCounter = 0;
109 // active clients, updated by stat and show
110 // might be an issue since bytarget will poll accordingly
111 __myself.activeClients = 1;
113 // auto init session?
114 if (paramArray.autoInit !== false)
121 clearTimeout(__myself.statTimer);
122 clearTimeout(__myself.showTimer);
123 clearTimeout(__myself.termTimer);
124 clearTimeout(__myself.bytargetTimer);
129 __myself.sessionID = null;
130 __myself.initStatusOK = false;
131 __myself.pingStatusOK = false;
132 __myself.searchStatusOK = false;
136 if ( __myself.resetCallback )
137 __myself.resetCallback();
139 init: function ( sessionId )
143 if ( sessionId != undefined ) {
144 __myself.initStatusOK = true;
145 __myself.sessionID = sessionId;
147 } else if (__myself.useSessions) {
148 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
150 { "command": "init" },
152 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
153 if ( data.getElementsByTagName("protocol")[0].childNodes[0].nodeValue != __myself.suppProtoVer )
154 throw new Error("Server's protocol not supported by the client");
155 __myself.initStatusOK = true;
156 __myself.sessionID = data.getElementsByTagName("session")[0].childNodes[0].nodeValue;
157 setTimeout("__myself.ping()", __myself.keepAlive);
160 // if it gets here the http return code was 200 (pz2 errors are 417)
161 // but the response was invalid, it should never occur
162 setTimeout("__myself.init()", 1000);
166 __myself.initStatusOK = true;
169 // no need to ping explicitly
172 if( !__myself.initStatusOK )
174 // session is not initialized code here
175 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
177 { "command": "ping", "session": __myself.sessionID },
179 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
180 __myself.pingStatusOK = true;
181 setTimeout("__myself.ping()", __myself.keepAlive);
184 // if it gets here the http return code was 200 (pz2 errors are 417)
185 // but the response was invalid, it should never occur
186 setTimeout("__myself.ping()", 1000);
190 search: function (query, num, sort, filter)
192 clearTimeout(__myself.statTimer);
193 clearTimeout(__myself.showTimer);
194 clearTimeout(__myself.termTimer);
195 clearTimeout(__myself.bytargetTimer);
197 __myself.showCounter = 0;
198 __myself.termCounter = 0;
199 __myself.bytargetCounter = 0;
200 __myself.statCounter = 0;
202 if( !__myself.initStatusOK )
205 if( query !== undefined )
206 __myself.currQuery = query;
208 throw new Error("You need to supply query to the search command");
210 var searchParams = [{ "command": "search", "query": __myself.currQuery }];
211 searchParams.push({"session":__myself.sessionID});
212 if (filter !== undefined)
213 searchParams.push({"filter": filter});
214 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
218 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
219 __myself.searchStatusOK = true;
221 __myself.show(0, num, sort);
222 if ( __myself.statCallback )
224 //__myself.statTimer = setTimeout("__myself.stat()", __myself.statTime / 4);
225 if ( __myself.termlistCallback )
227 //__myself.termTimer = setTimeout("__myself.termlist()", __myself.termTime / 4);
228 if ( __myself.bytargetCallback )
230 //__myself.bytargetTimer = setTimeout("__myself.bytarget()", __myself.bytargetTime / 4);
233 // if it gets here the http return code was 200 (pz2 errors are 417)
234 // but the response was invalid, it should never occur
235 setTimeout("__myself.search(__myself.currQuery)", 500);
241 if( !__myself.initStatusOK )
243 // if called explicitly takes precedence
244 clearTimeout(__myself.statTimer);
245 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
247 { "command": "stat", "session": __myself.sessionID },
249 if ( data.getElementsByTagName("stat") ) {
250 var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
251 __myself.activeClients = activeClients;
253 "activeclients": activeClients,
254 "hits": Number( data.getElementsByTagName("hits")[0].childNodes[0].nodeValue ),
255 "records": Number( data.getElementsByTagName("records")[0].childNodes[0].nodeValue ),
256 "clients": Number( data.getElementsByTagName("clients")[0].childNodes[0].nodeValue ),
257 "initializing": Number( data.getElementsByTagName("initializing")[0].childNodes[0].nodeValue ),
258 "searching": Number( data.getElementsByTagName("searching")[0].childNodes[0].nodeValue ),
259 "presenting": Number( data.getElementsByTagName("presenting")[0].childNodes[0].nodeValue ),
260 "idle": Number( data.getElementsByTagName("idle")[0].childNodes[0].nodeValue ),
261 "failed": Number( data.getElementsByTagName("failed")[0].childNodes[0].nodeValue ),
262 "error": Number( data.getElementsByTagName("error")[0].childNodes[0].nodeValue )
265 __myself.statCounter++;
266 var delay = __myself.statTime + __myself.statCounter * __myself.dumpFactor;
267 if ( activeClients > 0 )
268 __myself.statTimer = setTimeout("__myself.stat()", delay);
270 __myself.statCallback(stat);
273 // if it gets here the http return code was 200 (pz2 errors are 417)
274 // but the response was invalid, it should never occur
275 __myself.statTimer = setTimeout("__myself.stat()", __myself.statTime / 4);
279 show: function(start, num, sort)
281 if( !__myself.searchStatusOK )
283 // if called explicitly takes precedence
284 clearTimeout(__myself.showTimer);
286 if( sort !== undefined )
287 __myself.currentSort = sort;
288 if( start !== undefined )
289 __myself.currentStart = Number( start );
290 if( num !== undefined )
291 __myself.currentNum = Number( num );
292 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
295 { "command": "show", "session": __myself.sessionID, "start": __myself.currentStart,
296 "num": __myself.currentNum, "sort": __myself.currentSort, "block": 1 },
298 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
299 // first parse the status data send along with records
300 // this is strictly bound to the format
301 var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
302 __myself.activeClients = activeClients;
304 "activeclients": activeClients,
305 "merged": Number( data.getElementsByTagName("merged")[0].childNodes[0].nodeValue ),
306 "total": Number( data.getElementsByTagName("total")[0].childNodes[0].nodeValue ),
307 "start": Number( data.getElementsByTagName("start")[0].childNodes[0].nodeValue ),
308 "num": Number( data.getElementsByTagName("num")[0].childNodes[0].nodeValue ),
311 // parse all the first-level nodes for all <hit> tags
312 var hits = data.getElementsByTagName("hit");
313 var hit = new Array();
314 for (i = 0; i < hits.length; i++) {
315 show.hits[i] = new Array();
316 show.hits[i]['location'] = new Array();
317 for ( j = 0; j < hits[i].childNodes.length; j++) {
319 if ( hits[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
320 if (hits[i].childNodes[j].nodeName == 'location') {
321 var locNode = hits[i].childNodes[j];
322 var id = locNode.getAttribute('id');
323 show.hits[i]['location'][id] = {
324 "id": locNode.getAttribute("id"),
325 "name": locNode.getAttribute("name")
329 var nodeName = hits[i].childNodes[j].nodeName;
330 var nodeText = 'ERROR'
331 if ( hits[i].childNodes[j].firstChild )
332 nodeText = hits[i].childNodes[j].firstChild.nodeValue;
333 show.hits[i][nodeName] = nodeText;
338 __myself.showCounter++;
339 var delay = __myself.showTime;
340 if (__myself.showCounter > __myself.showFastCount)
341 delay += __myself.showCounter * __myself.dumpFactor;
342 if ( activeClients > 0 )
343 __myself.showTimer = setTimeout("__myself.show()", delay);
345 __myself.showCallback(show);
348 // if it gets here the http return code was 200 (pz2 errors are 417)
349 // but the response was invalid, it should never occur
350 __myself.showTimer = setTimeout("__myself.show()", __myself.showTime / 4);
356 if( !__myself.searchStatusOK )
359 if( id !== undefined )
360 __myself.currRecID = id;
361 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
363 { "command": "record", "session": __myself.sessionID, "id": __myself.currRecID },
366 var record = new Array();
367 if ( recordNode = data.getElementsByTagName("record")[0] ) {
368 // if stylesheet was fetched do not parse the response
369 if ( __myself.xslDoc ) {
370 record['recid'] = recordNode.getElementsByTagName("recid")[0].firstChild.nodeValue;
371 record['xmlDoc'] = data;
372 record['xslDoc'] = __myself.xslDoc;
374 for ( i = 0; i < recordNode.childNodes.length; i++) {
375 if ( recordNode.childNodes[i].nodeType == Node.ELEMENT_NODE
376 && recordNode.childNodes[i].nodeName != 'location' ) {
377 var nodeName = recordNode.childNodes[i].nodeName;
378 var nodeText = recordNode.childNodes[i].firstChild.nodeValue;
379 record[nodeName] = nodeText;
382 // the location might be empty!!
383 var locationNodes = recordNode.getElementsByTagName("location");
384 record["location"] = new Array();
385 for ( i = 0; i < locationNodes.length; i++ ) {
386 record["location"][i] = {
387 "id": locationNodes[i].getAttribute("id"),
388 "name": locationNodes[i].getAttribute("name")
391 for ( j = 0; j < locationNodes[i].childNodes.length; j++) {
392 if ( locationNodes[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
393 var nodeName = locationNodes[i].childNodes[j].nodeName;
395 if (locationNodes[i].childNodes[j].firstChild)
396 nodeText = locationNodes[i].childNodes[j].firstChild.nodeValue;
397 record["location"][i][nodeName] = nodeText;
403 __myself.recordCallback(record);
406 // if it gets here the http return code was 200 (pz2 errors are 417)
407 // but the response was invalid, it should never occur
408 setTimeout("__myself.record(__myself.currRecID)", 500);
414 if( !__myself.searchStatusOK )
416 // if called explicitly takes precedence
417 clearTimeout(__myself.termTimer);
418 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
420 { "command": "termlist", "session": __myself.sessionID, "name": __myself.termKeys },
422 if ( data.getElementsByTagName("termlist") ) {
423 var activeClients = Number( data.getElementsByTagName("activeclients")[0].childNodes[0].nodeValue );
424 __myself.activeClients = activeClients;
425 var termList = { "activeclients": activeClients };
426 var termLists = data.getElementsByTagName("list");
428 for (i = 0; i < termLists.length; i++) {
429 var listName = termLists[i].getAttribute('name');
430 termList[listName] = new Array();
431 var terms = termLists[i].getElementsByTagName('term');
432 //for each term in the list
433 for (j = 0; j < terms.length; j++) {
435 "name": (terms[j].getElementsByTagName("name")[0].childNodes.length
436 ? terms[j].getElementsByTagName("name")[0].childNodes[0].nodeValue
438 "freq": terms[j].getElementsByTagName("frequency")[0].childNodes[0].nodeValue || 'ERROR'
441 var termIdNode = terms[j].getElementsByTagName("id");
442 if(terms[j].getElementsByTagName("id").length)
443 term["id"] = termIdNode[0].childNodes[0].nodeValue;
445 termList[listName][j] = term;
449 __myself.termCounter++;
450 var delay = __myself.termTime + __myself.termCounter * __myself.dumpFactor;
451 if ( activeClients > 0 )
452 __myself.termTimer = setTimeout("__myself.termlist()", delay);
454 __myself.termlistCallback(termList);
457 // if it gets here the http return code was 200 (pz2 errors are 417)
458 // but the response was invalid, it should never occur
459 __myself.termTimer = setTimeout("__myself.termlist()", __myself.termTime / 4);
466 if( !__myself.searchStatusOK )
468 // if called explicitly takes precedence
469 clearTimeout(__myself.bytargetTimer);
470 var request = new pzHttpRequest(__myself.pz2String, __myself.errorHandler);
472 { "command": "bytarget", "session": __myself.sessionID },
474 if ( data.getElementsByTagName("status")[0].childNodes[0].nodeValue == "OK" ) {
475 var targetNodes = data.getElementsByTagName("target");
476 var bytarget = new Array();
477 for ( i = 0; i < targetNodes.length; i++) {
478 bytarget[i] = new Array();
479 for( j = 0; j < targetNodes[i].childNodes.length; j++ ) {
480 if ( targetNodes[i].childNodes[j].nodeType == Node.ELEMENT_NODE ) {
481 var nodeName = targetNodes[i].childNodes[j].nodeName;
482 var nodeText = targetNodes[i].childNodes[j].firstChild.nodeValue;
483 bytarget[i][nodeName] = nodeText;
488 __myself.bytargetCounter++;
489 var delay = __myself.bytargetTime + __myself.bytargetCounter * __myself.dumpFactor;
490 if ( __myself.activeClients > 0 )
491 __myself.bytargetTimer = setTimeout("__myself.bytarget()", delay);
493 __myself.bytargetCallback(bytarget);
496 // if it gets here the http return code was 200 (pz2 errors are 417)
497 // but the response was invalid, it should never occur
498 __myself.bytargetTimer = setTimeout("__myself.bytarget()", __myself.bytargetTime / 4);
502 // just for testing, probably shouldn't be here
503 showNext: function(page)
505 var step = page || 1;
506 __myself.show( ( step * __myself.currentNum ) + __myself.currentStart );
508 showPrev: function(page)
510 if (__myself.currentStart == 0 )
512 var step = page || 1;
513 var newStart = __myself.currentStart - (step * __myself.currentNum );
514 __myself.show( newStart > 0 ? newStart : 0 );
516 showPage: function(pageNum)
518 //var page = pageNum || 1;
519 __myself.show(pageNum * __myself.currentNum);
524 *********************************************************************************
525 ** AJAX HELPER CLASS ************************************************************
526 *********************************************************************************
528 var pzHttpRequest = function ( url, errorHandler ) {
531 this.errorHandler = errorHandler || null;
534 if ( window.XMLHttpRequest ) {
535 this.request = new XMLHttpRequest();
536 } else if ( window.ActiveXObject ) {
538 this.request = new ActiveXObject( 'Msxml2.XMLHTTP' );
540 this.request = new ActiveXObject( 'Microsoft.XMLHTTP' );
545 pzHttpRequest.prototype =
547 get: function ( params, callback )
549 this._send( 'GET', params, null, callback );
552 post: function ( params, data, callback )
554 this._send( 'POST', params, data, callback );
557 _send: function ( type, params, data, callback )
559 this.callback = callback;
561 this.request.open( type, this._urlAppendParams(params), this.async );
562 //this.request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
563 this.request.onreadystatechange = function () {
564 context._handleResponse();
566 this.request.send(data);
569 _urlAppendParams: function (params)
571 var getUrl = this.url;
575 for (var key in el) {
576 if (el[key] != null) {
577 getUrl += sep + key + '=' + encodeURI(el[key]);
584 _handleResponse: function ()
586 if ( this.request.readyState == 4 ) {
587 if ( this.request.status == 200 ) {
588 this.callback( this.request.responseXML );
591 else if ( this.request.status == 417 ) {
592 var errMsg = this.request.responseXML.getElementsByTagName("error")[0].childNodes[0].nodeValue;
593 var errCode = this.request.responseXML.getElementsByTagName("error")[0].getAttribute("code");
595 var err = new Error(errMsg);
598 if (this.errorHandler) {
599 this.errorHandler(err);
606 var err = new Error("XMLHttpRequest error. STATUS: "
607 + this.request.status + " STATUS TEXT: "
608 + this.request.statusText );
611 if (this.errorHandler) {
612 this.errorHandler(err);
623 *********************************************************************************
624 ** QUERY CLASS ******************************************************************
625 *********************************************************************************
627 var pzQuery = function()
629 this.simpleQuery = '';
630 this.singleFilter = null;
631 this.advTerms = new Array();
632 this.filterHash = new Array();
636 pzQuery.prototype = {
639 this.simpleQuery = '';
640 this.advTerms = new Array();
641 this.simpleFilter = null;
644 clearSimpleQuery: function()
646 this.simpleQuery = '';
648 addTerm: function(field, value)
650 var term = {"field": field, "value": value};
651 this.advTerms[this.numTerms] = term;
654 getTermValueByIdx: function(index)
656 return this.advTerms[index].value;
658 getTermFieldByIdx: function(index)
660 return this.advTerms[index].field;
662 /* semicolon separated list of terms for given field*/
663 getTermsByField: function(field)
666 for(var i = 0; i < this.advTerms.length; i++)
668 if( this.advTerms[i].field == field )
669 terms = terms + this.queryHas[i].value + ';';
673 addTermsFromList: function(inputString, field)
675 var inputArr = inputString.split(';');
676 for(var i=0; i < inputArr.length; i++)
678 if(inputArr[i].length < 3) continue;
679 this.advTerms[this.numTerms] = {"field": field, "value": inputArr[i] };
683 removeTermByIdx: function(index)
685 this.advTerms.splice(index, 1);
691 if( this.simpleQuery != '')
692 ccl = this.simpleQuery;
693 for(var i = 0; i < this.advTerms.length; i++)
695 if (ccl != '') ccl = ccl + ' and ';
696 ccl = ccl + this.advTerms[i].field+'="'+this.advTerms[i].value+'"';
700 addFilter: function(name, value)
702 var filter = {"name": name, "id": value };
703 this.filterHash[this.filterHash.length] = filter;
705 return this.filterHash.length - 1;
707 setFilter: function(name, value)
709 this.filterHash = new Array();
711 this.addFilter(name, value);
713 getFilter: function(index)
715 return this.filterHash[index].id;
717 getFilterName: function(index)
719 return this.filterHash[index].name;
721 removeFilter: function(index)
723 delete this.filterHash[index];
726 clearFilter: function()
728 this.filterHash = new Array();
731 getFilterString: function()
734 if( this.singleFilter != null ) {
735 return 'pz:id='+this.singleFilter.id;
737 else if( this.filterNums <= 0 ) {
741 var filter = 'pz:id=';
742 for(var i = 0; i < this.filterHash.length; i++)
744 if (this.filterHash[i] == undefined) continue;
745 if (filter > 'pz:id=') filter = filter + '|';
746 filter += this.filterHash[i].id;
750 totalLength: function()
752 var simpleLength = this.simpleQuery != '' ? 1 : 0;
753 return this.advTerms.length + simpleLength;
755 clearSingleFilter: function()
757 this.singleFilter = null;
759 setSingleFilter: function(name, value)
761 this.singleFilter = {"name": name, "id": value };
763 getSingleFilterName: function()
765 return this.singleFilter.name;