source: main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js

Last change on this file was 38427, checked in by anupama, 5 months ago

Maintaining a list of running AJAX calls, which get aborted if the user navigates away from the page before they've finished. When any call terminates (either sucessfully, with error or on abort) it gets removed from the list of *running* Ajax calls, so only running ones can be aborted.

File size: 44.3 KB
Line 
1var SUCCESS = 1;
2var ACCEPTED = 2;
3var ERROR = 3;
4var CONTINUING = 10;
5var COMPLETED = 11;
6var HALTED = 12;
7
8var runningAjaxCalls = [];
9
10gs.functions = {};
11
12gs.jqGet = function(id)
13{
14 return $("#" + id.replace(/\./g, "\\.").replace(/:/g,"\\:"));
15}
16
17// Debugging function to print a string's non-basic chars in hex. So does string2hex on all non-basic and non-printable ASCII chars
18// Dr Bainbridge said that printing anything with charCode over 128 in hex is okay, but I'd already made extra allowances for non-printable ASCII
19// Based on https://stackoverflow.com/questions/36637146/javascript-encode-string-to-hex/36637293
20// https://stackoverflow.com/questions/21647928/javascript-unicode-string-to-hex
21gs.functions.debug_unicode_string = function(str) {
22 var hex, i;
23
24 var result = "";
25 for (i=0; i<str.length; i++) {
26 charcode = str.charCodeAt(i);
27 // ASCII table: https://cdn.sparkfun.com/assets/home_page_posts/2/1/2/1/ascii_table_black.png
28 // if the unicode character code pt is less than the ASCII code for space and greater than for tilda, let's display the char in hex (x0000 format)
29 if(charcode < 20 || charcode > 126) { //doesn't work: if(str.charAt(i) < ' ' || str.charAt(i) > '~') {
30 hex = charcode.toString(16);
31 result += "x{" + ("000"+hex).slice(-4) + "}"; // looks like: x{4-char-codepoint}
32 }
33 else {
34 result += str.charAt(i);
35 }
36 }
37
38 return result;
39}
40
41gs.functions.ajaxRequest = function()
42{
43 var activexmodes=["Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
44 if(window.ActiveXObject)
45 {
46 for (var i=0; i<activexmodes.length; i++)
47 {
48 try
49 {
50 return new ActiveXObject(activexmodes[i]);
51 }
52 catch(e){}
53 }
54 }
55 else if (window.XMLHttpRequest)
56 {
57 return new XMLHttpRequest();
58 }
59 else
60 {
61 return false
62 }
63}
64
65gs.functions.hasClass = function(elem, classVal)
66{
67 if(!elem || !elem.getAttribute("class"))
68 {
69 return false;
70 }
71
72 return (elem.getAttribute("class").search(classVal) != -1)
73}
74
75gs.functions.getElementsByClassName = function(cl)
76{
77 var nodes = new Array();
78 var classRegEx = new RegExp('\\b'+cl+'\\b');
79 var allElems = document.getElementsByTagName('*');
80
81 for (var i = 0; i < allElems.length; i++)
82 {
83 var classes = allElems[i].className;
84 if (classRegEx.test(classes))
85 {
86 nodes.push(allElems[i]);
87 }
88 }
89 return nodes;
90};
91
92gs.functions.makeToggle = function(buttons, divs)
93{
94 var buttonArray = (buttons.length) ? buttons : [buttons];
95 var divArray = (divs.length) ? divs : [divs];
96
97 for(var i = 0; i < buttonArray.length; i++)
98 {
99 buttonArray[i].onclick = function()
100 {
101 for(var j = 0; j < divArray.length; j++)
102 {
103 if(divArray[j].style.display == "none")
104 {
105 divArray[j].style.display = "block";
106 }
107 else
108 {
109 divArray[j].style.display = "none";
110 }
111 }
112
113 for(var j = 0; j < buttonArray.length; j++)
114 {
115 if(buttonArray[j].getAttribute("src") == gs.imageURLs.collapse)
116 {
117 buttonArray[j].setAttribute("src", gs.imageURLs.expand);
118 }
119 else if(buttonArray[j].getAttribute("src") == gs.imageURLs.expand)
120 {
121 buttonArray[j].setAttribute("src", gs.imageURLs.collapse);
122 }
123 }
124 };
125 }
126}
127
128gs.functions.checkForErrors = function(xml)
129{
130 var errorElems = xml.getElementsByTagName("error");
131
132 if(errorElems && errorElems.length > 0)
133 {
134 var errorString = gs.text.dse.error_saving_changes + ": ";
135 for(var i = 0; i < errorElems.length; i++)
136 {
137 errorString += " " + errorElems.item(i).firstChild.nodeValue;
138 }
139 alert(errorString);
140 return true;
141 }
142 return false; //No errors
143}
144
145gs.functions.validateXML = function(txt)
146{
147 // code for IE
148 if (window.ActiveXObject)
149 {
150 var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
151 xmlDoc.async = "false";
152 xmlDoc.loadXML(document.all(txt).value);
153
154 if(xmlDoc.parseError.errorCode!=0)
155 {
156 txt = dse.error_code + ": " + xmlDoc.parseError.errorCode + "\n";
157 txt = txt + dse.error_reason + ": " + xmlDoc.parseError.reason;
158 txt = txt + dse.error_line + ": " + xmlDoc.parseError.line;
159 console.log(txt);
160 return null;
161 }
162
163 return xmlDoc;
164 }
165 // code for Mozilla, Firefox, Opera, etc.
166 else if (document.implementation.createDocument)
167 {
168 var parser = new DOMParser();
169 var xmlDoc = parser.parseFromString(txt,"text/xml");
170
171 if (xmlDoc.getElementsByTagName("parsererror").length > 0)
172 {
173 console.log(gs.text.dse.xml_error);
174 return null;
175 }
176
177 return xmlDoc;
178 }
179 else
180 {
181 console.log(gs.text.dse.browse_cannot_validate_xml);
182 }
183 return null;
184}
185
186gs.functions.buildCollections = function(collections, finalFunction)
187{
188 if(!collections || collections.length == 0)
189 {
190 console.log("List of collections to build is empty");
191 return;
192 }
193
194 var counter = 0;
195 var buildFunction = function()
196 {
197 var ajax = new gs.functions.ajaxRequest();
198 ajax.open("GET", gs.xsltParams.library_name + "?a=g&rt=r&ro=1&s=BuildCollection&s1.collection=" + collections[counter]);
199 ajax.onreadystatechange = function()
200 {
201 if(ajax.readyState == 4 && ajax.status == 200)
202 {
203 var text = ajax.responseText;
204 var xml = gs.functions.validateXML(text);
205
206 if(!xml || gs.functions.checkForErrors(xml))
207 {
208 console.log("Could not build collection -> " + collections[counter] + ", aborting");
209 return;
210 }
211
212 var status = xml.getElementsByTagName("status")[0];
213 var pid = status.getAttribute("pid");
214
215 gs.functions.startCheckLoop(pid, "BuildCollection", function()
216 {
217 var localAjax = new gs.functions.ajaxRequest();
218 localAjax.open("GET", gs.xsltParams.library_name + "?a=g&rt=r&ro=1&s=ActivateCollection&s1.collection=" + collections[counter], true);
219 localAjax.onreadystatechange = function()
220 {
221 if(localAjax.readyState == 4 && localAjax.status == 200)
222 {
223 var localText = localAjax.responseText;
224 var localXML = gs.functions.validateXML(localText);
225
226 if(!xml || gs.functions.checkForErrors(xml))
227 {
228 console.log("Could not activate collection -> " + collections[counter] + ", aborting");
229 return;
230 }
231
232 var localStatus = localXML.getElementsByTagName("status")[0];
233 if(localStatus)
234 {
235 var localPID = localStatus.getAttribute("pid");
236 gs.functions.startCheckLoop(localPID, "ActivateCollection", function()
237 {
238 if (++counter == collections.length)
239 {
240 //Run this function once we are done building all the collections
241 if(finalFunction){finalFunction();}
242 }
243 else
244 {
245 buildFunction();
246 }
247 });
248 }
249 }
250 }
251 localAjax.send();
252 });
253 }
254 }
255 ajax.send();
256 }
257 buildFunction();
258}
259
260gs.functions.startCheckLoop = function(pid, serverFunction, callbackFunction)
261{
262 var ajaxFunction = function()
263 {
264 var ajax = new gs.functions.ajaxRequest();
265 ajax.open("GET", gs.xsltParams.library_name + "?a=g&rt=s&ro=1&s=" + serverFunction + "&s1.pid=" + pid, true);
266 ajax.onreadystatechange = function()
267 {
268 if(ajax.readyState == 4 && ajax.status == 200)
269 {
270 var text = ajax.responseText;
271 var xml = gs.functions.validateXML(text);
272
273 if(!xml || gs.functions.checkForErrors(xml))
274 {
275 console.log("Could not check status of " + serverFunction + ", there was an error in the XML, aborting");
276 return;
277 }
278
279 var status = xml.getElementsByTagName("status")[0];
280 var code = status.getAttribute("code");
281
282 if (code == COMPLETED || code == SUCCESS)
283 {
284 callbackFunction();
285 }
286 else if (code == HALTED || code == ERROR)
287 {
288 console.log("Could not check status of " + serverFunction + ", there was an error on the server, aborting");
289 }
290 else
291 {
292 setTimeout(ajaxFunction, 1000);
293 }
294 }
295 }
296 ajax.send();
297 }
298 ajaxFunction();
299}
300
301function inc(a, b)
302{
303 var carry = 0;
304 var num = 0;
305 var i = 0;
306
307 while((carry || (i < a.length) || (i < b.length)) && (i < 100))
308 {
309 num = carry;
310 if(i < a.length){num += a[i];}
311 if(i < b.length){num += b[i];}
312
313 if(num >= 256)
314 {
315 num -= 256;
316 carry = 1;
317 }
318 else
319 {
320 carry = 0;
321 }
322
323 a[i] = num;
324
325 i++;
326 }
327}
328
329function ifposDec(a, b)
330{
331 var carry = 0;
332 var num = 0;
333 var i = 0;
334
335 if(b.length > a.length){return a;}
336 if(b.length == a.length)
337 {
338 i = a.length - 1;
339 while(i >= 0)
340 {
341 if(a[i] > b[i]){break;}
342 if(a[i] < b[i]){return a;}
343 i--;
344 }
345 }
346
347 i = 0;
348 var len = 0;
349 var outString = "";
350 while((i < a.length) || (i < b.length))
351 {
352 num = -carry;
353 if(i < a.length){num += a[i];}
354 if(i < b.length){num -= b[i];}
355
356 if(num < 0)
357 {
358 num += 256;
359 carry = 1;
360 }
361 else
362 {
363 carry = 0;
364 }
365
366 a[i] = num;
367 outString += num + ","
368 i++
369
370 if(num != 0){len = i}
371 }
372
373 if(len < a.length)
374 {
375 a = a.slice(0, len);
376 }
377
378 return a;
379}
380
381function convertNum(a)
382{
383 var result = new Array();
384 var i;
385 var convert = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];
386
387 if(a.length == 0)
388 {
389 result.push("0");
390 return result;
391 }
392
393 for(i = a.length - 1; i >= 0; i--)
394 {
395 result.push(convert[Math.floor(a[i]/16)]);
396 result.push(convert[Math.floor(a[i]%16)]);
397 }
398
399 var resultString = "";
400 for(var j = 0; j < result.length; j++)
401 {
402 resultString += result[j];
403 }
404
405 return resultString;
406}
407
408gs.functions.hashString = function(str)
409{
410 var remainder = new Array();
411 var primePow = new Array();
412 var pow =
413 [
414 255, 255, 255,
415 255, 255, 255,
416 255, 255, 255,
417 255, 255, 1
418 ];
419
420 for(var i = 0; i < 8; i++)
421 {
422 primePow.push(pow.slice()); //The javascript way to do an array copy (yuck!)
423 inc(pow, pow);
424 }
425
426 for(var i = 0; i < str.length; i++)
427 {
428 var c = str.charCodeAt(i);
429
430 if(remainder.length == 99)
431 {
432 return null;
433 }
434
435 for(var j = remainder.length; j > 0; j--)
436 {
437 remainder[j] = remainder[j-1];
438 }
439 remainder[0] = c;
440
441 for(var j = 7; j >= 0; j--)
442 {
443 remainder = ifposDec(remainder, primePow[j]);
444 }
445 }
446
447 return convertNum(remainder);
448}
449
450// This method performs an AJAX call after working out, based on parameters and internal decision-making code,
451// if it's using GET or POST,
452// asynchronous or synchronous AJAX,
453// jQuery's .ajax() method or gsajaxapi.js' regular JavaScript way of calling AJAX (necessary functions
454// now ported from GS2 to GS3)
455// and whether it needs to transmit the payload in URL or data structure (Java object) form.
456// In the past, the AJAX calls to metadataserver.pl only dealt with URLs and used jQuery .ajax(). As a
457// consequence of this particular combination, the calls in the past were all GET operations.
458//
459// - payload param: contains both the URL form and the data object form of the package to transmit over
460// AJAX to metadataserver.pl. Based on the parameters and some internal variables, _callMetadataServer()
461// determines which to use.
462// - opts param: No function overloading in JavaScript. Can pass a custom object, however can pass opts,
463// see http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices
464//
465// BEWARE:
466// errorResponseFunction is at present only called on error if using jQuery ajax (sync/async) and not if using gsajaxapi (sync/async).
467gs.functions._callMetadataServer = function(callingFunction, payload, successResponseFunction, errorResponseFunction, opts)
468{
469
470 // async AJAX by default for get operations: because internal processing of 'read' operations (get meta)
471 // is not order dependent.
472 // Set/remove operations will switch to synchronous AJAX, unless opt["forceSync"] is set otherwise
473 var async_setting = true;
474 var method = "POST"; // GET was the default before
475
476 // Set to false if you wish to use the regular JavaScript AJAX way in gsajaxapi.js (will use payload.url)
477 // Set to true if using jQuery AJAX (will use payload.data).
478 var _use_jQuery_ajax_not_gsajaxapi = true;
479
480 // _use_payload_in_data_not_url_form is determined based on vars method and _use_jQuery_ajax_not_gsajaxapi
481 // If using AJAX with payload data (with jQuery) rather than using URLs containing data (payload in url):
482 // using data will allow us to use jQuery to POST stuff too.
483 // For gsajaxapi, payload to be transmitted over AJAX must be in URL form, whether GET or POST.
484 // For jQuery, AJAX calls ended up as GET when the payload is in URL form.
485 // Default used to be payload in url form. To get the default back,
486 // set method = "GET" (above, but also in calling functions that specify this optional parameter!)
487 // and set the default here below for _use_payload_in_data_not_url_form to false.
488 var _use_payload_in_data_not_url_form = true; // code will anyway set this to true for jQuery $.ajax() with POST
489
490 var _modifyingMeta = false;
491
492 var url = payload["url"]; // for jQuery GET, and for GET and POST using JavaScript AJAX
493 var data = payload["data"]; // for jQuery POST
494
495
496 // check for any caller overrides
497 if(opts != null) {
498 if(opts["requestMethod"] != null) {
499 method = opts["requestMethod"];
500 }
501 }
502
503 // sync or async? Generally, synchronous AJAX for set-meta operations, and asynchronous for get-meta ops
504 var metaServerCommand = (data["s1.a"] == null) ? data["a"] : data["s1.a"];
505 if(metaServerCommand.indexOf("set-") != -1 || metaServerCommand.indexOf("remove-") != -1) {
506 _modifyingMeta = true;
507 async_setting = false; // for 'write' operations (set/remove meta), we force sequential processing of the internal operation.
508
509 }
510 // check for any overrides by calling code that knows what it's doing
511 if (opts != null && opts["forceSync"] != null) {
512 async_setting = (!opts["forceSync"]);
513 }
514
515 if(_use_jQuery_ajax_not_gsajaxapi) {
516 if(method == "POST") {
517 _use_payload_in_data_not_url_form = true;
518 } // if GET, can use payload in URL form or in data form for jQuery AJAX
519 // to put it another way: can't do jQuery POST operations with payload in URL form
520
521 } else { // using gsajaxapi.js, which only accepts the payload in URL form, whether GET or POST
522 _use_payload_in_data_not_url_form = false;
523 }
524
525 // use the URL form or the data form to transmit the payload over AJAX?
526 // Payload in data form implies jQuery AJAX, not gsajaxapi calls,
527 // since we can't use gsajaxapi.js AJAX GET/POST calls without payload in URL form
528 if(_use_payload_in_data_not_url_form) { // using data payload to do AJAX (regardless of request method)
529
530 // for get-meta operations, go directly through metadata-server.pl
531 // for set-meta ops, should go via GS3 authentication, which is off the GS3 library servlet
532 url = (_modifyingMeta) ? gs.xsltParams.library_name : "cgi-bin/metadata-server.pl";
533
534 } else { // uses data encoded into URL, rather than a data structure.
535 data = null; // we're using the URL as payload, don't duplicate the payload to be transmitted into data
536
537 url = payload["url"]; // payload["url"] contains the URL + data encoded in URL form
538 // URL is already correct for get-meta vs meta-modification operations.
539 // For meta-modification ops, it will through GS3 authentication first rather than metadata-server.pl
540
541 }
542
543 // finally, can do the AJAX call
544
545 //console.log("*** Away to call: " + url);
546 var ajaxResponse = async_setting ? "*** No response received yet, async ajax request" : null;
547
548
549 if(_use_jQuery_ajax_not_gsajaxapi) {
550 // ajax calls default to using method GET, we want to do POST operations for get-meta and set-meta requests
551 // since get-meta-array and especially set-meta-array can be large, e.g. for user comments.
552 var ajaxCall = $.ajax({url: url, async: async_setting, type: method, data: data})
553 .done(function(response) {
554 ajaxResponse = response;
555// console.log("** (" + callingFunction + ") Response received from server: " + ajaxResponse);
556
557 //var xml = $.parseXML(response);
558 //console.log(xml);
559
560
561 // Handle the otherwise silent die statement of gsdl_cgi->generate_error(),
562 // forcing the errorResponseFunction route in such a case
563 if(response.indexOf("ERROR: ") !== -1 || response.indexOf("<Error>") !== -1) {
564 if(errorResponseFunction != null) {
565 console.log("!!!! (" + callingFunction + ") Failed with gsdlcgi error:\n" + response);
566 errorResponseFunction(response);
567 } else {
568 alert("@@@ (" + callingFunction + ") Failed\n" + response);
569 console.log("@@@ (" + callingFunction + ") Failed\n" + response);
570 }
571 }
572
573 else if(successResponseFunction != null) {
574 //console.log("XXXX callMetaServer - Got response: " + response);
575 successResponseFunction(response);
576 }
577 })
578 .fail(function(response, textStatus, errorThrown) {
579 // https://stackoverflow.com/questions/13258784/stopping-an-ajax-call-on-page-unload
580 // https://api.jquery.com/jQuery.ajax/
581 if(textStatus == "abort") {
582 console.log ("INFO: Navigating away from page, so aborted running ajax.");
583 } else {
584
585 if(errorResponseFunction != null) {
586 console.log("*** (" + callingFunction + ") Failed\n" + response);
587 errorResponseFunction(response);
588 } else {
589 alert("(" + callingFunction + ") Failed\n" + response);
590 console.log("*** (" + callingFunction + ") Failed\n" + response);
591 }
592 }
593 })
594 .always(function(data) { // https://api.jquery.com/jQuery.ajax/
595
596 // this call has terminated one way or another, remove it from the list we maintain
597 // of runningAjaxCalls
598 var index_of_call = runningAjaxCalls.indexOf(ajaxCall);
599 if(index_of_call != -1) {
600 // null this ajaxCall in the runningAjaxCalls array if not already nulled
601 //runningAjaxCalls[index_of_call] = null;
602
603 // remove this 1 call from the runningAjaxCalls array
604 runningAjaxCalls.splice(index_of_call, 1);
605 }
606
607 });
608
609 // now we've launched an ajax call, let's add it to our list of runningAjaxCalls
610 runningAjaxCalls.push(ajaxCall);
611 }
612 else {
613 // USES GSAJAXAPI.JS to do AJAX. In this case, the payload must be in URL form
614
615 var splitURL = url.split("?");
616 url = splitURL[0]; // base URL
617 var params = splitURL[1]; // query-string
618
619 // Don't need to delete objects created with 'new' in JavaScript. Garbage collection will do it.
620 // http://stackoverflow.com/questions/4869712/new-without-delete-on-same-variable-in-javascript
621 var gsapi = new GSAjaxAPI(url);
622
623 // ajax calls default to using method GET, we want to do POST operations for get-meta and set-meta requests
624 // since get-meta-array and especially set-meta-array can be large, e.g. for user comments.
625
626 if(async_setting) {
627 gsapi.urlPostAsync(url, params, successResponseFunction, errorResponseFunction);
628 } else {
629 ajaxResponse = gsapi.urlPostSync(url, params);
630 //ajaxResponse = ajaxResponse;
631 }
632
633// console.log("*** (" + callingFunction + ") Response from server: " + ajaxResponse);
634
635 }
636
637// console.log("*** Finished ajax call to: " + url);
638// console.log("*** Got response: " + ajaxResponse);
639
640 return ajaxResponse;
641}
642
643
644// Prepare the payload (data package) to transmit to metadataserver.pl over AJAX.
645// These next 2 functions prepare both the URL version of the payload and the data object version
646// of the payload. Then calling functions, and the _callMetadataServer() function they call, will control
647// and determine which of the two forms ultimately gets used.
648// (Constructing both URL and data structure, as I could not successfully convert old URL way to data structure
649// http://stackoverflow.com/questions/8648892/convert-url-parameters-to-a-javascript-object)
650gs.functions.getBasicDataForMetadataServer = function(metaServerCommand, collection, site, documentID, metadataName,
651 metamode, metadataValue, prevMetadataValue, metadataPosition)
652{
653 // if we're doing set- or remove- metadata operations,
654 // then we need to change the data params that will make up the query string
655 // to make sure we go through GS3's authentication
656 // 1. prefix meta names with s1,
657 // 2. use s1.collection for collectionName since c is a special param name for GS2Construct
658 // 3. Additional parameters for rerouting through Authentication: a=g&rt=r&ro=1&s=ModifyMetadata
659
660 var modifyingMeta = false;
661 var prefix = "";
662 var colPropName = "c";
663 var baseURL = "cgi-bin/metadata-server.pl?";
664
665 // if we need authentication:
666 if(metaServerCommand.indexOf("set-") != -1 || metaServerCommand.indexOf("remove-") != -1) {
667 modifyingMeta = true;
668 prefix = "s1.";
669 colPropName = prefix+"collection"; // "s1.collection"
670 baseURL = gs.xsltParams.library_name + "?a=g&rt=r&ro=1&s=ModifyMetadata&";
671 }
672
673
674 // 1. when using jQuery to POST, need to invoke AJAX with a data structure rather than a URL
675 var data = {};
676
677 // customizable portion of ajax call
678 data[prefix+"a"] = metaServerCommand;
679 data[colPropName] = collection;
680 data[prefix+"site"] = site;
681 data[prefix+"d"] = documentID;
682 data[prefix+"metaname"] = metadataName;
683 data[prefix+"metapos"] = metadataPosition;
684 data[prefix+"metavalue"] = metadataValue;
685 data[prefix+"prevmetavalue"] = prevMetadataValue;
686 data[prefix+"metamode"] = metamode;
687
688 if(modifyingMeta) {
689 // fixed portion of url: add the a=g&rt=r&ro=1&s=ModifyMetadata part of the GS3 URL for
690 // going through authentication. Don't prefix "s1." to these!
691 data["a"] = "g";
692 data["rt"] = "r";
693 data["ro"] = "1";
694 data["s"] = "ModifyMetadata";
695 }
696
697 // 2. Construct the URL version of the metadata-server.pl operation:
698 // for GET requests, the URL can contain the data.
699 // Regular JavaScript AJAX code in gsajaxapi.js can also POST data in URL form, but not jQuery's .ajax().
700
701
702 // If doing set- or remove- (not get-) metadata, then rewrite URLs to call GS2Construct's ModfiyMetadata service instead (which will ensure this only works when authenticated).
703 // From:
704 // <gs3server>/cgi-bin/metadata-server.pl?a=set-archives-metadata&c=smallcol&site=localsite&d=HASH01454f31011f6b6b26eaf8d7&metaname=Title&metavalue=Moo&prevmetavalue=Blabla&metamode=override
705 // To:
706 // <gs3server>/library?a=g&rt=r&ro=1&s=ModifyMetadata&s1.a=set-archives-metadata&s1.collection=smallcol&s1.site=localsite&s1.d=HASH01454f31011f6b6b26eaf8d7&s1.metaname=Title&s1.metavalue=Moo&s1.prevmetavalue=Blabla&s1.metamode=override
707
708 var extraParams = "";
709
710 if(metadataValue != null) {
711 extraParams += "&"+prefix+"metavalue=" + metadataValue;
712 }
713
714 if(metadataPosition != null)
715 {
716 extraParams += "&"+prefix+"metapos=" + metadataPosition;
717 }
718
719 if(prevMetadataValue != null) {
720 extraParams += "&"+prefix+"prevmetavalue=" + prevMetadataValue;
721 }
722
723 var url = baseURL + prefix+"a=" + metaServerCommand + "&"+colPropName+"=" + collection + "&"+prefix+"site=" + site + "&"+prefix+"d=" + documentID + "&"+prefix+"metaname=" + metadataName + extraParams + "&"+prefix+"metamode=" + metamode;
724
725 // 3. Return both the constructed url & data variants of the payload to be transmitted over ajax
726 var payload = {
727 url: url,
728 data: data
729 };
730
731 return payload;
732}
733
734// See description for getBasicDataForMetadataServer()
735gs.functions.getComplexDataForMetadataServer = function(metaServerCommand, collection, site, docArray, metamode, where)
736{
737 var docArrayJSON = JSON.stringify(docArray);
738
739 // if we're doing set- or remove- metadata operations,
740 // then we need change the data params that will make up the query string
741 // to make sure we go through GS3's authentication
742 // 1. prefix meta names with s1,
743 // 2. use s1.collection for collectionName since c is a special param name for GS2Construct
744 // 3. Additional parameters for rerouting through Authentication: a=g&rt=r&ro=1&s=ModifyMetadata
745
746 var modifyingMeta = false;
747 var prefix = "";
748 var colPropName = "c";
749 var baseURL = "cgi-bin/metadata-server.pl?";
750
751 // if we need authentication:
752 if(metaServerCommand.indexOf("set-") != -1 || metaServerCommand.indexOf("remove-") != -1) {
753 modifyingMeta = true;
754 prefix = "s1.";
755 colPropName = prefix+"collection"; // "s1.collection"
756 baseURL = gs.xsltParams.library_name + "?a=g&rt=r&ro=1&s=ModifyMetadata&";
757 }
758
759 // 1. when using jQuery to POST, need to invoke AJAX with a data structure rather than a URL
760 var data = {};
761
762 // customizable portion of ajax call
763 data[prefix+"a"] = metaServerCommand;
764 data[colPropName] = collection;
765 data[prefix+"site"] = site;
766 data[prefix+"json"] = docArrayJSON;
767
768 if(where != null) {
769 data[prefix+"where"] = where;
770 }
771 if (metamode!=null) {
772 data[prefix+"metamode"] = metamode;
773 }
774
775 if(modifyingMeta) {
776 // fixed portion of url: add the a=g&rt=r&ro=1&s=ModifyMetadata part of the GS3 URL for
777 // going through authentication. Don't prefix "s1." to these!
778 data["a"] = "g";
779 data["rt"] = "r";
780 data["ro"] = "1";
781 data["s"] = "ModifyMetadata";
782 }
783
784
785 // 2. URL for when doing AJAX in URL mode. GET with jQuery allows the data to be part of the URL, but
786 // not jQuery POST. But our regular JavaScript AJAX code in gsajaxapi.js allows GET and POST with URLs
787 // containing the data.
788
789 var params = prefix+"a=" + escape(metaServerCommand); //"a=set-metadata-array";
790 if(where != null) {
791 params += "&"+prefix+"where=" + escape(where); // if where not specified, meta-server will default to setting index meta
792 //} else {
793 // params += "&"+prefix+"where=import|archives|index";
794 }
795 params += "&"+colPropName+"="+escape(collection);
796 params += "&"+prefix+"site="+escape(site);
797 params += "&"+prefix+"json="+escape(docArrayJSON);
798
799 if (metamode!=null) {
800 params += "&"+prefix+"metamode=" + escape(metamode);
801 }
802
803 // 3. Return both the constructed url & data variants of the payload to be transmitted over ajax
804 var payload = {
805 url: baseURL + params,
806 data: data
807 };
808
809 return payload;
810}
811
812/*************************
813* SET METADATA FUNCTIONS *
814*************************/
815
816gs.functions.setImportMetadata = function(collection, site, documentID, metadataName, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
817{
818 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
819
820 gs.functions._callMetadataServer(
821 "setImportMetadata",
822 gs.functions.getBasicDataForMetadataServer("set-import-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, null /*metapos*/),
823 successResponseFunction,
824 errorResponseFunction);
825
826}
827
828gs.functions.setArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
829{
830 if(metadataValue) console.log("metaval: " + metadataValue + " | " + gs.functions.debug_unicode_string(metadataValue)); //metadataValue.hexEncode()
831 if(prevMetadataValue) console.log("prevmetaval: " + prevMetadataValue + " | " + gs.functions.debug_unicode_string(prevMetadataValue));
832
833 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
834
835 if(metadataPosition != null) {
836 prevMetadataValue = null; // to force the same ultimate behaviour as in the old version of this code
837 }
838
839 gs.functions._callMetadataServer(
840 "setArchivesMetadata",
841 gs.functions.getBasicDataForMetadataServer("set-archives-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, metadataPosition),
842 successResponseFunction,
843 errorResponseFunction);
844
845}
846
847gs.functions.setIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
848{
849 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
850
851 if(metadataPosition != null) {
852 prevMetadataValue = null; // to force the same ultimate behaviour as in the old version of this code
853 }
854
855 // old version of this function would only call _callMetadataServer if either metapos
856 // or prevMetaValue had a value. So sticking to the same behaviour in rewriting this function.
857 if(metadataPosition != null || prevMetadataValue != null) {
858
859 gs.functions._callMetadataServer(
860 "setIndexMetadata",
861 gs.functions.getBasicDataForMetadataServer("set-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, metadataPosition),
862 successResponseFunction,
863 errorResponseFunction);
864 }
865}
866
867gs.functions.setMetadata = function(collection, site, documentID, metadataName, metadataValue, metamode, successResponseFunction, errorResponseFunction)
868{
869 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
870
871 var nameArray = ["setImportMetadata", "setArchivesMetadata", "setIndexMetadata"];
872 var functionArray = ["set-import-metadata", "set-archives-metadata", "set-metadata"];
873
874 for(var i = 0; i < nameArray.length; i++)
875 {
876 // previous version of this function did not allow setting metapos or prevMetavalue
877 // so leaving the behaviour the same along with function signature.
878 gs.functions._callMetadataServer(
879 nameArray[i],
880 gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, metamode, metadataValue, null /*prevMetadataValue*/, null /*metadataPosition*/),
881 successResponseFunction,
882 errorResponseFunction);
883 }
884}
885
886// New. Modified version of the GS2 version of this method in gsajaxapi.js.
887// The where parameter can be specified as one or more of: import, archives, index, live
888// separated by |. If null, it is assumed to be index which is the original default
889// behaviour of calling set-metadata-array. E.g. where=import|archives|index
890// THIS METHOD IS SYNCHRONOUS by default. Set forceSync to false to override this default behaviour
891gs.functions.setMetadataArray = function(collection, site, docArray, metamode, where, successResponseFunction, forceSync, errorResponseFunction)
892{
893 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
894
895 var payload = gs.functions.getComplexDataForMetadataServer("set-metadata-array", collection, site, docArray, metamode, where);
896
897 // set operations are generally synchronous, but allow calling function to force ajax call
898 // to be synchronous or not. Default is synchronous, as it was for GS2
899 if(forceSync == null) {
900 forceSync = true;
901 }
902
903 //console.log("cgi-bin/metadata-server.pl?"+params);
904
905 var response = gs.functions._callMetadataServer("Setting metadata in "+where, payload, successResponseFunction, errorResponseFunction, {"forceSync": forceSync, "requestMethod": "POST"});
906
907 return response;
908}
909
910
911/*************************
912* GET METADATA FUNCTIONS *
913*************************/
914
915// New. Modified version of the GS2 version of this method in gsajaxapi.js.
916// See description for setMetadataArray above for information about the 'where' parameter.
917// THIS METHOD IS SYNCHRONOUS BY DEFAULT. Set forceSync to false to override this default behaviour
918gs.functions.getMetadataArray = function(collection, site, docArray, where, successResponseFunction, forceSync, errorResponseFunction)
919{
920 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
921
922 var payload = gs.functions.getComplexDataForMetadataServer("get-metadata-array", collection, site, docArray, null /*metamode*/, where);
923
924 // get operations are generally asynchronous, but allow calling function to force ajax call
925 // to be synchronous or not. Default for get-metadata-array is synchronous, as it was for GS2
926 if(forceSync == null) {
927 forceSync = true;
928 }
929 // Objects/maps can use identifiers or strings for property names
930 // http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices
931 // https://www.w3schools.com/js/js_objects.asp
932 var response = gs.functions._callMetadataServer("Getting metadata from "+where, payload, successResponseFunction, errorResponseFunction, {"forceSync":forceSync, "requestMethod": "POST"});
933
934 return response;
935}
936
937
938gs.functions.getImportMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
939{
940 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
941
942 var payload = gs.functions.getBasicDataForMetadataServer("get-import-metadata", collection, site, documentID, metadataName);
943 gs.functions._callMetadataServer("getImportMetadata", payload, function(responseText) {
944 var metadata = new GSMetadata(collection, site, documentID, metadataName, null, null, responseText);
945 if(successResponseFunction != null)
946 {
947 successResponseFunction(metadata);
948 }
949 },
950 errorResponseFunction);
951}
952
953gs.functions.getArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, successResponseFunction, errorResponseFunction)
954{
955 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
956
957 var payload = gs.functions.getBasicDataForMetadataServer("get-archives-metadata", collection, site, documentID, metadataName, null /*metamode*/, null /*metavalue*/, null /*prevmetavalue*/, metadataPosition);
958
959 gs.functions._callMetadataServer("getArchivesMetadata", payload, function(responseText) {
960 var metadata = new GSMetadata(collection, site, documentID, metadataName, null, metadataPosition, responseText); // indexPos, archivesPos, metaval (responseText)
961 if(successResponseFunction != null)
962 {
963 successResponseFunction(metadata);
964 }
965 },
966 errorResponseFunction);
967}
968
969gs.functions.getIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, successResponseFunction, errorResponseFunction)
970{
971 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
972
973 var payload = gs.functions.getBasicDataForMetadataServer("get-metadata", collection, site, documentID, metadataName, null /*metamode*/, null /*metavalue*/, null /*prevmetavalue*/, metadataPosition);
974
975 gs.functions._callMetadataServer("getIndexMetadata", payload, function(responseText) {
976 var metadata = new GSMetadata(collection, site, documentID, metadataName, metadataPosition, null, responseText); // indexPos, archivesPos, metaval (responseText)
977 if(successResponseFunction != null)
978 {
979 successResponseFunction(metadata);
980 }
981 },
982 errorResponseFunction);
983}
984
985/****************************
986* REMOVE METADATA FUNCTIONS *
987****************************/
988
989gs.functions.removeImportMetadata = function(collection, site, documentID, metadataName, metadataValue, successResponseFunction, errorResponseFunction)
990{
991 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
992
993 gs.functions._callMetadataServer(
994 "removeImportMetadata",
995 gs.functions.getBasicDataForMetadataServer("remove-import-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, null /*metapos*/),
996 successResponseFunction,
997 errorResponseFunction);
998}
999
1000gs.functions.removeArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, successResponseFunction, errorResponseFunction)
1001{
1002 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1003
1004 if(metadataPosition != null) {
1005 metadataValue = null; // retaining behaviour of previous version of this function removeArchivesMetadata()
1006 }
1007
1008 gs.functions._callMetadataServer(
1009 "removeArchivesMetadata",
1010 gs.functions.getBasicDataForMetadataServer("remove-archives-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, metadataPosition),
1011 successResponseFunction,
1012 errorResponseFunction);
1013}
1014
1015gs.functions.removeIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, successResponseFunction, errorResponseFunction)
1016{
1017 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1018
1019 if(metadataPosition != null) {
1020 metadataValue = null; // retaining behaviour of previous version of this function removeIndexMetadata()
1021 }
1022
1023 gs.functions._callMetadataServer(
1024 "removeIndexMetadata",
1025 gs.functions.getBasicDataForMetadataServer("remove-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, metadataPosition),
1026 successResponseFunction,
1027 errorResponseFunction);
1028}
1029
1030gs.functions.removeMetadata = function(collection, site, documentID, metadataName, metadataValue, successResponseFunction, errorResponseFunction)
1031{
1032 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1033
1034 var nameArray = ["removeImportMetadata", "removeArchivesMetadata", "removeIndexMetadata"];
1035 var functionArray = ["remove-import-metadata", "remove-archives-metadata", "remove-metadata"];
1036
1037 for(var i = 0; i < nameArray.length; i++)
1038 {
1039 gs.functions._callMetadataServer(
1040 nameArray[i],
1041 gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, null /*metapos*/),
1042 successResponseFunction,
1043 errorResponseFunction);
1044 }
1045}
1046
1047gs.functions.removeMetadataAtPos = function(collection, site, documentID, metadataName, metadataPosition, successResponseFunction, errorResponseFunction)
1048{
1049 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1050
1051 var nameArray = ["removeImportMetadata", "removeArchivesMetadata", "removeIndexMetadata"];
1052 var functionArray = ["remove-import-metadata", "remove-archives-metadata", "remove-metadata"];
1053
1054 for(var i = 0; i < nameArray.length; i++)
1055 {
1056 gs.functions._callMetadataServer(
1057 nameArray[i],
1058 gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, null /*metamode*/, null /*metadataValue*/, null /*prevmetavalue*/, metadataPosition),
1059 successResponseFunction,
1060 errorResponseFunction);
1061 }
1062}
1063
1064// metamode=override is in theory supported by remove_archives and remove_import metadata
1065// but not by remove_index and remove_live metadata. However, even in the former 2 cases,
1066// passing in metamode=override still appears to be ineffective, see recent svn revision 38193.
1067// And metamode accumulate prevents all removal.
1068// So for now, this function forces metamode to null, regardless of what is passed in.
1069// At least this ensures a more consistent effect with remove_index (and remove_live) functions.
1070gs.functions.removeMetadataArray = function(collection, site, docArray, metamode,
1071 where, successResponseFunction, forceSync, errorResponseFunction)
1072{
1073 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1074
1075 var payload = gs.functions.getComplexDataForMetadataServer("remove-metadata-array", collection, site, docArray, null/*metamode*/, where);
1076
1077 // set operations are generally synchronous, but allow calling function to force ajax call
1078 // to be synchronous or not. Default is synchronous, as it was for GS2
1079 if(forceSync == null) {
1080 forceSync = true;
1081 }
1082
1083 //console.log("cgi-bin/metadata-server.pl?"+params);
1084
1085 var response = gs.functions._callMetadataServer("Removing metadata from "+where, payload, successResponseFunction, errorResponseFunction, {"forceSync": forceSync, "requestMethod": "POST"});
1086
1087 //console.log("in gs.functions.removeMetaArray: " + JSON.stringify(payload));
1088
1089 return response;
1090}
1091
1092
1093/***********************************************************
1094* ERASE METADATA FUNCTIONS *
1095* Erase all meta in docID (sectionID) that match metaname. *
1096***********************************************************/
1097
1098gs.functions.eraseImportMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
1099{
1100 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1101
1102 gs.functions._callMetadataServer(
1103 "eraseImportMetadata",
1104 gs.functions.getBasicDataForMetadataServer("erase-import-metadata", collection, site, documentID, metadataName, "override" /*metamode*/, null /*metadataValue*/, null /*prevmetavalue*/, null /*metadataPosition*/),
1105 successResponseFunction,
1106 errorResponseFunction);
1107}
1108
1109gs.functions.eraseArchivesMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
1110{
1111 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1112
1113 gs.functions._callMetadataServer(
1114 "eraseArchivesMetadata",
1115 gs.functions.getBasicDataForMetadataServer("erase-archives-metadata", collection, site, documentID, metadataName, "override" /*metamode*/, null /*metadataValue*/, null /*prevmetavalue*/, null /*metadataPosition*/),
1116 successResponseFunction,
1117 errorResponseFunction);
1118}
1119
1120gs.functions.eraseIndexMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
1121{
1122 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1123
1124 gs.functions._callMetadataServer(
1125 "eraseIndexMetadata",
1126 gs.functions.getBasicDataForMetadataServer("erase-metadata", collection, site, documentID, metadataName, "override" /*metamode*/, null /*metadataValue*/, null /*prevmetavalue*/, null /*metadataPosition*/),
1127 successResponseFunction,
1128 errorResponseFunction);
1129}
1130
1131gs.functions.eraseMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
1132{
1133 if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
1134
1135 var nameArray = ["eraseImportMetadata", "eraseArchivesMetadata", "eraseIndexMetadata"];
1136 var functionArray = ["erase-import-metadata", "erase-archives-metadata", "erase-metadata"];
1137
1138 for(var i = 0; i < nameArray.length; i++)
1139 {
1140 gs.functions._callMetadataServer(
1141 nameArray[i],
1142 gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, "override" /*metamode*/, null /*metadataValue*/, null /*prevmetavalue*/, null /*metadataPosition*/),
1143 successResponseFunction,
1144 errorResponseFunction);
1145 }
1146}
1147
1148// When user prematurely navigates away from current page before the page has finished all
1149// running ajax calls, abort those still running. Else the error callback is called
1150// and error messages are displayed.
1151// https://stackoverflow.com/questions/13258784/stopping-an-ajax-call-on-page-unload
1152// See also gs.functions._callMetadataServer() which maintains the array of running ajax calls
1153// and removes finished calls from the runningAjaxCalls array.
1154//window.onbeforeunload = function(event) {
1155$(window).on("beforeunload", function(event) {
1156 var i = 0;
1157 var ajaxCall;
1158 for (i = 0; i < runningAjaxCalls.length; i++) { // list of only *running* ajax calls
1159 ajaxCall = runningAjaxCalls[i];
1160 // abort each still running ajaxCall (gets removed from runningAjaxCalls list elsewhere)
1161 ajaxCall.abort();
1162 }
1163});
Note: See TracBrowser for help on using the repository browser.