/**
 * @fileoverview Zapatec Transport library. Used to fetch data from the server,
 * parse and serialize XML and JSON data.
 *
 * <pre>
 * Copyright (c) 2004-2009 by Zapatec, Inc.
 * http://www.zapatec.com
 * 1700 MLK Way, Berkeley, California,
 * 94709, U.S.A.
 * All rights reserved.
 * </pre>
 */
// $Id: transport.js 42001 2009-04-29 10:00:40Z maria $
if (typeof zapatecTransport != 'function') {
if (typeof Zapatec == 'undefined') {
/**
 * @ignore Namespace definition.
 */
Zapatec = function() {};
}
/**
 * @constructor
 */
zapatecTransport =
Zapatec.Transport = function() {};
// set Z.Transport flag into true
// default value for processing JSON
Zapatec.Transport.RELIABLE = false;
// Determine most current versions of ActiveX objects available
if (typeof ActiveXObject != 'undefined') {
/**
 * String variable with most current version of XMLDOM ActiveX object name
 * available.
 * @private
 */
Zapatec.Transport.XMLDOM = null;
/**
 * String variable with Most current version of XMLHTTP ActiveX object name
 * available.
 * @private
 */
Zapatec.Transport.XMLHTTP = null;
/**
 * @ignore
 * Returns first available ActiveX object name from the given list.
 *
 * @param {object} aVersions List of ActiveX object names to test
 * @return First available ActiveX object name or null
 * @type string
 */
Zapatec.Transport.pickActiveXVersion = function(aVersions) {
for (var iVn = 0; iVn < aVersions.length; iVn++) {
try {
var oDoc = new ActiveXObject(aVersions[iVn]);
if (oDoc) {
// If it gets to this point, the string worked
return aVersions[iVn];
}
} catch (oExpn) {};
}
return null;
};
/**
 * Most current version of XMLDOM ActiveX object.
 * @private
 */
Zapatec.Transport.XMLDOM = zapatecTransport.pickActiveXVersion([
'Msxml2.DOMDocument.4.0',
'Msxml2.DOMDocument.3.0',
'MSXML2.DOMDocument',
'MSXML.DOMDocument',
'Microsoft.XMLDOM'
]);
/**
 * Most current version of XMLHTTP ActiveX object.
 * @private
 */
Zapatec.Transport.XMLHTTP = zapatecTransport.pickActiveXVersion([
'Msxml2.XMLHTTP.4.0',
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP'
]);
/**
 * @ignore We don't need this anymore.
 */
Zapatec.Transport.pickActiveXVersion = null;
}
/**
 * Creates cross browser XMLHttpRequest object.
 *
 * @return New XMLHttpRequest object.
 * @type object
 */
Zapatec.Transport.createXmlHttpRequest = function() {
if (typeof ActiveXObject != 'undefined') {
try {
return new ActiveXObject(zapatecTransport.XMLHTTP);
} catch (oExpn) {};
}
if (typeof XMLHttpRequest != 'undefined') {
return new XMLHttpRequest();
}
return null;
};
/**
 * Checks if animated GIF is already displayed in the specified div.
 *
 * <pre>
 * Arguments object format:
 * {
 *   busyContainer: [object or string] element where to put animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL
 * }
 * </pre>
 *
 * @private
 * @param {object} oArg Arguments object
 * @return True if image is displayed
 * @type boolean
 */
Zapatec.Transport.isBusy = function(oArg) {
// Get container
var oContr = oArg.busyContainer;
if (typeof oContr == 'string') {
oContr = document.getElementById(oContr);
}
if (!oContr) {
return;
}
// Get image name
var sImage = oArg.busyImage;
if (typeof sImage != 'string') {
sImage = '';
}
sImage = sImage.split('/').pop();
if (!sImage.length) {
sImage = 'zpbusy.gif';
}
// Check if image is displayed
var oFC = oContr.firstChild;
if (oFC) {
oFC = oFC.firstChild;
if (oFC) {
oFC = oFC.firstChild;
if (oFC && oFC.tagName && oFC.tagName.toLowerCase() == 'img') {
var sSrc = oFC.getAttribute('src');
if (typeof sSrc == 'string' && sSrc.length) {
// Get last token
sSrc = sSrc.split('/').pop();
if (sSrc == sImage) {
return true;
}
}
}
}
}
return false;
};
/**
 * Shows animated GIF in the specified div.
 *
 * <pre>
 * Arguments object format:
 * {
 *   busyContainer: [object or string] element where to put animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL,
 *   busyImageWidth: [number or string, optional] image width,
 *   busyImageHeight: [number or string, optional] image height
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.showBusy = function(oArg) {
// Make sure image is not displayed yet
if (zapatecTransport.isBusy(oArg)) {
return;
}
// Get container
var oContr = oArg.busyContainer;
if (typeof oContr == 'string') {
oContr = document.getElementById(oContr);
}
if (!oContr) {
return;
}
// Get image name and dimensions
var sImage = oArg.busyImage;
var sImageWidth = oArg.busyImageWidth;
var sImageHeight = oArg.busyImageHeight;
if (typeof sImage != 'string' || !sImage.length) {
sImage = 'zpbusy.gif';
} else {
if (typeof sImageWidth == 'number' ||
 (typeof sImageWidth == 'string' && /\d$/.test(sImageWidth))) {
sImageWidth += 'px';
}
if (typeof sImageHeight == 'number' ||
 (typeof sImageHeight == 'string' && /\d$/.test(sImageHeight))) {
sImageHeight += 'px';
}
}
if (!sImageWidth) {
sImageWidth = '65px';
}
if (!sImageHeight) {
sImageHeight = '35px';
}
// Get path
var sPath = '';
// Check if path is specified
if (sImage.indexOf('/') < 0) {
// Use default path
if (Zapatec.zapatecPath) {
sPath = Zapatec.zapatecPath;
} else {
sPath = zapatecTransport.getPath('transport.js');
}
}
// Form tag
var aImg = [];
aImg.push('<img src="');
aImg.push(sPath);
aImg.push(sImage);
aImg.push('"');
if (sImageWidth || sImageHeight) {
aImg.push(' style="');
if (sImageWidth) {
aImg.push('width:');
aImg.push(sImageWidth);
aImg.push(';');
}
if (sImageHeight) {
aImg.push('height:');
aImg.push(sImageHeight);
}
aImg.push('"');
}
aImg.push(' />');
// Get container dimensions
var iContainerWidth = oContr.offsetWidth;
var iContainerHeight = oContr.offsetHeight;
// Display image
var oBusyContr = zapatecUtils.createElement('div');
oBusyContr.style.position = 'relative';
oBusyContr.style.zIndex = 2147483583;
var oBusy = zapatecUtils.createElement('div', oBusyContr);
oBusy.style.position = 'absolute';
oBusy.innerHTML = aImg.join('');
oContr.insertBefore(oBusyContr, oContr.firstChild);
// Move to the center of container
var iBusyWidth = oBusy.offsetWidth;
var iBusyHeight = oBusy.offsetHeight;
if (iContainerWidth > iBusyWidth) {
oBusy.style.left = oContr.scrollLeft +
 (iContainerWidth - iBusyWidth) / 2 + 'px';
}
if (iContainerHeight > iBusyHeight) {
oBusy.style.top = oContr.scrollTop +
 (iContainerHeight - iBusyHeight) / 2 + 'px';
}
};
/**
 * Removes animated GIF which was put by {@link Zapatec.Transport#showBusy}
 * from the specified div.
 *
 * <pre>
 * Arguments object format:
 * {
 *   busyContainer: [object or string] element where to put animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.removeBusy = function(oArg) {
// Get container
var oContr = oArg.busyContainer;
if (typeof oContr == 'string') {
oContr = document.getElementById(oContr);
}
if (!oContr) {
return;
}
// Make sure image is displayed
if (zapatecTransport.isBusy(oArg)) {
// Remove image
oContr.removeChild(oContr.firstChild);
}
};
/**
 * Fetches specified URL using new XMLHttpRequest object.
 *
 * <pre>
 * Asynchronous mode is recommended because it is safer and there is no risk of
 * having your script hang in case of network problem. Synchronous mode means
 * that the code will hang until a response comes back.
 *
 * When request is completed, one of provided callback functions is called:
 * onLoad on success or onError on error. In synchronous mode onLoad callback
 * can be omitted. Instead use returned object.
 *
 * onLoad callback function receives XMLHttpRequest object as argument and may
 * use its various properties like responseText, responseXML, etc.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value2' with urlencoded values. If you wish to send other
 * content, set appropriate contentType. E.g. 'multipart/form-data', 'text/xml',
 * etc.
 *
 * If server response contains non-ASCII characters, server must send
 * corresponding content-type header. E.g.
 * "Content-type: text/plain; charset=utf-8" or
 * "Content-type: text/plain; charset=windows-1251".
 *
 * Arguments object format:
 * {
 *   url: [string] relative or absolute URL to fetch,
 *   method: [string, optional] method ('GET', 'POST', 'HEAD', 'PUT'),
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   contentType: [string, optional] content type when using POST,
 *   content: [string or object, optional] postable string or DOM object data
 *    when using POST,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error,
 *   username: [string, optional] username,
 *   password: [string, optional] password,
 *   busyContainer: [object or string, optional] element or id of element where
 *    to put "Busy" animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL,
 *   busyImageWidth: [number or string, optional] image width,
 *   busyImageHeight: [number or string, optional] image height
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return XMLHttpRequest object or null on error
 * @type object
 */
Zapatec.Transport.fetch = function(oArg) {
// Check arguments
if (oArg == null || typeof oArg != 'object') {
return null;
}
if (!oArg.url) {
return null;
}
if (!oArg.method) {
oArg.method = 'GET';
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
if (!oArg.contentType && oArg.method.toUpperCase() == 'POST') {
oArg.contentType = 'application/x-www-form-urlencoded';
}
if (!oArg.content) {
oArg.content = null;
}
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
// Request URL
var oRequest = zapatecTransport.createXmlHttpRequest();
if (oRequest == null) {
return null;
}
// Show "Busy" animated GIF
zapatecTransport.showBusy(oArg);
// IE 6 calls onreadystatechange and then raises exception if local file was
// not found. This flag is used to prevent duplicate onError calls.
var bErrorDisplayed = false;
// Onready handler
var funcOnReady = function() {
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process response
try {
if (oRequest.status == 200 || oRequest.status == 304 ||
(location.protocol == 'file:' && !oRequest.status)) {
// OK or found, but determined unchanged and loaded from cache
if (typeof oArg.onLoad == 'function') {
oArg.onLoad(oRequest);
}
} else if (!bErrorDisplayed) {
bErrorDisplayed = true;
// 404 Not found, etc.
zapatecTransport.displayError(
oRequest.status,
"Error: Can't fetch " + oArg.url + '. ' + (oRequest.statusText || ''),
oArg.onError
);
}
} catch (oExpn) {
if (oExpn.name == 'NS_ERROR_NOT_AVAILABLE') {
// Firefox 1.5 raises exception on attempt to access any property of
// oRequest if URL was not found
if (!bErrorDisplayed) {
bErrorDisplayed = true;
zapatecTransport.displayError(
0,
"Error: Can't fetch " + oArg.url + '. File not found.',
oArg.onError
);
}
} else {
// Other exception
throw(oExpn);
}
};
};
try {
// Open request
if (typeof oArg.username != 'undefined' &&
typeof oArg.password != 'undefined') {
oRequest.open(
oArg.method,
oArg.url,
oArg.async,
oArg.username,
oArg.password
);
} else {
oRequest.open(
oArg.method,
oArg.url,
oArg.async
);
}
// Prevent duplicate funcOnReady call in synchronous mode
if (oArg.async) {
// Set onreadystatechange handler
oRequest.onreadystatechange = function() {
if (oRequest.readyState == 4) {
// Request complete
funcOnReady();
// Prevent memory leak
oRequest.onreadystatechange = {};
}
};
}
// Set content type if needed
if (oArg.contentType) {
oRequest.setRequestHeader('Content-Type', oArg.contentType);
}
// Send request
oRequest.send(oArg.content);
// In synchronous mode result is ready on next line
if (!oArg.async) {
funcOnReady();
}
return oRequest;
} catch (oExpn) {
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process error
if (oExpn.name && oExpn.name == 'NS_ERROR_FILE_NOT_FOUND') {
if (!bErrorDisplayed) {
bErrorDisplayed = true;
zapatecTransport.displayError(
0,
"Error: Can't fetch " + oArg.url + '. File not found.',
oArg.onError
);
}
} else {
// Other exception
throw(oExpn);
}
};
return null;
};
/**
 * Parses HTML fragment into HTMLElement object.
 *
 * @param {string} sHtml HTML fragment
 * @param {object} oContainer Optional. Container element, which must be
 * populated with parsed HTML fragment.
 * @return If no container passed, div element containing parsed HTML fragment.
 * If container passed, undefined.
 * @type object
 */
Zapatec.Transport.parseHtml = function(sHtml, oContainer) {
// Convert to string
sHtml += '';
// Remove leading whitespace characters because Firefox and Opera don't parse
// fragment that starts from whitespace character
sHtml = sHtml.replace(/^\s+/g, '');
// Create temporaty container
var oTmpContr;
if (document.createElementNS) {
// use the XHTML namespace
oTmpContr =
 document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
} else {
oTmpContr = document.createElement('div');
}
// Parse HTML fragment
oTmpContr.innerHTML = sHtml;
// If there is container
if (oContainer) {
// Populate container
var oEl = oTmpContr.firstChild;
while (oEl) {
oContainer.appendChild(oEl);
oEl = oTmpContr.firstChild;
}
} else {
// Return temporary container element
return oTmpContr;
}
};
/**
 * Evaluates javascript in global scope.
 *
 * <p><b>
 * Note: Global variables must be declared without "var" keyword. Otherwise
 * they will be ignored by Safari.
 * </b></p>
 *
 * @param {string} sScript Script to evaluate
 */
Zapatec.Transport.evalGlobalScope = function(sScript) {
if (typeof sScript != 'string' || !sScript.match(/\S/)) {
return;
}
if (window.execScript) {
// IE
window.execScript(sScript, 'javascript');
} else if (window.eval) {
// Others
window.eval(sScript);
/*
 This should never be reached
} else {
var funcScript = new Function(sScript);
funcScript.call(window);
*/
}
};
/**
 * Assigns passed HTML fragment to the specified element's innerHTML property
 * and evaluates in global scope javascripts found in the fragment.
 *
 * <pre>
 * Arguments object format:
 * {
 *   html: [string] HTML fragment,
 *   container: [object or string, optional] element or id of element to put
 *    HTML fragment into
 * }
 * </pre>
 *
 * <p><b>
 * Note: Scripts are executed after HTML fragment is assigned to innerHTML.
 * If external scripts are used, they are loaded asynchronously and execution
 * sequence is not preserved.
 * </b></p>
 *
 * <p><b>
 * Note: Global variables must be declared without "var" keyword. Otherwise
 * they will be ignored by Safari.
 * </b></p>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.setInnerHtml = function(oArg) {
// Check arguments
if (!oArg || typeof oArg.html != 'string') {
return;
}
var sHtml = oArg.html;
// Get container
var oContr = null;
if (typeof oArg.container == 'string') {
oContr = document.getElementById(oArg.container);
} else if (typeof oArg.container == 'object') {
oContr = oArg.container;
}
// Extract javascripts
var aScripts = [];
if (sHtml.match(/<\s*\/\s*script\s*>/i)) {
// Split whole string by </script>
var aTokens = sHtml.split(/<\s*\/\s*script\s*>/i);
var aHtml = [];
for (var iToken = aTokens.length - 1; iToken >= 0; iToken--) {
var sToken = aTokens[iToken];
if (sToken.match(/\S/)) {
// Search <script ... > in the middle of each token
var aMatch = sToken.match(/<\s*script([^>]*)>/i);
if (aMatch) {
// Separate HTML from javascript
var aCouple = sToken.split(/<\s*script[^>]*>/i);
// IE doesn't put empty tokens into the array
while (aCouple.length < 2) {
if (sToken.match(/^<\s*script[^>]*>/i)) {
// HTML part is absent
aCouple.unshift('');
} else {
// javascript part is absent
aCouple.push('');
}
}
// Save HTML fragment
aHtml.unshift(aCouple[0]);
// Get script attributes
var sAttrs = aMatch[1];
// Get script text
var srtScript = aCouple[1];
// Ignore script text if "src" attribute is present
if (sAttrs.match(/\s+src\s*=/i)) {
srtScript = '';
} else {
// Fix functions: function aaa() -> aaa = function()
srtScript = srtScript.replace(/function\s+([^(]+)/g, '$1=function');
}
aScripts.push([sAttrs, srtScript]);
} else if (iToken < aTokens.length - 1) {
// On error assume this token is a part of previous token
aTokens[iToken - 1] += '</script>' + sToken;
} else {
// If this is last token, assume it is HTML fragment
aHtml.unshift(sToken);
}
} else {
// Empty token
aHtml.unshift(sToken);
}
}
// Get HTML part
sHtml = aHtml.join('');
}
// Set inner HTML
if (oContr) {
// Opera hack
if (window.opera) {
// Without this line Opera will not form correct DOM structure if HTML
// fragment contains forms
oContr.innerHTML = '<form></form>';
}
oContr.innerHTML = sHtml;
}
// Evaluate javascripts
for (var iScript = 0; iScript < aScripts.length; iScript++) {
if (aScripts[iScript][1].length) {
// Evaluate in global scope
zapatecTransport.evalGlobalScope(aScripts[iScript][1]);
}
// Load external script
var sAttrs = aScripts[iScript][0];
sAttrs = zapatecUtils.multispacekill(sAttrs).replace(/ = /g, '=');
if (sAttrs.indexOf('src=') >= 0) {
// Get container
var oContr = document.body;
if (!oContr) {
oContr = document.getElementsByTagName('head')[0];
if (!oContr) {
oContr = document;
}
}
// Get attributes
var aAttrs = sAttrs.split(' ');
// Load script
var oScript = zapatecUtils.createElement('script');
for (var iAttr = 0; iAttr < aAttrs.length; iAttr++) {
var aAttr = aAttrs[iAttr].split('=');
if (aAttr.length > 1) {
oScript.setAttribute(aAttr[0],
 aAttr[1].match(/^[\s|"|']*([\s|\S]*[^'|"])[\s|"|']*$/)[1]);
} else {
oScript.setAttribute(aAttr[0], aAttr[0]);
}
}
// It's important for Safari to assign attributes before appending
oContr.appendChild(oScript);
}
}
};
/**
 * Fetches and parses XML document from the specified URL.
 *
 * <pre>
 * When XML document is fetched and parsed, one of provided callback functions
 * is called: onLoad on success or onError on error. In synchronous mode onLoad
 * callback can be omitted. Instead use returned object.
 *
 * onLoad callback function receives XMLDocument object as argument and may use
 * its documentElement and other properties.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be 0 unless Zapatec.Transport.fetch was used to fetch URL
 * and there was a problem during fetching.
 *
 * If method argument is not defined, more efficient XMLDOM in IE and
 * document.implementation.createDocument in Mozilla will be used to fetch
 * and parse document. Otherwise Zapatec.Transport.fetch will be used to fetch
 * document and Zapatec.Transport.parseXml to parse.
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value'. If you wish to send other content, set appropriate
 * contentType. E.g. to send XML string, you should set contentType: 'text/xml'.
 *
 * If server response contains non-ASCII characters, encoding must be specified.
 * E.g. <?xml version="1.0" encoding="utf-8"?> or 
 * <?xml version="1.0" encoding="windows-1251"?>.
 *
 * If server response contains non-ASCII characters, server must send
 * corresponding content-type header. E.g.
 * "Content-type: text/xml; charset=utf-8" or
 * "Content-type: text/xml; charset=windows-1251".
 *
 * Arguments object format:
 * {
 *   url: [string] relative or absolute URL to fetch,
 *   method: [string, optional] method ('GET', 'POST', 'HEAD', 'PUT'),
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   contentType: [string, optional] content type when using POST,
 *   content: [string or object, optional] postable string or DOM object data
 *    when using POST,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error,
 *   username: [string, optional] username,
 *   password: [string, optional] password,
 *   busyContainer: [object or string, optional] element or id of element where
 *    to put "Busy" animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL,
 *   busyImageWidth: [number or string, optional] image width,
 *   busyImageHeight: [number or string, optional] image height
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return XMLHttpRequest in asynchronous mode; XMLDocument in synchronous mode;
 * DOMDocument in IE in both synchronous and asynchronous modes; null on error
 * @type object
 */
Zapatec.Transport.fetchXmlDoc = function(oArg) {
// Check arguments
if (oArg == null || typeof oArg != 'object') {
return null;
}
if (!oArg.url) {
return null;
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
// Try more efficient methods first
if (!oArg.method && typeof oArg.username == 'undefined' &&
typeof oArg.password == 'undefined') {
if (document.implementation && document.implementation.createDocument) {
// Mozilla
var oDoc = null;
if (!oArg.reliable) {
oArg.reliable = Zapatec.Transport.RELIABLE;
}
// Form argument for fetch
var oFetchArg = {};
for (var sKey in oArg) {
oFetchArg[sKey] = oArg[sKey];
}
if (oArg.async) {
oFetchArg.onLoad = function(oRequest) {
// Prevent onload being called more than once
oFetchArg.onLoad = null;
// Parse xml response string
var parser = new DOMParser();
oDoc = parser.parseFromString(oRequest.responseText, "text/xml");
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process response
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
};
} else {
// Prevent duplicate parseXml call in synchronous mode
oFetchArg.onLoad = null;
}
// Fetch URL
var oRequest = zapatecTransport.fetch(oFetchArg);
if (oRequest) {
if (oArg.async) {
return oRequest;
} else {
// In synchronous mode result is ready on next line
// Parse xml response string
var parser = new DOMParser();
oDoc = parser.parseFromString(oRequest.responseText, "text/xml");
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process response
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
return oDoc;
}
}
return null;
}
if (typeof ActiveXObject != 'undefined') {
// IE
// Show "Busy" animated GIF
zapatecTransport.showBusy(oArg);
// Load document
try {
var oDoc = new ActiveXObject(zapatecTransport.XMLDOM);
oDoc.async = oArg.async;
// Prevent duplicate onXmlDocLoad call in synchronous mode
if (oArg.async) {
oDoc.onreadystatechange = function() {
if (oDoc.readyState == 4) {
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process response
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
// Prevent memory leak
oDoc.onreadystatechange = {};
}
};
}
oDoc.load(oArg.url);
// In synchronous mode result is ready on next line
if (!oArg.async) {
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
// Process response
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
}
return oDoc;
} catch (oExpn) {
// Remove "Busy" animated GIF
zapatecTransport.removeBusy(oArg);
};
}
}
// Try XMLHttpRequest
// Form argument for fetch
var oFetchArg = {};
for (var sKey in oArg) {
oFetchArg[sKey] = oArg[sKey];
}
if (oArg.async) {
oFetchArg.onLoad = function(oRequest) {
zapatecTransport.parseXml({
strXml: oRequest.responseText,
onLoad: oArg.onLoad,
onError: oArg.onError
});
};
} else {
// Prevent duplicate parseXml call in synchronous mode
oFetchArg.onLoad = null;
}
// Fetch URL
var oRequest = zapatecTransport.fetch(oFetchArg);
if (oRequest) {
if (oArg.async) {
return oRequest;
} else {
// In synchronous mode result is ready on next line
return zapatecTransport.parseXml({
strXml: oRequest.responseText,
onLoad: oArg.onLoad,
onError: oArg.onError
});
}
}
return null;
};
/**
 * Parses XML string into XMLDocument object.
 *
 * <pre>
 * When XML string is parsed, one of provided callback functions is called:
 * onLoad on success or onError on error. In synchronous mode onLoad callback
 * can be omitted. Instead use returned object.
 *
 * onLoad callback function receives XMLDocument object as argument and may use
 * its documentElement and other properties.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be always 0.
 *
 * Returns XMLDocument object, so onLoad callback function is optional.
 * Returned value and its documentElement property should be checked before
 * use because they can be null or undefined.
 *
 * If XML string contains non-ASCII characters, encoding must be specified.
 * E.g. <?xml version="1.0" encoding="utf-8"?> or 
 * <?xml version="1.0" encoding="windows-1251"?>.
 *
 * Arguments object format:
 * {
 *   strXml: XML string to parse [string],
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return XMLDocument object or null
 * @type object
 */
Zapatec.Transport.parseXml = function(oArg) {
if (oArg == null || typeof oArg != 'object') {
return null;
}
if (!oArg.strXml) {
return null;
}
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
if (window.DOMParser) {
// Mozilla
try {
var oDoc = (new DOMParser()).parseFromString(oArg.strXml, 'text/xml');
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
return oDoc;
} catch (oExpn) {
zapatecTransport.displayError(
0,
'Error: Cannot parse. String does not appear to be a valid XML fragment.',
oArg.onError
);
};
return null;
}
if (typeof ActiveXObject != 'undefined') {
// IE
try {
var oDoc = new ActiveXObject(zapatecTransport.XMLDOM);
oDoc.loadXML(oArg.strXml);
zapatecTransport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError);
return oDoc;
} catch (oExpn) {};
}
return null;
};
/**
 * Checks if there were errors during XML document fetching and parsing and
 * calls onLoad or onError callback function correspondingly.
 *
 * @private
 * @param {object} oDoc XMLDocument object
 * @param {function} onLoad Callback function provided by user
 * @param {function} onError Callback function provided by user
 */
Zapatec.Transport.onXmlDocLoad = function(oDoc, onLoad, onError) {
var sError = null;
if (oDoc.parseError) {
// Parsing error in IE
sError = oDoc.parseError.reason;
if (oDoc.parseError.srcText) {
sError += 'Location: ' + oDoc.parseError.url +
 '\nLine number ' + oDoc.parseError.line + ', column ' +
 oDoc.parseError.linepos + ':\n' +
 oDoc.parseError.srcText + '\n';
}
} else if (oDoc.documentElement &&
 oDoc.documentElement.tagName == 'parsererror') {
// If an error is caused while parsing, Mozilla doesn't throw an exception.
// Instead, it creates an XML string containing the details of the error:
// <parsererror xmlns="http://www.w3.org/1999/xhtml">XML Parsing Error: ...
// Check if strings has been generated.
sError = oDoc.documentElement.firstChild.data + '\n' +
 oDoc.documentElement.firstChild.nextSibling.firstChild.data;
} else if (!oDoc.documentElement) {
sError = 'String does not appear to be a valid XML fragment.';
}
if (sError) {
// Parsing error
zapatecTransport.displayError(
0,
'Error: Cannot parse. ' + sError,
onError
);
} else {
// Success
if (typeof onLoad == 'function') {
onLoad(oDoc);
}
}
};
/**
 * Serializes XMLDocument object into XML string.
 *
 * @param {object} oDoc XMLDocument object
 * @return XML string
 * @type string
 */
Zapatec.Transport.serializeXmlDoc = function(oDoc) {
if (window.XMLSerializer) {
// Mozilla
return (new XMLSerializer).serializeToString(oDoc);
}
if (oDoc.xml) {
// IE
return oDoc.xml;
}
};
/**
 * Fetches and parses JSON object from the specified URL.
 *
 * <pre>
 * When JSON object is fetched and parsed, one of provided callback functions
 * is called: onLoad on success or onError on error. In synchronous mode onLoad
 * callback can be omitted. Instead use returned object.
 *
 * onLoad callback function receives JSON object as argument.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be 0 unless there was a problem during fetching.
 *
 * Note: Some browsers implement caching for GET requests. Caching can be
 * prevented by adding 'r=' + Math.random() parameter to URL.
 *
 * If you use POST method, content argument should be something like
 * 'var1=value1&var2=value'. If you wish to send other content, set appropriate
 * contentType. E.g. to send XML string, you should set contentType: 'text/xml'.
 *
 * If server response contains non-ASCII characters, server must send
 * corresponding content-type header. E.g.
 * "Content-type: text/plain; charset=utf-8" or
 * "Content-type: text/plain; charset=windows-1251".
 *
 * Arguments object format:
 * {
 *   url: [string] relative or absolute URL to fetch,
 *   reliable: [boolean, optional] false (string will be parsed) or true
 *   (evaluated) (default: false),
 *   method: [string, optional] method ('GET', 'POST', 'HEAD', 'PUT'),
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   contentType: [string, optional] content type when using POST,
 *   content: [string or object, optional] postable string or DOM object data
 *    when using POST,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error,
 *   username: [string, optional] username,
 *   password: [string, optional] password,
 *   busyContainer: [object or string, optional] element or id of element where
 *    to put "Busy" animated GIF,
 *   busyImage: [string, optional] standard image name or custom image URL,
 *   busyImageWidth: [number or string, optional] image width,
 *   busyImageHeight: [number or string, optional] image height
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return JSON in synchronous mode; XMLHttpRequest in asynchronous mode; null
 * on error
 * @type object
 */
Zapatec.Transport.fetchJsonObj = function(oArg) {
// Check arguments
if (oArg == null || typeof oArg != 'object') {
return null;
}
if (!oArg.url) {
return null;
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
if (!oArg.reliable) {
oArg.reliable = Zapatec.Transport.RELIABLE;
}
// Form argument for fetch
var oFetchArg = {};
for (var sKey in oArg) {
oFetchArg[sKey] = oArg[sKey];
}
if (oArg.async) {
oFetchArg.onLoad = function(oRequest) {
zapatecTransport.parseJson({
strJson: oRequest.responseText,
reliable: oArg.reliable,
onLoad: oArg.onLoad,
onError: oArg.onError
});
};
} else {
// Prevent duplicate parseJson call in synchronous mode
oFetchArg.onLoad = null;
}
// Fetch URL
var oRequest = zapatecTransport.fetch(oFetchArg);
if (oRequest) {
if (oArg.async) {
return oRequest;
} else {
// In synchronous mode result is ready on next line
return zapatecTransport.parseJson({
strJson: oRequest.responseText,
reliable: oArg.reliable,
onLoad: oArg.onLoad,
onError: oArg.onError
});
}
}
return null;
};
/**
 * Parses JSON string into object.
 *
 * <pre>
 * When JSON string is parsed, one of provided callback functions is called:
 * onLoad on success or onError on error.
 *
 * onLoad callback function receives JSON object as argument.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: error code [number],
 *   errorDescription: human readable error description [string]
 * }
 * Error code will be always 0.
 *
 * Returns JSON object, so onLoad callback function is optional.
 * Returned value should be checked before use because it can be null.
 *
 * Arguments object format:
 * {
 *   strJson: JSON string to parse [string],
 *   reliable: false (string will be parsed) or true (evaluated) [boolean]
 *   (optional, false by default),
 *   onLoad: function reference to call on success [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return JSON object or null
 * @type object
 */
Zapatec.Transport.parseJson = function(oArg) {
if (oArg == null || typeof oArg != 'object') {
return null;
}
if (!oArg.reliable) {
oArg.reliable = Zapatec.Transport.RELIABLE;
}
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
var oJson = null;
if(typeof(oArg.strJson) == "string" && oArg.strJson.length > 0){
try {
if (oArg.reliable) {
if (oArg.strJson) {
oJson = eval('(' + oArg.strJson + ')');
}
} else {
oJson = zapatecTransport.parseJsonStr(oArg.strJson);
}
} catch (oExpn) {
var sError =
'Error: Cannot parse. String does not appear to be a valid JSON fragment: ' +
oExpn.message;
if (typeof oExpn.text == 'string') {
sError += '\n' + oExpn.text;
}
sError += '\n' + oArg.strJson;
zapatecTransport.displayError(0, sError, oArg.onError);
// onLoad should not be called in this case
return null;
};
}
if (typeof oArg.onLoad == 'function') {
oArg.onLoad(oJson);
}
return oJson;
};
/**
 * Parses JSON string into object.
 *
 * <pre>
 * Throws exception if parsing error occurs.
 *
 * JSON format is described at http://json.org/js.html.
 * </pre>
 *
 * @private
 * @param {string} text JSON string to parse
 * @return JSON object
 * @type object
 */
Zapatec.Transport.parseJsonStr = function(text) {
var p = /^\s*(([,:{}\[\]])|"(\\.|[^\x00-\x1f"\\])*"|-?\d+(\.\d*)?([eE][+-]?\d+)?|true|false|null)\s*/,
token,
operator;
function error(m, t) {
throw {
name: 'JSONError',
message: m,
text: t || operator || token
};
}
function next(b) {
if (b && b != operator) {
error("Expected '" + b + "'");
}
if (text) {
var t = p.exec(text);
if (t) {
if (t[2]) {
token = null;
operator = t[2];
} else {
operator = null;
try {
token = eval(t[1]);
} catch (e) {
error("Bad token", t[1]);
}
}
text = text.substring(t[0].length);
} else {
error("Unrecognized token", text);
}
} else {
// undefined changed to null because it is not supported in IE 5.0
token = operator = null;
}
}
function val() {
var k, o;
switch (operator) {
case '{':
next('{');
o = {};
if (operator != '}') {
for (;;) {
if (operator || typeof token != 'string') {
error("Missing key");
}
k = token;
next();
next(':');
o[k] = val();
if (operator != ',') {
break;
}
next(',');
}
}
next('}');
return o;
case '[':
next('[');
o = [];
if (operator != ']') {
for (;;) {
o.push(val());
if (operator != ',') {
break;
}
next(',');
}
}
next(']');
return o;
default:
if (operator !== null) {
error("Missing value");
}
k = token;
next();
return k;
}
}
next();
return val();
};
/**
 * Serializes JSON object into JSON string.
 *
 * @param {object} v JSON object
 * @param {boolean} bAllowFunctions If true, functions are not ignored
 * @return JSON string
 * @type string
 */
Zapatec.Transport.serializeJsonObj = function(v, bAllowFunctions) {
var a = [];
// Emit a string.
var e = function(s) {
a[a.length] = s;
};
// Convert a value.
var g = function(x) {
var c, i, l, v;
switch (typeof x) {
case 'object':
if (x) {
if (x instanceof Array) {
e('[');
l = a.length;
for (i = 0; i < x.length; i += 1) {
if (l < a.length) {
e(',');
}
g(x[i]);
}
e(']');
return;
} else if (x instanceof Date) {
e('"');
e(x.toString());
e('"');
return;
} else if (typeof x.toString != 'undefined') {
e('{');
l = a.length;
for (i in x) {
v = x[i];
if (x.hasOwnProperty(i) && typeof v != 'undefined' &&
(bAllowFunctions || typeof v != 'function')) {
if (l < a.length) {
e(',');
}
g(i);
e(':');
g(v);
}
}
e('}');
return;
}
}
e('null');
return;
case 'number':
e(isFinite(x) ? +x : 'null');
return;
case 'string':
l = x.length;
e('"');
for (i = 0; i < l; i += 1) {
c = x.charAt(i);
if (c >= ' ') {
if (c == '\\' || c == '"') {
e('\\');
}
e(c);
} else {
switch (c) {
case '\b':
e('\\b');
break;
case '\f':
e('\\f');
break;
case '\n':
e('\\n');
break;
case '\r':
e('\\r');
break;
case '\t':
e('\\t');
break;
default:
c = c.charCodeAt();
e('\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16));
}
}
}
e('"');
return;
case 'boolean':
e(String(x));
return;
case 'function':
if (bAllowFunctions) {
e(x.toString().replace(/function anonymous/g, 'function'));
} else {
e('null');
}
return;
default:
e('null');
return;
}
};
g(v);
return a.join('');
};
/**
 * Displays error message.
 *
 * <pre>
 * Calls onError callback function with following argument:
 * {
 *   errorCode [number]: error code,
 *   errorDescription [string]: human readable error description
 * }
 *
 * If onError is not passed, displays human readable error description into
 * debug window ({@link Zapatec#Debug} is required).
 * </pre>
 *
 * @private
 * @param {number} iErrCode Error code
 * @param {string} sError Human readable error description
 * @param {function} onError Callback function provided by user
 */
Zapatec.Transport.displayError = function(iErrCode, sError, onError) {
if (typeof onError == 'function') {
onError({
errorCode: iErrCode,
errorDescription: sError
});
} else if (typeof zapatecDebug == 'function') {
zapatecDebug.log.error('Zapatec.Transport: ' + sError);
}
};
/**
 * Translates a URL to the URL relative to the specified or to absolute URL.
 * Calls {@link Zapatec.Transport#fixUrl} for the URL.
 *
 * <pre>
 * Arguments object format:
 * {
 *   url: [string] absolute or relative URL to translate; absolute URLs are not
 *    translated, only fixed,
 *   relativeTo: [string, optional] "url" will be translated to the URL relative
 *    to this absolute or relative URL; default: current page URL
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 * @return Translated URL
 * @type string
 */
Zapatec.Transport.translateUrl = function(oArg) {
if (!oArg || !oArg.url) {
return null;
}
// Cut arguments part from url
var aFullUrl = oArg.url.split('?', 2);
var sUrl = aFullUrl[0];
// Check url
if (sUrl.indexOf(':') >= 0) {
// Absolute URL
aFullUrl[0] = zapatecTransport.fixUrl(sUrl);
return aFullUrl.join('?');
}
var oLocation = document.location;
var sPort = oLocation.port;
if (sPort) {
sPort = ':' + sPort;
}
if (sUrl.charAt(0) == '/') {
// Add hostname and return absolute URL
aFullUrl[0] = [
oLocation.protocol,
'//',
oLocation.hostname,
sPort,
zapatecTransport.fixUrl(sUrl)
].join('');
} else {
// Get relativeTo
var sLocation;
if (sPort) {
sLocation = [
oLocation.protocol,
'//',
oLocation.hostname,
sPort,
oLocation.pathname
].join('');
} else {
sLocation = oLocation.toString();
}
var sRelativeTo;
if (typeof oArg.relativeTo != 'string') {
// By default relative to current page URL
sRelativeTo = sLocation.split('?', 2)[0];
} else {
// Remove arguments from relativeTo
sRelativeTo = oArg.relativeTo.split('?', 2)[0];
// Check relativeTo
if (sRelativeTo.indexOf('/') < 0) {
// Relative to current page URL
sRelativeTo = sLocation.split('?', 2)[0];
} else if (sRelativeTo.charAt(0) != '/' &&
 sRelativeTo.indexOf(':') < 0) {
// Transform relativeTo to absolute URL to be able to translate URLs
// starting from ../
sRelativeTo = zapatecTransport.translateUrl({
url: sRelativeTo
});
}
}
// Remove #
sRelativeTo = sRelativeTo.split('#')[0];
// Remove file name
sRelativeTo = sRelativeTo.split('/');
sRelativeTo.pop();
sRelativeTo = sRelativeTo.join('/');
// Form new URL
aFullUrl[0] = zapatecTransport.fixUrl(sRelativeTo + '/' + sUrl);
}
// Restore arguments part
return aFullUrl.join('?');
};
/**
 * Removes '/../' and '/./' from URL.
 *
 * @param {string} sUrl URL
 * @return Fixed URL
 * @type string
 */
Zapatec.Transport.fixUrl = function(sUrl) {
// Split URL
var aTokens = sUrl.split('/');
// Form new URL
var aUrl = [];
var iTokens = aTokens.length;
var sToken;
for (var iToken = 0; iToken < iTokens; iToken++) {
sToken = aTokens[iToken];
if (sToken == '..') {
aUrl.pop();
} else if (sToken != '.') {
aUrl.push(sToken);
}
}
return aUrl.join('/');
};
/**
 * Holds currently loading URLs to prevent duplicate loads.
 * @private
 */
Zapatec.Transport.loading = {};
/**
 * Prevents duplicate loads of the same URL when second request is done before
 * first request is completed.
 *
 * <pre>
 * Arguments object format:
 * {
 *   url: [string] absolute URL,
 *   force: [boolean, optional] force reload if it is already loaded,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error
 * }
 *
 * Returned object format:
 *
 * If this URL is already loading by another process:
 * {
 *   loading: [boolean] always true
 * }
 *
 * Otherwise:
 * {
 *   onLoad: [function, optional] replacement for function to call on success,
 *   onError: [function, optional] replacement for function to call on error
 * }
 * </pre>
 *
 * @private
 * @param {object} oArg Arguments object
 * @return Returned object
 * @type object
 */
Zapatec.Transport.setupEvents = function(oArg) {
// Check arguments
if (!oArg) {
return {};
}
// If loading is forced, we don't need to check if it is already loading
// If EventDriven is not available, operate as in older versions
// Check if URL is passed
if (oArg.force || !Zapatec.EventDriven || !oArg.url) {
return {
onLoad: oArg.onLoad,
onError: oArg.onError
};
}
var sUrl = oArg.url;
// Add onLoad listener
if (typeof oArg.onLoad == 'function') {
Zapatec.EventDriven.addEventListener('zpTransportOnLoad' + sUrl,
 oArg.onLoad);
}
// Add onError listener
if (typeof oArg.onError == 'function') {
Zapatec.EventDriven.addEventListener('zpTransportOnError' + sUrl,
 oArg.onError);
}
// Check if it is already loading
if (zapatecTransport.loading[sUrl]) {
return {
loading: true
};
} else {
// Flag
zapatecTransport.loading[sUrl] = true;
// Replace original callbacks
return {
onLoad: new Function("Zapatec.EventDriven.fireEvent('zpTransportOnLoad" +
 sUrl + "');Zapatec.EventDriven.removeEvent('zpTransportOnLoad" +
 sUrl + "');Zapatec.EventDriven.removeEvent('zpTransportOnError" +
 sUrl + "');zapatecTransport.loading['" + sUrl + "'] = false;"),
onError: new Function('oError',
 "Zapatec.EventDriven.fireEvent('zpTransportOnError" +
 sUrl + "',oError);Zapatec.EventDriven.removeEvent('zpTransportOnLoad" +
 sUrl + "');Zapatec.EventDriven.removeEvent('zpTransportOnError" +
 sUrl + "');zapatecTransport.loading['" + sUrl + "'] = false;")
};
}
};
/**
 * Holds URLs of already loaded JS files to prevent duplicate loads.
 * @private
 */
Zapatec.Transport.loadedJS = {};
/**
 * Checks if specified JS file is already loaded.
 *
 * @private
 * @param {string} sUrl Absolute or relative URL of JS file
 * @param {string} sAbsUrl Optional. Absolute URL of JS file
 * @return Loaded or not
 * @type boolean
 */
Zapatec.Transport.isLoadedJS = function(sUrl, sAbsUrl) {
// Get absolute URL of the JS file
if (typeof sAbsUrl == 'undefined') {
sAbsUrl = zapatecTransport.translateUrl({url: sUrl});
}
// Check in the list of loaded
if (zapatecTransport.loadedJS[sAbsUrl]) {
return true;
}
// Try to find script tag
var aScripts = document.getElementsByTagName('script');
for (var iScript = 0; iScript < aScripts.length; iScript++) {
var sSrc = aScripts[iScript].getAttribute('src') || '';
if (sSrc == sUrl) {
// Add this URL to the list of loaded
zapatecTransport.loadedJS[sAbsUrl] = true;
return true;
}
}
// Not found
return false;
};
/**
 * Returns path to the specified js file. Iterates over all loaded script
 * elements starting from the end. Finds specified js file in src attribute of
 * the script element. Splits src attribute value and returns path without js
 * file name.
 *
 * @param {string} sScriptFileName Script file name, e.g. 'zpmywidget.js'
 * @return Path to the script, e.g. '../src/' or '' if path is not found
 * @type string
 */
Zapatec.Transport.getPath = function(sScriptFileName) {
// Get all script elements
var aScripts = document.getElementsByTagName('script');
// Find the script in the list
for (var iScript = aScripts.length - 1; iScript >= 0; iScript--) {
var sSrc = aScripts[iScript].getAttribute('src') || '';
var aTokens = sSrc.split('/');
// Remove last token
var sLastToken = aTokens.pop();
if (sLastToken == sScriptFileName) {
return aTokens.length ? aTokens.join('/') + '/' : '';
}
}
// Search in loaded JS files
for (var sSrc in zapatecTransport.loadedJS) {
var aTokens = sSrc.split('/');
// Remove last token
var sLastToken = aTokens.pop();
if (sLastToken == sScriptFileName) {
return aTokens.length ? aTokens.join('/') + '/' : '';
}
}
// Not found
return '';
};
/**
 * Writes script tag to the document. Checks if specified JS file is already
 * loaded unless bForce argument is true.
 *
 * <pre>
 * Note: This function must be invoked during page load because it uses
 * document.write method.
 *
 * If special zapatecDoNotInclude flag is set, this function does nothing.
 *
 * This function overwrites {@link Zapatec#include}.
 * </pre>
 *
 * @param {string} sSrc Src attribute value of the script element
 * @param {string} sId Optional. Id of the script element
 * @param {boolean} bForce Optional. Force reload if it is already loaded
 */
Zapatec.Transport.include = function(sSrc, sId, bForce) {
// Check flag
if (zapatecDoNotInclude) {
return;
}
// Get absolute URL of the JS file
var sAbsUrl = zapatecTransport.translateUrl({url: sSrc});
// Check if it is already loaded
if (!bForce && zapatecTransport.isLoadedJS(sSrc, sAbsUrl)) {
return;
}
// Include file
document.write('<script type="text/javascript" src="' + sSrc +
 (typeof sId == 'string' ? '" id="' + sId : '') + '"></script>');
// Add this URL to the list of loaded
zapatecTransport.loadedJS[sAbsUrl] = true;
};
/**
 * @ignore Overwrite.
 */
Zapatec.include = zapatecTransport.include;
/**
 * Includes JS file into the page. Allows URLs from foreign domains. Doesn't
 * check if the JS file is already included. File is loaded asynchronously.
 *
 * @param {string} sSrc Src attribute value of the script element
 * @param {string} sId Optional. Id of the script element
 */
Zapatec.Transport.includeJS = function(sSrc, sId) {
// Make sure it is asynchronous in all browsers
setTimeout(function() {
// Include file
var oContr = document.body;
if (!oContr) {
oContr = document.getElementsByTagName('head')[0];
if (!oContr) {
oContr = document;
}
}
var oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = sSrc;
if (typeof sId == 'string') {
oScript.id = sId;
}
// This is important for Safari to assign attributes before appending
oContr.appendChild(oScript);
}, 0);
};
/**
 * Fetches JS file using fetch and evaluates it in global scope.
 *
 * <pre>
 * When JS file is loaded successfully, onLoad callback function is called
 * without arguments. URL is added into Zapatec.Transport.loadedJS array
 * and will not be fetched again on next function call unless force argument is
 * set to true.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: [number] server status number (404, etc.),
 *   errorDescription: [string] human readable error description
 * }
 *
 * One of the arguments: module or url is required. When url is passed,
 * module argument is ignored.
 *
 * If module argument is used, function gets all "script" elements using
 * getElementsByTagName and searches for the first element having "src"
 * attribute value ending with (relativeModule + ".js") (default relativeModule
 * value is "transport"). Path to the module is taken from that src attribute
 * value and will be the same as path to relativeModule file.
 *
 * Arguments object format:
 * {
 *   url: [string, optional] absolute or relative URL of JS file,
 *   module: [string, optional] module name (file name without .js extension);
 *    ignored when "url" is defined,
 *   path: [string, optional] path where to search "module" (default is
 *    Zapatec.zapatecPath if it is defined); ignored when "url" is defined,
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   force: [boolean, optional] force reload if it is already loaded,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error
 * }
 *
 * Note: If "force" is used, you should add 'r=' + Math.random() parameter to
 * URL to prevent loading from browser cache.
 *
 * <b>
 * Note: Global variables must be declared without "var" keyword. Otherwise
 * they will be ignored by Safari.
 * </b>
 *
 * If special zapatecDoNotInclude flag is set, this function just calls onLoad
 * callback function.
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.loadJS = function(oArg) {
// Check arguments
if (!(oArg instanceof Object)) {
return;
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
// Get URL of JS file
var sUrl = null;
if (oArg.url) {
sUrl = oArg.url;
} else if (oArg.module) {
var sPath = '';
if (typeof oArg.path != 'undefined') {
sPath = oArg.path;
} else if (typeof Zapatec.zapatecPath != 'undefined') {
sPath = Zapatec.zapatecPath;
}
sUrl = sPath + oArg.module + '.js';
} else {
return;
}
// Get absolute URL of the JS file
var sAbsUrl = zapatecTransport.translateUrl({url: sUrl});
// Check arguments
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
// Check if it is already loaded
if (zapatecDoNotInclude ||
 (!oArg.force && zapatecTransport.isLoadedJS(sUrl, sAbsUrl))) {
// onLoad callback
if (typeof oArg.onLoad == 'function') {
oArg.onLoad();
}
return;
}
// Setup onLoad and onError events
var oHandlers = zapatecTransport.setupEvents({
url: sAbsUrl,
force: oArg.force,
onLoad: oArg.onLoad,
onError: oArg.onError
});
// Don't need to continue if this url is already loading by another process
if (oHandlers.loading) {
return;
}
// Load JS file
zapatecTransport.fetch({
url: sUrl,
async: oArg.async,
onLoad: function(oRequest) {
// Can be loaded in two processes simultaneously
if (oArg.force || !zapatecTransport.loadedJS[sAbsUrl]) {
var aTokens = sUrl.split('/');
// Remove last token
var sLastToken = aTokens.pop();
// Store path to current module
Zapatec.lastLoadedModule = aTokens.join('/') + '/';
// Evaluate code in global scope
zapatecTransport.evalGlobalScope(oRequest.responseText);
// clear path to last loaded module
Zapatec.lastLoadedModule = null;
// Add this URL to the list of loaded
zapatecTransport.loadedJS[sAbsUrl] = true;
}
// onLoad callback
if (typeof oHandlers.onLoad == 'function') {
oHandlers.onLoad();
}
},
onError: oHandlers.onError
});
};
/**
 * Includes CSS file into the page. Allows URLs from foreign domains. Doesn't
 * check if the CSS file is already included. File is loaded asynchronously.
 * Requires that head section of the page already exists because link tag
 * may appear only inside head.
 *
 * @param {string} sHref Href attribute value of the link element
 */
Zapatec.Transport.includeCSS = function(sHref) {
// May appear only inside head
var oContr = document.getElementsByTagName('head')[0];
if (!oContr) {
return;
}
var oLink = document.createElement('link');
oLink.setAttribute('rel', 'stylesheet');
oLink.setAttribute('type', 'text/css');
oLink.setAttribute('href', sHref);
oContr.appendChild(oLink);
};
/**
 * Holds URLs of already loaded CSS files to prevent duplicate loads.
 * @private
 */
Zapatec.Transport.loadedCss = {};
/**
 * Fetches style sheet using fetch and loads it into the document. Requires
 * utils/stylesheet.js module.
 *
 * <pre>
 * When stylesheet is loaded successfully, onLoad callback function is called
 * without arguments. URL is added into Zapatec.Transport.loadedCss array
 * and will not be fetched again on next function call unless force argument is
 * set to true.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * Arguments object format:
 * {
 *   url: absolute or relative URL of CSS file [string],
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   force: [boolean, optional] force reload if it is already loaded,
 *   onLoad: [function, optional] function reference to call on success,
 *   onError: [function, optional] function reference to call on error
 * }
 *
 * Note: If "force" is used, you should add 'r=' + Math.random() parameter to
 * URL to prevent loading from browser cache.
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.loadCss = function(oArg) {
// Check arguments
if (!(oArg instanceof Object)) {
return;
}
if (!oArg.url) {
return;
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
// Get absolute URL of the CSS file
var sAbsUrl = zapatecTransport.translateUrl({url: oArg.url});
// Check if it is already loaded
if (!oArg.force) {
if (zapatecTransport.loadedCss[sAbsUrl]) {
// onLoad callback
if (typeof oArg.onLoad == 'function') {
oArg.onLoad();
}
return;
}
var aLinks = document.getElementsByTagName('link');
for (var iLnk = 0; iLnk < aLinks.length; iLnk++) {
var sHref = aLinks[iLnk].getAttribute('href') || '';
// Make it absolute
sHref = zapatecTransport.translateUrl({url: sHref});
if (sHref == sAbsUrl) {
// Add this url to the list of loaded
zapatecTransport.loadedCss[sAbsUrl] = true;
// onLoad callback
if (typeof oArg.onLoad == 'function') {
oArg.onLoad();
}
return;
}
}
}
// Setup onLoad and onError events
var oHandlers = zapatecTransport.setupEvents({
url: sAbsUrl,
force: oArg.force,
onLoad: oArg.onLoad,
onError: oArg.onError
});
// Don't need to continue if this url is already loading by another process
if (oHandlers.loading) {
return;
}
// Load CSS file
zapatecTransport.fetch({
url: sAbsUrl,
async: oArg.async,
onLoad: function(oRequest) {
// Parse CSS file.
// Find URLs and translate them to absolute.
// Find @import rules and load corresponding CSS files.
var sCss = oRequest.responseText;
var aResultCss = [];
// Will hold image URLs to preload
var aImgUrls = [];
// Will hold CSS URLs to load
var aCssUrls = [];
// Move first cursor to the beginning of the string
var iPos = 0;
// Move second cursor to the pattern
var iNextPos = sCss.indexOf('url(', iPos);
while (iNextPos >= 0) {
// Move first cursor to the URL
iNextPos += 4;
// Check if this is @import rule
var sToken = sCss.substring(iPos, iNextPos);
var bIsImport = /@import\s+url\($/.test(sToken);
// Add part of the string before URL
aResultCss.push(sToken);
// Move second cursor to the new location to start the search from
iPos = iNextPos;
// Search the end of URL
iNextPos = sCss.indexOf(')', iPos);
if (iNextPos >= 0) {
// Remove quotes
var sImgUrl = sCss.substring(iPos, iNextPos);
sImgUrl = sImgUrl.replace(/['"]/g, '');
// Translate image URL relative to CSS file URL
sImgUrl = zapatecTransport.translateUrl({
url: sImgUrl,
relativeTo: oArg.url
});
// Convert to absolute URL
sImgUrl = zapatecTransport.translateUrl({
url: sImgUrl
});
// Add translated URL
aResultCss.push(sImgUrl);
// Add URL to the list
if (bIsImport) {
// Add CSS URL to load list
aCssUrls.push(sImgUrl);
} else {
// Add image URL to preload list
aImgUrls.push(sImgUrl);
}
// Move second cursor to the new location to start the search from
iPos = iNextPos;
// Search next pattern
iNextPos = sCss.indexOf('url(', iPos);
}
}
// Add the rest of string
aResultCss.push(sCss.substr(iPos));
// Get translated CSS text
sCss = aResultCss.join('');
// Load CSS files
zapatecTransport.loadCssList({
urls: aCssUrls,
async: oArg.async,
onLoad: function() {
// Add style sheet rules into the page
(new Zapatec.StyleSheet()).addParse(sCss);
// Fire event
if (typeof oHandlers.onLoad == 'function') {
oHandlers.onLoad();
}
}
});
// Add this URL to the list of loaded
zapatecTransport.loadedCss[sAbsUrl] = true;
// Preload images
zapatecTransport.preloadImages({
urls: aImgUrls,
timeout: 60000 // 1 minute
});
},
onError: oHandlers.onError
});
};
/**
 * Loads several CSS files one by one it into the document.
 *
 * <pre>
 * This function behaves differently from other Zapatec.Transport functions.
 * onLoad callback function will be called in any case, even if errors occured
 * during loading. If there are multiple errors, onError callback function will
 * be called once for every passed URL that wasn't loaded successfully.
 *
 * onLoad callback function is called without arguments.
 *
 * onError callback function receives following object:
 * {
 *   errorCode: server status number (404, etc.) [number],
 *   errorDescription: human readable error description [string]
 * }
 *
 * Arguments object format:
 * {
 *   urls: array of absolute or relative URLs of CSS files to load [object]
 *    (files will be loaded in order they appear in the array),
 *   async: [boolean, optional] use asynchronous mode (default: true),
 *   force: [boolean, optional] force reload if it is already loaded,
 *   onLoad: function reference to call on completion [function] (optional),
 *   onError: function reference to call on error [function] (optional)
 * }
 *
 * Note: If "force" is used, you should add 'r=' + Math.random() parameter to
 * URL to prevent loading from browser cache.
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.loadCssList = function(oArg) {
// Check arguments
if (!(oArg instanceof Object)) {
return;
}
if (typeof oArg.async == 'undefined') {
oArg.async = true;
}
if (!oArg.onLoad) {
oArg.onLoad = null;
}
if (!oArg.onError) {
oArg.onError = null;
}
if (!oArg.urls || !oArg.urls.length) {
// onLoad callback
if (typeof oArg.onLoad == 'function') {
oArg.onLoad();
}
return;
}
// Get first URL in the array
var sUrl = oArg.urls.shift();
// CSS file onLoad handler
var funcOnLoad = function() {
// Load the rest of URLs
zapatecTransport.loadCssList({
urls: oArg.urls,
async: oArg.async,
force: oArg.force,
onLoad: oArg.onLoad,
onError: oArg.onError
});
};
// Load CSS file
zapatecTransport.loadCss({
url: sUrl,
async: oArg.async,
force: oArg.force,
onLoad: funcOnLoad,
onError: function(oError) {
zapatecTransport.displayError(oError.errorCode, oError.errorDescription,
 oArg.onError);
funcOnLoad();
}
});
};
/**
 * Holds image preloads.
 * @private
 */
Zapatec.Transport.imagePreloads = [];
/**
 * Preloads one or several images at once. Requires utils/preloadimages.js
 * module. See Zapatec.PreloadImages class (utils/preloadimages.js) for details.
 *
 * <pre>
 * Arguments object format:
 * {
 *   urls: [object] array of absolute or relative image URLs to preload,
 *   onLoad: [function, optional] onload event handler,
 *   timeout: [number, optional] number of milliseconds to wait for onload
 *    event before forcing it
 * }
 * </pre>
 *
 * @param {object} oArg Arguments object
 */
Zapatec.Transport.preloadImages = function(oArg) {
if (typeof zapatecPreloadImages == 'function') {
zapatecTransport.imagePreloads.push(new zapatecPreloadImages(oArg));
}
};
}
