/** * @fileoverview Zapatec Transport library. Used to fetch data from the server, * parse and serialize XML and JSON data. * *
* Copyright (c) 2004-2006 by Zapatec, Inc. * http://www.zapatec.com * 1700 MLK Way, Berkeley, California, * 94709, U.S.A. * All rights reserved. **/ // $Id: transport.js 4976 2006-11-02 00:10:50Z alex $ if (typeof Zapatec == 'undefined') { /** * Namespace definition. * @constructor */ Zapatec = function() {}; } /** * @constructor */ Zapatec.Transport = function() {}; // 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} arrVersions List of ActiveX object names to test * @return First available ActiveX object name or null * @type string */ Zapatec.Transport.pickActiveXVersion = function(arrVersions) { for (var iVn = 0; iVn < arrVersions.length; iVn++) { try { var objDocument = new ActiveXObject(arrVersions[iVn]); // If it gets to this point, the string worked return arrVersions[iVn]; } catch (objException) {}; } return null; }; /** * Most current version of XMLDOM ActiveX object. * @private */ Zapatec.Transport.XMLDOM = Zapatec.Transport.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 = Zapatec.Transport.pickActiveXVersion([ 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]); // We don't need this any more 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(Zapatec.Transport.XMLHTTP); } catch (objException) {}; } if (typeof XMLHttpRequest != 'undefined') { return new XMLHttpRequest(); } return null; }; /** * Checks if animated GIF is already displayed in the specified div. * *
* Arguments object format: * { * busyContainer: [object or string] element where to put animated GIF, * busyImage: [string, optional] image name * } ** * @private * @param {object} objArgs Arguments object * @return True if image is displayed * @type boolean */ Zapatec.Transport.isBusy = function(objArgs) { // Get container var objContainer = objArgs.busyContainer; if (typeof objContainer == 'string') { objContainer = document.getElementById(objContainer); } if (!objContainer) { return; } // Get image name var strImage = objArgs.busyImage; if (typeof strImage != 'string') { strImage = ''; } strImage = strImage.split('/').pop(); if (!strImage.length) { strImage = 'zpbusy.gif'; } // Check if image is displayed var objFC = objContainer.firstChild; if (objFC) { objFC = objFC.firstChild; if (objFC) { objFC = objFC.firstChild; if (objFC && objFC.tagName && objFC.tagName.toLowerCase() == 'img') { var strSrc = objFC.getAttribute('src'); if (typeof strSrc == 'string' && strSrc.length) { // Get last token strSrc = strSrc.split('/').pop(); if (strSrc == strImage) { return true; } } } } } return false; }; /** * Shows animated GIF in the specified div. * *
* Arguments object format: * { * busyContainer: [object or string] element where to put animated GIF, * busyImage: [string, optional] image name, * busyImageWidth: [number or string, optional] image width, * busyImageHeight: [number or string, optional] image height * } ** * @private * @param {object} objArgs Arguments object */ Zapatec.Transport.showBusy = function(objArgs) { // Make sure image is not displayed yet if (Zapatec.Transport.isBusy(objArgs)) { return; } // Get container var objContainer = objArgs.busyContainer; if (typeof objContainer == 'string') { objContainer = document.getElementById(objContainer); } if (!objContainer) { return; } // Get image name and dimensions var strImage = objArgs.busyImage; var strImageWidth = objArgs.busyImageWidth; var strImageHeight = objArgs.busyImageHeight; if (typeof strImage != 'string' || !strImage.length) { strImage = 'zpbusy.gif'; } else { if (typeof strImageWidth == 'number' || (typeof strImageWidth == 'string' && /\d$/.test(strImageWidth))) { strImageWidth += 'px'; } if (typeof strImageHeight == 'number' || (typeof strImageHeight == 'string' && /\d$/.test(strImageHeight))) { strImageHeight += 'px'; } } if (!strImageWidth) { strImageWidth = '65px'; } if (!strImageHeight) { strImageHeight = '35px'; } // Get path var strPath = ''; // Check if path is specified if (strImage.indexOf('/') < 0) { // Use default path strPath = Zapatec.Transport.getPath('transport.js'); } // Form tag var arrImgTag = []; arrImgTag.push('
* Arguments object format: * { * busyContainer: [object or string] element where to put animated GIF, * busyImage: [string, optional] image name * } ** * @private * @param {object} objArgs Arguments object */ Zapatec.Transport.removeBusy = function(objArgs) { // Get container var objContainer = objArgs.busyContainer; if (typeof objContainer == 'string') { objContainer = document.getElementById(objContainer); } if (!objContainer) { return; } // Make sure image is displayed if (Zapatec.Transport.isBusy(objArgs)) { // Remove image objContainer.removeChild(objContainer.firstChild); } }; /** * Fetches specified URL using new XMLHttpRequest object. * *
* 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] image name, * busyImageWidth: [number or string, optional] image width, * busyImageHeight: [number or string, optional] image height * } ** * @param {object} objArgs Arguments object * @return In synchronous mode XMLHttpRequest object or null. In asynchronous * mode always null. * @type object */ Zapatec.Transport.fetch = function(objArgs) { // Check arguments if (objArgs == null || typeof objArgs != 'object') { return null; } if (!objArgs.url) { return null; } if (!objArgs.method) { objArgs.method = 'GET'; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } if (!objArgs.contentType && objArgs.method.toUpperCase() == 'POST') { objArgs.contentType = 'application/x-www-form-urlencoded'; } if (!objArgs.content) { objArgs.content = null; } if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } // Request URL var objRequest = Zapatec.Transport.createXmlHttpRequest(); if (objRequest == null) { return null; } // Show "Busy" animated GIF Zapatec.Transport.showBusy(objArgs); // IE 6 calls onreadystatechange and then raises exception if local file was // not found. This flag is used to prevent duplicate onError calls. var boolErrorDisplayed = false; // Onready handler var funcOnReady = function () { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process response try { if (objRequest.status == 200 || objRequest.status == 304 || (location.protocol == 'file:' && !objRequest.status)) { // OK or found, but determined unchanged and loaded from cache if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(objRequest); } } else if (!boolErrorDisplayed) { boolErrorDisplayed = true; // 404 Not found, etc. Zapatec.Transport.displayError(objRequest.status, "Error: Can't fetch " + objArgs.url + '.\n' + (objRequest.statusText || ''), objArgs.onError); } } catch (objException) { // Firefox 1.5 raises exception on attempt to access any property of // objRequest if URL was not found if (!boolErrorDisplayed) { boolErrorDisplayed = true; if (objException.name && objException.name == 'NS_ERROR_NOT_AVAILABLE') { Zapatec.Transport.displayError(0, "Error: Can't fetch " + objArgs.url + '.\nFile not found.', objArgs.onError); } else { Zapatec.Transport.displayError(0, "Error: Can't fetch " + objArgs.url + '.\n' + (objException.message || ''), objArgs.onError); } } }; }; try { // Open request if (typeof objArgs.username != 'undefined' && typeof objArgs.password != 'undefined') { objRequest.open(objArgs.method, objArgs.url, objArgs.async, objArgs.username, objArgs.password); } else { objRequest.open(objArgs.method, objArgs.url, objArgs.async); } // Prevent duplicate funcOnReady call in synchronous mode if (objArgs.async) { // Set onreadystatechange handler objRequest.onreadystatechange = function () { if (objRequest.readyState == 4) { // Request complete funcOnReady(); // Prevent memory leak objRequest.onreadystatechange = {}; } }; } // Set content type if needed if (objArgs.contentType) { objRequest.setRequestHeader('Content-Type', objArgs.contentType); } // Send request objRequest.send(objArgs.content); // In synchronous mode the result is ready on the next line if (!objArgs.async) { funcOnReady(); return objRequest; } } catch (objException) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process error if (!boolErrorDisplayed) { boolErrorDisplayed = true; if (objException.name && objException.name == 'NS_ERROR_FILE_NOT_FOUND') { Zapatec.Transport.displayError(0, "Error: Can't fetch " + objArgs.url + '.\nFile not found.', objArgs.onError); } else { Zapatec.Transport.displayError(0, "Error: Can't fetch " + objArgs.url + '.\n' + (objException.message || ''), objArgs.onError); } } }; return null; }; /** * Parses HTML fragment into HTMLElement object. * * @param {string} strHtml HTML fragment * @return Div element which contains parsed HTML fragment * @type object */ Zapatec.Transport.parseHtml = function(strHtml) { // Convert to string strHtml += ''; // Remove leading whitespace characters because Firefox and Opera don't parse // fragment that starts from whitespace character strHtml = strHtml.replace(/^\s+/g, ''); // Create temporaty container var objTempContainer = null; if (document.createElementNS) { // use the XHTML namespace objTempContainer = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); } else { objTempContainer = document.createElement('div'); } // Parse HTML fragment objTempContainer.innerHTML = strHtml; // Return container element return objTempContainer; }; /** * Evaluates javascript in global scope. * *
* Note: Global variables must be declared without "var" keyword. Otherwise * they will be ignored by Safari. *
* * @param {string} strScript Script to evaluate */ Zapatec.Transport.evalGlobalScope = function(strScript) { if (typeof strScript != 'string' || !strScript.match(/\S/)) { return; } if (window.execScript) { // IE window.execScript(strScript, 'javascript'); } else if (window.eval) { // Others window.eval(strScript); /* This should never be reached } else { var funcScript = new Function(strScript); funcScript.call(window); */ } }; /** * Assigns passed HTML fragment to the specified element's innerHTML property * and evaluates in global scope javascripts found in the fragment. * ** Arguments object format: * { * html: [string] HTML fragment, * container: [object or string, optional] element or id of element to put * HTML fragment into * } ** *
* 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. *
* ** Note: Global variables must be declared without "var" keyword. Otherwise * they will be ignored by Safari. *
* * @param {object} objArgs Arguments object */ Zapatec.Transport.setInnerHtml = function(objArgs) { // Check arguments if (!objArgs || typeof objArgs.html != 'string') { return; } var strHtml = objArgs.html; // Get container var objContainer = null; if (typeof objArgs.container == 'string') { objContainer = document.getElementById(objArgs.container); } else if (typeof objArgs.container == 'object') { objContainer = objArgs.container; } // Extract javascripts var arrScripts = []; if (strHtml.match(/<\s*\/\s*script\s*>/i)) { // Split whole string by var arrTokens = strHtml.split(/<\s*\/\s*script\s*>/i); var arrHtml = []; for (var iToken = arrTokens.length - 1; iToken >= 0; iToken--) { var strToken = arrTokens[iToken]; if (strToken.match(/\S/)) { // Search ' + strToken; } else { // If this is last token, assume it is HTML fragment arrHtml.unshift(strToken); } } else { // Empty token arrHtml.unshift(strToken); } } // Get HTML part strHtml = arrHtml.join(''); } // Set inner HTML if (objContainer) { // Opera hack if (window.opera) { // Without this line Opera will not form correct DOM structure if HTML // fragment contains forms objContainer.innerHTML = ''; } objContainer.innerHTML = strHtml; } // Evaluate javascripts for (var iScript = 0; iScript < arrScripts.length; iScript++) { if (arrScripts[iScript][1].length) { // Evaluate in global scope Zapatec.Transport.evalGlobalScope(arrScripts[iScript][1]); } // Load external script var strAttrs = arrScripts[iScript][0]; strAttrs = strAttrs.replace(/\s+/g, ' ').replace(/^\s/, '') .replace(/\s$/, '').replace(/ = /g, '='); if (strAttrs.indexOf('src=') >= 0) { // Get container var objContainer = document.body; if (!objContainer) { objContainer = document.getElementsByTagName('head')[0]; if (!objContainer) { objContainer = document; } } // Get attributes var arrAttrs = strAttrs.split(' '); // Load script var objScript = Zapatec.Utils.createElement('script'); for (var iAttr = 0; iAttr < arrAttrs.length; iAttr++) { var arrAttr = arrAttrs[iAttr].split('='); if (arrAttr.length > 1) { objScript.setAttribute(arrAttr[0], arrAttr[1].match(/^[\s|"|']*([\s|\S]*[^'|"])[\s|"|']*$/)[1]); } else { objScript.setAttribute(arrAttr[0], arrAttr[0]); } } // It's important for Safari to assign attributes before appending objContainer.appendChild(objScript); } } }; /** * Fetches and parses XML document from the specified URL. * ** 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. or * . * * 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] image name, * busyImageWidth: [number or string, optional] image width, * busyImageHeight: [number or string, optional] image height * } ** * @param {object} objArgs Arguments object * @return In synchronous mode XMLDocument object or null. In asynchronous mode * always null. * @type object */ Zapatec.Transport.fetchXmlDoc = function(objArgs) { // Check arguments if (objArgs == null || typeof objArgs != 'object') { return null; } if (!objArgs.url) { return null; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } // Try more efficient methods first if (!objArgs.method && typeof objArgs.username == 'undefined' && typeof objArgs.password == 'undefined') { if (document.implementation && document.implementation.createDocument) { // Mozilla var objDocument = null; if (!objArgs.reliable) { objArgs.reliable = false; } // Form argument for fetch var objFetchArgs = {}; for (var strKey in objArgs) { objFetchArgs[strKey] = objArgs[strKey]; } // Prevent duplicate parseXml call in synchronous mode if (objArgs.async) { objFetchArgs.onLoad = function(objRequest) { // Prevent onload being called more than once objFetchArgs.onLoad = null; // Parse xml response string var parser = new DOMParser(); objDocument = parser.parseFromString(objRequest.responseText, "text/xml"); // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process response Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); }; } else { objFetchArgs.onLoad = null; } // Fetch URL var objRequest = Zapatec.Transport.fetch(objFetchArgs); // In synchronous mode the result is ready on the next line if (!objArgs.async && objRequest) { // Parse xml response string var parser = new DOMParser(); objDocument = parser.parseFromString(objRequest.responseText, "text/xml"); // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process response Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); return objDocument; } } if (typeof ActiveXObject != 'undefined') { // IE // Show "Busy" animated GIF Zapatec.Transport.showBusy(objArgs); // Load document try { var objDocument = new ActiveXObject(Zapatec.Transport.XMLDOM); objDocument.async = objArgs.async; // Prevent duplicate onXmlDocLoad call in synchronous mode if (objArgs.async) { objDocument.onreadystatechange = function () { if (objDocument.readyState == 4) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process response Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); // Prevent memory leak objDocument.onreadystatechange = {}; } }; } objDocument.load(objArgs.url); // In synchronous mode the result is ready on the next line if (!objArgs.async) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); // Process response Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); return objDocument; } return null; } catch (objException) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(objArgs); }; } } // Try XMLHttpRequest // Form argument for fetch var objFetchArgs = {}; for (var strKey in objArgs) { objFetchArgs[strKey] = objArgs[strKey]; } // Prevent duplicate parseXml call in synchronous mode if (objArgs.async) { objFetchArgs.onLoad = function(objRequest) { Zapatec.Transport.parseXml({ strXml: objRequest.responseText, onLoad: objArgs.onLoad, onError: objArgs.onError }); }; } else { objFetchArgs.onLoad = null; } // Fetch URL var objRequest = Zapatec.Transport.fetch(objFetchArgs); // In synchronous mode the result is ready on the next line if (!objArgs.async && objRequest) { return Zapatec.Transport.parseXml({ strXml: objRequest.responseText, onLoad: objArgs.onLoad, onError: objArgs.onError }); } return null; }; /** * Parses XML string into XMLDocument object. * *
* 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. or * . * * 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) * } ** * @param {object} objArgs Arguments object * @return XMLDocument object or null * @type object */ Zapatec.Transport.parseXml = function(objArgs) { if (objArgs == null || typeof objArgs != 'object') { return null; } if (!objArgs.strXml) { return null; } if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } if (window.DOMParser) { // Mozilla try { var objDocument = (new DOMParser()).parseFromString(objArgs.strXml, 'text/xml'); Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); return objDocument; } catch (objException) { Zapatec.Transport.displayError(0, "Error: Can't parse.\n" + 'String does not appear to be a valid XML fragment.', objArgs.onError); }; return null; } if (typeof ActiveXObject != 'undefined') { // IE try { var objDocument = new ActiveXObject(Zapatec.Transport.XMLDOM); objDocument.loadXML(objArgs.strXml); Zapatec.Transport.onXmlDocLoad(objDocument, objArgs.onLoad, objArgs.onError); return objDocument; } catch (objException) {}; } return null; }; /** * Checks if there were errors during XML document fetching and parsing and * calls onLoad or onError callback function correspondingly. * * @private * @param {object} objDocument XMLDocument object * @param {function} onLoad Callback function provided by user * @param {function} onError Callback function provided by user */ Zapatec.Transport.onXmlDocLoad = function(objDocument, onLoad, onError) { var strError = null; if (objDocument.parseError) { // Parsing error in IE strError = objDocument.parseError.reason; if (objDocument.parseError.srcText) { strError += 'Location: ' + objDocument.parseError.url + '\nLine number ' + objDocument.parseError.line + ', column ' + objDocument.parseError.linepos + ':\n' + objDocument.parseError.srcText + '\n'; } } else if (objDocument.documentElement && objDocument.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: //
* 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] image name, * busyImageWidth: [number or string, optional] image width, * busyImageHeight: [number or string, optional] image height * } ** * @param {object} objArgs Arguments object * @return In synchronous mode JSON object or null. In asynchronous mode always * null. * @type object */ Zapatec.Transport.fetchJsonObj = function(objArgs) { // Check arguments if (objArgs == null || typeof objArgs != 'object') { return null; } if (!objArgs.url) { return null; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } if (!objArgs.reliable) { objArgs.reliable = false; } // Form argument for fetch var objFetchArgs = {}; for (var strKey in objArgs) { objFetchArgs[strKey] = objArgs[strKey]; } // Prevent duplicate parseXml call in synchronous mode if (objArgs.async) { objFetchArgs.onLoad = function(objRequest) { Zapatec.Transport.parseJson({ strJson: objRequest.responseText, reliable: objArgs.reliable, onLoad: objArgs.onLoad, onError: objArgs.onError }); }; } else { objFetchArgs.onLoad = null; } // Fetch URL var objRequest = Zapatec.Transport.fetch(objFetchArgs); // In synchronous mode the result is ready on the next line if (!objArgs.async && objRequest) { return Zapatec.Transport.parseJson({ strJson: objRequest.responseText, reliable: objArgs.reliable, onLoad: objArgs.onLoad, onError: objArgs.onError }); } return null; }; /** * Parses JSON string into object. * *
* 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) * } ** * @param {object} objArgs Arguments object * @return JSON object or null * @type object */ Zapatec.Transport.parseJson = function(objArgs) { if (objArgs == null || typeof objArgs != 'object') { return null; } if (!objArgs.reliable) { objArgs.reliable = false; } if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } var objJson = null; try { if (objArgs.reliable) { if (objArgs.strJson) { objJson = eval('(' + objArgs.strJson + ')'); } } else { objJson = Zapatec.Transport.parseJsonStr(objArgs.strJson); } } catch (objException) { var strError = "Error: Can't parse.\nString doesn't appear to be a valid JSON fragment: "; strError += objException.message; if (typeof objException.text != 'undefined' && objException.text.length) { strError += '\n' + objException.text; } strError += '\n' + objArgs.strJson; Zapatec.Transport.displayError(0, strError, objArgs.onError); }; if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(objJson); } return objJson; }; /** * Parses JSON string into object. * *
* Was taken with changes from http://json.org/json.js. * * Throws exception if parsing error occurs. * * JSON format is described at http://json.org/js.html. ** * @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. * * Was taken with changes from http://json.org/json.js. * * @param {object} v JSON object * @return JSON string * @type string */ Zapatec.Transport.serializeJsonObj = function(v) { var a = []; /* Emit a string. */ function e(s) { a[a.length] = s; } /* Convert a value. */ function g(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) { v = x[i]; if (typeof v != 'undefined' && typeof v != 'function') { if (l < a.length) { e(','); } g(v); } } 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' && typeof v != 'function') { if (l < a.length) { e(','); } g(i); e(':'); g(v); } } return e('}'); } } 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; default: e('null'); return; } } g(v); return a.join(''); }; /** * Displays error message. * *
* Calls onError callback function provided by user. If there is no onError * callback function, displays alert with human readable error description. * onError callback function receives following object: * { * errorCode: error code [number], * errorDescription: human readable error description [string] * } ** * @private * @param {number} iErrCode Error code * @param {string} strError Human readable error description * @param {function} onError Callback function provided by user */ Zapatec.Transport.displayError = function(iErrCode, strError, onError) { if (typeof onError == 'function') { onError({ errorCode: iErrCode, errorDescription: strError }); } else { alert(strError); } }; /** * Translates a URL to the URL relative to the specified or to absolute URL. * *
* Arguments object format: * { * url: absolute or relative URL to translate [string] (if absolute, will be * returned as is), * relativeTo: "url" will be translated to the URL relative to this absolute * or relative URL [string] (optional, current page URL by default) * } ** * @param {object} objArgs Arguments object * @return Translated URL * @type string */ Zapatec.Transport.translateUrl = function(objArgs) { if (!objArgs || !objArgs.url) { return null; } // Cut arguments part from url var arrFullUrl = objArgs.url.split('?', 2); var strUrl = arrFullUrl[0]; // Check url if (strUrl.charAt(0) == '/' || strUrl.indexOf(':') >= 0) { // Return absolute URL as is return objArgs.url; } // Get relativeTo var strRelativeTo; if (typeof objArgs.relativeTo != 'string') { // By default relative to current page URL strRelativeTo = document.location.toString().split('?', 2)[0]; } else { // Remove arguments from relativeTo strRelativeTo = objArgs.relativeTo.split('?', 2)[0]; // Check relativeTo if (strRelativeTo.indexOf('/') < 0) { // Relative to current page URL strRelativeTo = document.location.toString().split('?', 2)[0]; } else if (strRelativeTo.charAt(0) != '/' && strRelativeTo.indexOf(':') < 0) { // Transform relativeTo to absolute URL to be able to translate URLs // starting from ../ strRelativeTo = Zapatec.Transport.translateUrl({ url: strRelativeTo }); } } // Split URLs var arrUrl = strUrl.split('/'); var arrRelativeTo = strRelativeTo.split('/'); // Remove file name arrRelativeTo.pop(); // Form new URL for (var iToken = 0; iToken < arrUrl.length; iToken++) { var strToken = arrUrl[iToken]; if (strToken == '..') { arrRelativeTo.pop(); } else if (strToken != '.') { arrRelativeTo.push(strToken); } } arrFullUrl[0] = arrRelativeTo.join('/'); // Restore arguments part return arrFullUrl.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. * *
* 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 * } ** * @private * @param {object} objArgs Arguments object * @return Returned object * @type object */ Zapatec.Transport.setupEvents = function(objArgs) { // Check arguments if (!objArgs) { 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 (objArgs.force || !Zapatec.EventDriven || !objArgs.url) { return { onLoad: objArgs.onLoad, onError: objArgs.onError }; } var strUrl = objArgs.url; // Add onLoad listener if (typeof objArgs.onLoad == 'function') { Zapatec.EventDriven.addEventListener('zpTransportOnLoad' + strUrl, objArgs.onLoad); } // Add onError listener if (typeof objArgs.onError == 'function') { Zapatec.EventDriven.addEventListener('zpTransportOnError' + strUrl, objArgs.onError); } // Check if it is already loading if (Zapatec.Transport.loading[strUrl]) { return { loading: true }; } else { // Flag Zapatec.Transport.loading[strUrl] = true; // Replace original callbacks return { onLoad: new Function("\ Zapatec.EventDriven.fireEvent('zpTransportOnLoad" + strUrl + "');\ Zapatec.EventDriven.removeEvent('zpTransportOnLoad" + strUrl + "');\ Zapatec.EventDriven.removeEvent('zpTransportOnError" + strUrl + "');\ Zapatec.Transport.loading['" + strUrl + "'] = false;\ "), onError: new Function('objError', "\ Zapatec.EventDriven.fireEvent('zpTransportOnError" + strUrl + "',\ objError);\ Zapatec.EventDriven.removeEvent('zpTransportOnLoad" + strUrl + "');\ Zapatec.EventDriven.removeEvent('zpTransportOnError" + strUrl + "');\ Zapatec.Transport.loading['" + strUrl + "'] = 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} strUrl Absolute or relative URL of JS file * @param {string} strAbsoluteUrl Optional. Absolute URL of JS file * @return Loaded or not * @type boolean */ Zapatec.Transport.isLoadedJS = function(strUrl, strAbsoluteUrl) { // Get absolute URL of the JS file if (typeof strAbsoluteUrl == 'undefined') { strAbsoluteUrl = Zapatec.Transport.translateUrl({url: strUrl}); } // Check in the list of loaded if (Zapatec.Transport.loadedJS[strAbsoluteUrl]) { return true; } // Try to find script tag var arrScripts = document.getElementsByTagName('script'); for (var iScript = 0; iScript < arrScripts.length; iScript++) { var strSrc = arrScripts[iScript].getAttribute('src') || ''; if (strSrc == strUrl) { // Add this URL to the list of loaded Zapatec.Transport.loadedJS[strAbsoluteUrl] = 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} strScriptFileName 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(strScriptFileName) { // Get all script elements var arrScripts = document.getElementsByTagName('script'); // Find the script in the list for (var iScript = arrScripts.length - 1; iScript >= 0; iScript--) { var strSrc = arrScripts[iScript].getAttribute('src') || ''; var arrTokens = strSrc.split('/'); // Remove last token var strLastToken = arrTokens.pop(); if (strLastToken == strScriptFileName) { return arrTokens.length ? arrTokens.join('/') + '/' : ''; } } // Search in loaded JS files for (var strSrc in Zapatec.Transport.loadedJS) { var arrTokens = strSrc.split('/'); // Remove last token var strLastToken = arrTokens.pop(); if (strLastToken == strScriptFileName) { return arrTokens.length ? arrTokens.join('/') + '/' : ''; } } // Not found return ''; }; /** * Writes script tag to the document. Checks if specified JS file is already * loaded unless boolForce argument is true. * *
* Note: This function must be invoked during page load because it uses * document.write method. * * If special Zapatec.doNotInclude flag is set, this function does nothing. ** * @param {string} strSrc Src attribute value of the script element * @param {string} strId Optional. Id of the script element * @param {boolean} boolForce Optional. Force reload if it is already loaded */ Zapatec.Transport.include = function(strSrc, strId, boolForce) { // Check flag if (Zapatec.doNotInclude) { return; } // Get absolute URL of the JS file var strAbsoluteUrl = Zapatec.Transport.translateUrl({url: strSrc}); // Check if it is already loaded if (!boolForce && Zapatec.Transport.isLoadedJS(strSrc, strAbsoluteUrl)) { return; } // Include file document.write(''); // Add this URL to the list of loaded Zapatec.Transport.loadedJS[strAbsoluteUrl] = true; }; /** * Shortcut. The same as Zapatec.Transport#include. */ Zapatec.include = Zapatec.Transport.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. * *
* If special Zapatec.doNotInclude flag is set, this function does nothing. ** * @param {string} strSrc Src attribute value of the script element * @param {string} strId Optional. Id of the script element */ Zapatec.Transport.includeJS = function(strSrc, strId) { // Check flag if (Zapatec.doNotInclude) { return; } // Include file var objContainer = document.body; if (!objContainer) { objContainer = document.getElementsByTagName('head')[0]; if (!objContainer) { objContainer = document; } } var objScript = document.createElement('script'); objScript.type = 'text/javascript'; objScript.src = strSrc; if (typeof strId == 'string') { objScript.id = strId; } // This is important for Safari to assign attributes before appending objContainer.appendChild(objScript); }; /** * Fetches JS file using fetch and evaluates it in local scope. * *
* 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. * * If special Zapatec.doNotInclude flag is set, this function just calls onLoad * callback function. ** * @param {object} objArgs Arguments object */ Zapatec.Transport.loadJS = function(objArgs) { // Check arguments if (!(objArgs instanceof Object)) { return; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } // Get URL of JS file var strUrl = null; if (objArgs.url) { strUrl = objArgs.url; } else if (objArgs.module) { var strPath = ''; if (typeof objArgs.path != 'undefined') { strPath = objArgs.path; } else if (typeof Zapatec.zapatecPath != 'undefined') { strPath = Zapatec.zapatecPath; } strUrl = strPath + objArgs.module + '.js'; } else { return; } // Get absolute URL of the JS file var strAbsUrl = Zapatec.Transport.translateUrl({url: strUrl}); // Check arguments if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } // Check if it is already loaded if (Zapatec.doNotInclude || (!objArgs.force && Zapatec.Transport.isLoadedJS(strUrl, strAbsUrl))) { // onLoad callback if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(); } return; } // Setup onLoad and onError events var objHandlers = Zapatec.Transport.setupEvents({ url: strAbsUrl, force: objArgs.force, onLoad: objArgs.onLoad, onError: objArgs.onError }); // Don't need to continue if this url is already loading by another process if (objHandlers.loading) { return; } // Load JS file Zapatec.Transport.fetch({ url: strUrl, async: objArgs.async, onLoad: function(objRequest) { // Can be loaded in two processes simultaneously if (objArgs.force || !Zapatec.Transport.loadedJS[strAbsUrl]) { var arrTokens = strUrl.split('/'); // Remove last token var strLastToken = arrTokens.pop(); // Store path to current module Zapatec.lastLoadedModule = arrTokens.join('/') + '/'; // Evaluate code in global scope Zapatec.Transport.evalGlobalScope(objRequest.responseText); // clear path to last loaded module Zapatec.lastLoadedModule = null; // Add this URL to the list of loaded Zapatec.Transport.loadedJS[strAbsUrl] = true; } // onLoad callback if (typeof objHandlers.onLoad == 'function') { objHandlers.onLoad(); } }, onError: objHandlers.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} strHref Href attribute value of the link element */ Zapatec.Transport.includeCSS = function(strHref) { // May appear only inside head var objContainer = document.getElementsByTagName('head')[0]; if (!objContainer) { return; } var objLink = document.createElement('link'); objLink.setAttribute('rel', 'stylesheet'); objLink.setAttribute('type', 'text/css'); objLink.setAttribute('href', strHref); objContainer.appendChild(objLink); }; /** * 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. * *
* 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. ** * @param {object} objArgs Arguments object */ Zapatec.Transport.loadCss = function(objArgs) { if (Zapatec.StyleSheet) { Zapatec.Transport.loadCssWithStyleSheet(objArgs); } else { // Load Zapatec.StyleSheet class Zapatec.Transport.loadJS({ module: 'stylesheet', async: objArgs.async, onLoad: function() { Zapatec.Transport.loadCssWithStyleSheet(objArgs); } }); } }; /** * Fetches style sheet using fetch and loads it into the document. * Zapatec.StyleSheet must exist. * * @private * @param {object} objArgs Arguments object see * {@link Zapatec.Transport#loadCss} */ Zapatec.Transport.loadCssWithStyleSheet = function(objArgs) { // Check arguments if (!(objArgs instanceof Object)) { return; } if (!objArgs.url) { return; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } // Get absolute URL of the CSS file var strAbsUrl = Zapatec.Transport.translateUrl({url: objArgs.url}); // Check if it is already loaded if (!objArgs.force) { if (Zapatec.Transport.loadedCss[strAbsUrl]) { // onLoad callback if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(); } return; } var arrLinks = document.getElementsByTagName('link'); for (var iLnk = 0; iLnk < arrLinks.length; iLnk++) { var strHref = arrLinks[iLnk].getAttribute('href') || ''; // Make it absolute strHref = Zapatec.Transport.translateUrl({url: strHref}); if (strHref == strAbsUrl) { // Add this url to the list of loaded Zapatec.Transport.loadedCss[strAbsUrl] = true; // onLoad callback if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(); } return; } } } // Setup onLoad and onError events var objHandlers = Zapatec.Transport.setupEvents({ url: strAbsUrl, force: objArgs.force, onLoad: objArgs.onLoad, onError: objArgs.onError }); // Don't need to continue if this url is already loading by another process if (objHandlers.loading) { return; } // Load CSS file Zapatec.Transport.fetch({ url: objArgs.url, async: objArgs.async, onLoad: function(objRequest) { // Parse CSS file. // Find URLs and translate them to absolute. // Find @import rules and load corresponding CSS files. var strCss = objRequest.responseText; var arrResultCss = []; // Will hold image URLs to preload var arrImgUrls = []; // Will hold CSS URLs to load var arrCssUrls = []; // Move first cursor to the beginning of the string var iPos = 0; // Move second cursor to the pattern var iNextPos = strCss.indexOf('url(', iPos); while (iNextPos >= 0) { // Move first cursor to the URL iNextPos += 4; // Check if this is @import rule var strToken = strCss.substring(iPos, iNextPos); var boolIsImport = /@import\s+url\($/.test(strToken); // Add part of the string before URL arrResultCss.push(strToken); // Move second cursor to the new location to start the search from iPos = iNextPos; // Search the end of URL iNextPos = strCss.indexOf(')', iPos); if (iNextPos >= 0) { // Remove quotes var strImgUrl = strCss.substring(iPos, iNextPos); strImgUrl = strImgUrl.replace(/['"]/g, ''); // Translate image URL relative to CSS file URL strImgUrl = Zapatec.Transport.translateUrl({ url: strImgUrl, relativeTo: objArgs.url }); // Convert to absolute URL strImgUrl = Zapatec.Transport.translateUrl({ url: strImgUrl }); // Add translated URL arrResultCss.push(strImgUrl); // Add URL to the list if (boolIsImport) { // Add CSS URL to load list arrCssUrls.push(strImgUrl); } else { // Add image URL to preload list arrImgUrls.push(strImgUrl); } // Move second cursor to the new location to start the search from iPos = iNextPos; // Search next pattern iNextPos = strCss.indexOf('url(', iPos); } } // Add the rest of string arrResultCss.push(strCss.substr(iPos)); // Get translated CSS text strCss = arrResultCss.join(''); // Load CSS files Zapatec.Transport.loadCssList({ urls: arrCssUrls, async: objArgs.async, onLoad: function() { // Add style sheet rules into the page var objStyleSheet = new Zapatec.StyleSheet(); objStyleSheet.addParse(strCss); // Fire event if (typeof objHandlers.onLoad == 'function') { objHandlers.onLoad(); } } }); // Add this URL to the list of loaded Zapatec.Transport.loadedCss[strAbsUrl] = true; // Preload images Zapatec.Transport.preloadImages({ urls: arrImgUrls, timeout: 60000 // 1 minute }); }, onError: objHandlers.onError }); }; /** * Loads several CSS files one by one it into the document. * *
* 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. ** * @param {object} objArgs Arguments object */ Zapatec.Transport.loadCssList = function(objArgs) { // Check arguments if (!(objArgs instanceof Object)) { return; } if (typeof objArgs.async == 'undefined') { objArgs.async = true; } if (!objArgs.onLoad) { objArgs.onLoad = null; } if (!objArgs.onError) { objArgs.onError = null; } if (!objArgs.urls || !objArgs.urls.length) { // onLoad callback if (typeof objArgs.onLoad == 'function') { objArgs.onLoad(); } return; } // Get first URL in the array var strUrl = objArgs.urls.shift(); // CSS file onLoad handler var funcOnLoad = function() { // Load the rest of URLs Zapatec.Transport.loadCssList({ urls: objArgs.urls, async: objArgs.async, force: objArgs.force, onLoad: objArgs.onLoad, onError: objArgs.onError }); }; // Load CSS file Zapatec.Transport.loadCss({ url: strUrl, async: objArgs.async, force: objArgs.force, onLoad: funcOnLoad, onError: function(objError) { Zapatec.Transport.displayError(objError.errorCode, objError.errorDescription, objArgs.onError); funcOnLoad(); } }); }; /** * Holds image preloads. * @private */ Zapatec.Transport.imagePreloads = []; /** * Preloads one or several images at once. See Zapatec.PreloadImages class * (utils/preloadimages.js) for details. * *
* 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 * } ** * @param {object} objArgs Arguments object */ Zapatec.Transport.preloadImages = function(objArgs) { if (Zapatec.PreloadImages) { Zapatec.Transport.imagePreloads.push(new Zapatec.PreloadImages(objArgs)); } else { // Load preloadimages.js module Zapatec.Transport.loadJS({ module: 'preloadimages', onLoad: function() { Zapatec.Transport.imagePreloads.push( new Zapatec.PreloadImages(objArgs)); } }); } };