Index: main/trunk/greenstone3/web/interfaces/default/js/documentedit_scripts_util.js
===================================================================
--- main/trunk/greenstone3/web/interfaces/default/js/documentedit_scripts_util.js	(revision 32872)
+++ main/trunk/greenstone3/web/interfaces/default/js/documentedit_scripts_util.js	(revision 32873)
@@ -285,5 +285,5 @@
 	var resultArray = new Array();
 	
-	console.log("**** changesToUpdate::editableElementsInitialisationProgress = " + editableElementsInitialisationProgress);
+	//console.log("**** changesToUpdate::editableElementsInitialisationProgress = " + editableElementsInitialisationProgress);
 	
 	// Only want to check for valid edited states if the editableInitStates has been fully set up:
Index: main/trunk/greenstone3/web/interfaces/default/js/gsajaxapi.js
===================================================================
--- main/trunk/greenstone3/web/interfaces/default/js/gsajaxapi.js	(revision 32872)
+++ main/trunk/greenstone3/web/interfaces/default/js/gsajaxapi.js	(revision 32873)
@@ -243,4 +243,8 @@
 	// that we want to dynamically update when the async post process has finished
 	
+	// NOTE: if passing in an error callback function *separate* from success callback (i.e. if current callback param won't check xmlHttp.status == 200)
+	// then see https://stackoverflow.com/questions/1442425/detect-xhr-error-is-really-due-to-browser-stop-or-click-to-new-page
+	// and https://stackoverflow.com/questions/8866761/xmlhttprequest-ajax-error
+	
 	var typeof_callback = typeof(callback);
 	if ((typeof_callback == "string") || (typeof_callback == "number") || (typeof_callback == "boolean")) {
Index: main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js
===================================================================
--- main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js	(revision 32872)
+++ main/trunk/greenstone3/web/interfaces/default/js/javascript-global-functions.js	(revision 32873)
@@ -432,9 +432,12 @@
 //
 // - payload param: contains both the URL form and the data object form of the package to transmit over
-// AJAX to metadataserver.pl. Based on the parameters and some internal variables, callMetadataServer()
+// AJAX to metadataserver.pl. Based on the parameters and some internal variables, _callMetadataServer()
 // determines which to use.
 // - opts param: No function overloading in JavaScript. Can pass a custom object, however can pass opts,
 // see http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices
-gs.functions.callMetadataServer = function(callingFunction, payload, responseFunction, opts)
+//
+// BEWARE:
+// errorResponseFunction is at present only called on error if using jQuery ajax (sync/async) and not if using gsajaxapi (sync/async).
+gs.functions._callMetadataServer = function(callingFunction, payload, successResponseFunction, errorResponseFunction, opts)
 {
 
@@ -529,11 +532,15 @@
 		//console.log(xml);
 		
-		if(responseFunction != null) {
+		if(successResponseFunction != null) {
 		    
-		    responseFunction(response);
+		    successResponseFunction(response);
 		}
 	    })
 	    .error(function() {
-		console.log("(" + callingFunction + ") Failed");
+			if(errorResponseFunction != null) { 
+				errorResponseFunction(response);
+			} else {
+				console.log("(" + callingFunction + ") Failed");
+			}
 	    });
     }
@@ -553,5 +560,5 @@
 	
 	if(async_setting) {
-	    gsapi.urlPostAsync(url, params, responseFunction);
+	    gsapi.urlPostAsync(url, params, successResponseFunction);
 	} else {
 	    ajaxResponse = gsapi.urlPostSync(url, params);
@@ -571,5 +578,5 @@
 // Prepare the payload (data package) to transmit to metadataserver.pl over AJAX.
 // These next 2 functions prepare both the URL version of the payload and the data object version of 
-// of the payload. Then calling functions, and the callMetadataServer() function they call, will control
+// of the payload. Then calling functions, and the _callMetadataServer() function they call, will control
 // and determine which of the two forms ultimately gets used.
 // (Constructing both URL and data structure, as I could not successfully convert old URL way to data structure
@@ -741,46 +748,56 @@
 *************************/
 
-gs.functions.setImportMetadata = function(collection, site, documentID, metadataName, metadataValue, prevMetadataValue, metamode, responseFunction)
+gs.functions.setImportMetadata = function(collection, site, documentID, metadataName, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
 {    
-
-    gs.functions.callMetadataServer(
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+
+    gs.functions._callMetadataServer(
 	"setImportMetadata", 
 	gs.functions.getBasicDataForMetadataServer("set-import-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, null /*metapos*/), 
-	responseFunction);
-    
-}
-
-gs.functions.setArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, responseFunction)
-{
+	successResponseFunction,
+	errorResponseFunction);
+    
+}
+
+gs.functions.setArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
     if(metadataPosition != null) {
 	prevMetadataValue = null; // to force the same ultimate behaviour as in the old version of this code
     }
 
-    gs.functions.callMetadataServer(
+    gs.functions._callMetadataServer(
 	"setArchivesMetadata",
 	gs.functions.getBasicDataForMetadataServer("set-archives-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, metadataPosition),
-	responseFunction);
-
-}
-
-gs.functions.setIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, responseFunction)
-{
+	successResponseFunction,
+	errorResponseFunction);
+
+}
+
+gs.functions.setIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, prevMetadataValue, metamode, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
     if(metadataPosition != null) {
 	prevMetadataValue = null; // to force the same ultimate behaviour as in the old version of this code
     }
 
-    // old version of this function would only call callMetadataServer if either metapos
+    // old version of this function would only call _callMetadataServer if either metapos
     // or prevMetaValue had a value. So sticking to the same behaviour in rewriting this function.
     if(metadataPosition != null || prevMetadataValue != null) { 
     
-	gs.functions.callMetadataServer(
+	gs.functions._callMetadataServer(
 	    "setIndexMetadata",
 	    gs.functions.getBasicDataForMetadataServer("set-metadata", collection, site, documentID, metadataName, metamode, metadataValue, prevMetadataValue, metadataPosition),
-	    responseFunction);
-    }
-}
-
-gs.functions.setMetadata = function(collection, site, documentID, metadataName, metadataValue, metamode, responseFunction)
-{
+	    successResponseFunction,
+		errorResponseFunction);
+    }
+}
+
+gs.functions.setMetadata = function(collection, site, documentID, metadataName, metadataValue, metamode, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
 	var nameArray = ["setImportMetadata", "setArchivesMetadata", "setIndexMetadata"];
 	var functionArray = ["set-import-metadata", "set-archives-metadata", "set-metadata"];
@@ -790,8 +807,9 @@
 	    // previous version of this function did not allow setting metapos or prevMetavalue
 	    // so leaving the behaviour the same along with function signature.
-	    gs.functions.callMetadataServer(
+	    gs.functions._callMetadataServer(
 		nameArray[i],
 		gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, metamode, metadataValue, null /*prevMetadataValue*/, null /*metadataPosition*/),
-		responseFunction);
+		successResponseFunction,
+		errorResponseFunction);
 	}
 }
@@ -802,6 +820,7 @@
 // behaviour of calling set-metadata-array. E.g. where=import|archives|index
 // THIS METHOD IS SYNCHRONOUS by default. Set forceSync to false to override this default behaviour
-gs.functions.setMetadataArray = function(collection, site, docArray, metamode, where, responseFunction, forceSync) 
+gs.functions.setMetadataArray = function(collection, site, docArray, metamode, where, successResponseFunction, forceSync, errorResponseFunction) 
 {  
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
 
     var payload = gs.functions.getComplexDataForMetadataServer("set-metadata-array", collection, site, docArray, metamode, where);
@@ -815,5 +834,5 @@
     //console.log("cgi-bin/metadata-server.pl?"+params);
     
-    var response = gs.functions.callMetadataServer("Setting metadata in "+where, payload, responseFunction, {"forceSync": forceSync, "requestMethod": "POST"});
+    var response = gs.functions._callMetadataServer("Setting metadata in "+where, payload, successResponseFunction, errorResponseFunction, {"forceSync": forceSync, "requestMethod": "POST"});
 	
     return response;
@@ -828,6 +847,8 @@
 // See description for setMetadataArray above for information about the 'where' parameter.
 // THIS METHOD IS SYNCHRONOUS BY DEFAULT. Set forceSync to false to override this default behaviour
-gs.functions.getMetadataArray = function(collection, site, docArray, where, responseFunction, forceSync)
-{
+gs.functions.getMetadataArray = function(collection, site, docArray, where, successResponseFunction, forceSync, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+
     var payload = gs.functions.getComplexDataForMetadataServer("get-metadata-array", collection, site, docArray, null /*metamode*/, where);    
 
@@ -840,5 +861,5 @@
     // http://stackoverflow.com/questions/456177/function-overloading-in-javascript-best-practices
     // https://www.w3schools.com/js/js_objects.asp
-    var response = gs.functions.callMetadataServer("Getting metadata from "+where, payload, responseFunction, {"forceSync":forceSync, "requestMethod": "POST"});
+    var response = gs.functions._callMetadataServer("Getting metadata from "+where, payload, successResponseFunction, errorResponseFunction, {"forceSync":forceSync, "requestMethod": "POST"});
 
     return response;
@@ -846,41 +867,49 @@
 
 
-gs.functions.getImportMetadata = function(collection, site, documentID, metadataName, responseFunction)
-{
+gs.functions.getImportMetadata = function(collection, site, documentID, metadataName, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+
     var payload = gs.functions.getBasicDataForMetadataServer("get-import-metadata", collection, site, documentID, metadataName);
-    gs.functions.callMetadataServer("getImportMetadata", payload, function(responseText) {
+    gs.functions._callMetadataServer("getImportMetadata", payload, function(responseText) {
 	    var metadata = new GSMetadata(collection, site, documentID, metadataName, null, null, responseText);
-		if(responseFunction != null)
-		{
-			responseFunction(metadata);
-		}
-	});
-}
-
-gs.functions.getArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, responseFunction)
-{
+		if(successResponseFunction != null)
+		{
+			successResponseFunction(metadata);
+		}
+	},
+	errorResponseFunction);
+}
+
+gs.functions.getArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+
     var payload = gs.functions.getBasicDataForMetadataServer("get-archives-metadata", collection, site, documentID, metadataName, null /*metamode*/, null /*metavalue*/, null /*prevmetavalue*/, metadataPosition);
 
-    gs.functions.callMetadataServer("getArchivesMetadata", payload, function(responseText) {
-	var metadata = new GSMetadata(collection, site, documentID, metadataName, null, metadataPosition, responseText); // indexPos, archivesPos, metaval (responseText)
-	if(responseFunction != null)
-	{
-	    responseFunction(metadata);
-	}
-    });
-}
-
-gs.functions.getIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, responseFunction)
-{
+    gs.functions._callMetadataServer("getArchivesMetadata", payload, function(responseText) {
+		var metadata = new GSMetadata(collection, site, documentID, metadataName, null, metadataPosition, responseText); // indexPos, archivesPos, metaval (responseText)
+		if(successResponseFunction != null)
+		{
+			successResponseFunction(metadata);
+		}
+    },
+	errorResponseFunction);
+}
+
+gs.functions.getIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+
     var payload = gs.functions.getBasicDataForMetadataServer("get-metadata", collection, site, documentID, metadataName, null /*metamode*/, null /*metavalue*/, null /*prevmetavalue*/, metadataPosition);
 
-    gs.functions.callMetadataServer("getIndexMetadata", payload, function(responseText) {
-	var metadata = new GSMetadata(collection, site, documentID, metadataName, metadataPosition, null, responseText); // indexPos, archivesPos, metaval (responseText)
-	
-	if(responseFunction != null)
-	{
-	    responseFunction(metadata);
-	}
-    });
+    gs.functions._callMetadataServer("getIndexMetadata", payload, function(responseText) {
+		var metadata = new GSMetadata(collection, site, documentID, metadataName, metadataPosition, null, responseText); // indexPos, archivesPos, metaval (responseText)		
+		if(successResponseFunction != null)
+		{
+			successResponseFunction(metadata);
+		}
+    },
+	errorResponseFunction);
 }
 
@@ -889,38 +918,49 @@
 ****************************/
 
-gs.functions.removeImportMetadata = function(collection, site, documentID, metadataName, metadataValue, responseFunction)
-{
-    gs.functions.callMetadataServer(
+gs.functions.removeImportMetadata = function(collection, site, documentID, metadataName, metadataValue, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
+    gs.functions._callMetadataServer(
 	"removeImportMetadata", 
 	gs.functions.getBasicDataForMetadataServer("remove-import-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, null /*metapos*/), 
-	responseFunction);
-}
-
-gs.functions.removeArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, responseFunction)
-{
+	successResponseFunction,
+	errorResponseFunction);
+}
+
+gs.functions.removeArchivesMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
     if(metadataPosition != null) {
 	metadataValue = null; // retaining behaviour of previous version of this function removeArchivesMetadata()
     }
 
-    gs.functions.callMetadataServer(
+    gs.functions._callMetadataServer(
 	"removeArchiveMetadata", 
 	gs.functions.getBasicDataForMetadataServer("remove-archives-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, metadataPosition), 
-	responseFunction);
-}
-
-gs.functions.removeIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, responseFunction)
-{
+	successResponseFunction,
+	errorResponseFunction);
+}
+
+gs.functions.removeIndexMetadata = function(collection, site, documentID, metadataName, metadataPosition, metadataValue, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
     if(metadataPosition != null) {
 	metadataValue = null; // retaining behaviour of previous version of this function removeIndexMetadata()
     }
 
-    gs.functions.callMetadataServer(
+    gs.functions._callMetadataServer(
 	"removeIndexMetadata", 
 	gs.functions.getBasicDataForMetadataServer("remove-metadata", collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, metadataPosition), 
-	responseFunction);
-}
-
-gs.functions.removeMetadata = function(collection, site, documentID, metadataName, metadataValue, responseFunction)
-{
+	successResponseFunction,
+	errorResponseFunction);
+}
+
+gs.functions.removeMetadata = function(collection, site, documentID, metadataName, metadataValue, successResponseFunction, errorResponseFunction)
+{
+	if( typeof errorResponseFunction === 'undefined' ) { errorResponseFunction = null; } // force error callback to be defined: either null or has value
+	
 	var nameArray = ["removeImportMetadata", "removeArchivesMetadata", "removeIndexMetadata"];
 	var functionArray = ["remove-import-metadata", "remove-archives-metadata", "remove-metadata"];
@@ -928,9 +968,10 @@
 	for(var i = 0; i < nameArray.length; i++)
 	{
-	    gs.functions.callMetadataServer(
+	    gs.functions._callMetadataServer(
 		nameArray[i],
 		gs.functions.getBasicDataForMetadataServer(functionArray[i], collection, site, documentID, metadataName, null /*metamode*/, metadataValue, null /*prevmetavalue*/, null /*metapos*/),
-		responseFunction);
-	}
-}
-
+		successResponseFunction,
+		errorResponseFunction);
+	}
+}
+
