[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
05122024
/
htdocs
/
simpeg
/
administrator
/
utils
/
[
Home
]
File: transport.js
/** * @fileoverview Zapatec Transport library. Used to fetch data from the server, * parse and serialize XML and JSON data. * * <pre> * Copyright (c) 2004-2007 by Zapatec, Inc. * http://www.zapatec.com * 1700 MLK Way, Berkeley, California, * 94709, U.S.A. * All rights reserved. * </pre> */ // $Id: transport.js 7699 2007-08-08 11:33:54Z alex $ if (typeof Zapatec == 'undefined') { /** * @ignore Namespace definition. */ 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} 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 = 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 (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 (Zapatec.Transport.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 = Zapatec.Transport.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 = Zapatec.Utils.createElement('div'); oBusyContr.style.position = 'relative'; oBusyContr.style.zIndex = 2147483583; var oBusy = Zapatec.Utils.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 (Zapatec.Transport.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 In synchronous mode XMLHttpRequest object or null. In asynchronous * mode always null. * @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 = Zapatec.Transport.createXmlHttpRequest(); if (oRequest == null) { return null; } // Show "Busy" animated GIF Zapatec.Transport.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 Zapatec.Transport.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. Zapatec.Transport.displayError(oRequest.status, "Error: Can't fetch " + oArg.url + '.\n' + (oRequest.statusText || ''), oArg.onError); } } catch (oExpn) { // Firefox 1.5 raises exception on attempt to access any property of // oRequest if URL was not found if (!bErrorDisplayed) { bErrorDisplayed = true; if (oExpn.name && oExpn.name == 'NS_ERROR_NOT_AVAILABLE') { Zapatec.Transport.displayError(0, "Error: Can't fetch " + oArg.url + '.\nFile not found.', oArg.onError); } else { Zapatec.Transport.displayError(0, "Error: Can't fetch " + oArg.url + '.\n' + (oExpn.message || ''), oArg.onError); } } }; }; 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 the result is ready on the next line if (!oArg.async) { funcOnReady(); return oRequest; } } catch (oExpn) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(oArg); // Process error if (!bErrorDisplayed) { bErrorDisplayed = true; if (oExpn.name && oExpn.name == 'NS_ERROR_FILE_NOT_FOUND') { Zapatec.Transport.displayError(0, "Error: Can't fetch " + oArg.url + '.\nFile not found.', oArg.onError); } else { Zapatec.Transport.displayError(0, "Error: Can't fetch " + oArg.url + '.\n' + (oExpn.message || ''), oArg.onError); } } }; return null; }; /** * Parses HTML fragment into HTMLElement object. * * @param {string} sHtml HTML fragment * @return Div element which contains parsed HTML fragment * @type object */ Zapatec.Transport.parseHtml = function(sHtml) { // 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; // Return 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 Zapatec.Transport.evalGlobalScope(aScripts[iScript][1]); } // Load external script var sAttrs = aScripts[iScript][0]; sAttrs = sAttrs.replace(/\s+/g, ' ').replace(/^\s/, '') .replace(/\s$/, '').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 = Zapatec.Utils.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 In synchronous mode XMLDocument object or null. In asynchronous mode * always null. * @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 = false; } // Form argument for fetch var oFetchArg = {}; for (var sKey in oArg) { oFetchArg[sKey] = oArg[sKey]; } // Prevent duplicate parseXml call in synchronous mode 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 Zapatec.Transport.removeBusy(oArg); // Process response Zapatec.Transport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError); }; } else { oFetchArg.onLoad = null; } // Fetch URL var oRequest = Zapatec.Transport.fetch(oFetchArg); // In synchronous mode the result is ready on the next line if (!oArg.async && oRequest) { // Parse xml response string var parser = new DOMParser(); oDoc = parser.parseFromString(oRequest.responseText, "text/xml"); // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(oArg); // Process response Zapatec.Transport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError); return oDoc; } return null; } if (typeof ActiveXObject != 'undefined') { // IE // Show "Busy" animated GIF Zapatec.Transport.showBusy(oArg); // Load document try { var oDoc = new ActiveXObject(Zapatec.Transport.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 Zapatec.Transport.removeBusy(oArg); // Process response Zapatec.Transport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError); // Prevent memory leak oDoc.onreadystatechange = {}; } }; } oDoc.load(oArg.url); // In synchronous mode the result is ready on the next line if (!oArg.async) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(oArg); // Process response Zapatec.Transport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError); return oDoc; } return null; } catch (oExpn) { // Remove "Busy" animated GIF Zapatec.Transport.removeBusy(oArg); }; } } // Try XMLHttpRequest // Form argument for fetch var oFetchArg = {}; for (var sKey in oArg) { oFetchArg[sKey] = oArg[sKey]; } // Prevent duplicate parseXml call in synchronous mode if (oArg.async) { oFetchArg.onLoad = function(oRequest) { Zapatec.Transport.parseXml({ strXml: oRequest.responseText, onLoad: oArg.onLoad, onError: oArg.onError }); }; } else { oFetchArg.onLoad = null; } // Fetch URL var oRequest = Zapatec.Transport.fetch(oFetchArg); // In synchronous mode the result is ready on the next line if (!oArg.async && oRequest) { return Zapatec.Transport.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'); Zapatec.Transport.onXmlDocLoad(oDoc, oArg.onLoad, oArg.onError); return oDoc; } catch (oExpn) { Zapatec.Transport.displayError(0, "Error: Can't parse.\n" + 'String does not appear to be a valid XML fragment.', oArg.onError); }; return null; } if (typeof ActiveXObject != 'undefined') { // IE try { var oDoc = new ActiveXObject(Zapatec.Transport.XMLDOM); oDoc.loadXML(oArg.strXml); Zapatec.Transport.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 Zapatec.Transport.displayError(0, "Error: Can't parse.\n" + 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 In synchronous mode JSON object or null. In asynchronous mode always * null. * @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 = false; } // Form argument for fetch var oFetchArg = {}; for (var sKey in oArg) { oFetchArg[sKey] = oArg[sKey]; } // Prevent duplicate parseXml call in synchronous mode if (oArg.async) { oFetchArg.onLoad = function(oRequest) { Zapatec.Transport.parseJson({ strJson: oRequest.responseText, reliable: oArg.reliable, onLoad: oArg.onLoad, onError: oArg.onError }); }; } else { oFetchArg.onLoad = null; } // Fetch URL var oRequest = Zapatec.Transport.fetch(oFetchArg); // In synchronous mode the result is ready on the next line if (!oArg.async && oRequest) { return Zapatec.Transport.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 = false; } if (!oArg.onLoad) { oArg.onLoad = null; } if (!oArg.onError) { oArg.onError = null; } var oJson = null; try { if (oArg.reliable) { if (oArg.strJson) { oJson = eval('(' + oArg.strJson + ')'); } } else { oJson = Zapatec.Transport.parseJsonStr(oArg.strJson); } } catch (oExpn) { var sError = "Error: Can't parse.\nString doesn't appear to be a valid JSON fragment: "; sError += oExpn.message; if (typeof oExpn.text != 'undefined' && oExpn.text.length) { sError += '\n' + oExpn.text; } sError += '\n' + oArg.strJson; Zapatec.Transport.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> * 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. * </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. * * 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. * * <pre> * 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 [number]: error code, * errorDescription [string]: human readable error description * } * </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 { alert(sError); } }; /** * Translates a URL to the URL relative to the specified or to absolute URL. * * <pre> * Arguments object format: * { * url [string]: absolute or relative URL to translate; if absolute, will be * returned as is, * 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) { // Return absolute URL as is return oArg.url; } var oLocation = document.location; var sPort = oLocation.port; if (sPort) { sPort = ':' + sPort; } if (sUrl[0] == '/') { // Add hostname and return absolute URL as is return [oLocation.protocol, '//', oLocation.hostname, sPort, sUrl].join(''); } // 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 = Zapatec.Transport.translateUrl({ url: sRelativeTo }); } } // Remove # sRelativeTo = sRelativeTo.split('#')[0]; // Split URLs var aUrl = sUrl.split('/'); var aRelativeTo = sRelativeTo.split('/'); // Remove file name aRelativeTo.pop(); // Form new URL for (var iToken = 0; iToken < aUrl.length; iToken++) { var sToken = aUrl[iToken]; if (sToken == '..') { aRelativeTo.pop(); } else if (sToken != '.') { aRelativeTo.push(sToken); } } aFullUrl[0] = aRelativeTo.join('/'); // Restore arguments part return aFullUrl.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 (Zapatec.Transport.loading[sUrl]) { return { loading: true }; } else { // Flag Zapatec.Transport.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 + "');Zapatec.Transport.loading['" + sUrl + "'] = false;"), onError: new Function('oError', "Zapatec.EventDriven.fireEvent('zpTransportOnError" + sUrl + "',oError);Zapatec.EventDriven.removeEvent('zpTransportOnLoad" + sUrl + "');Zapatec.EventDriven.removeEvent('zpTransportOnError" + sUrl + "');Zapatec.Transport.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 = Zapatec.Transport.translateUrl({url: sUrl}); } // Check in the list of loaded if (Zapatec.Transport.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 Zapatec.Transport.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 Zapatec.Transport.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 Zapatec.doNotInclude flag is set, this function does nothing. * </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 (Zapatec.doNotInclude) { return; } // Get absolute URL of the JS file var sAbsUrl = Zapatec.Transport.translateUrl({url: sSrc}); // Check if it is already loaded if (!bForce && Zapatec.Transport.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 Zapatec.Transport.loadedJS[sAbsUrl] = true; }; /** * Shortcut. The same as {@link 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. * * @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 Zapatec.doNotInclude 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 = Zapatec.Transport.translateUrl({url: sUrl}); // Check arguments if (!oArg.onLoad) { oArg.onLoad = null; } if (!oArg.onError) { oArg.onError = null; } // Check if it is already loaded if (Zapatec.doNotInclude || (!oArg.force && Zapatec.Transport.isLoadedJS(sUrl, sAbsUrl))) { // onLoad callback if (typeof oArg.onLoad == 'function') { oArg.onLoad(); } return; } // Setup onLoad and onError events var oHandlers = Zapatec.Transport.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 Zapatec.Transport.fetch({ url: sUrl, async: oArg.async, onLoad: function(oRequest) { // Can be loaded in two processes simultaneously if (oArg.force || !Zapatec.Transport.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 Zapatec.Transport.evalGlobalScope(oRequest.responseText); // clear path to last loaded module Zapatec.lastLoadedModule = null; // Add this URL to the list of loaded Zapatec.Transport.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 = Zapatec.Transport.translateUrl({url: oArg.url}); // Check if it is already loaded if (!oArg.force) { if (Zapatec.Transport.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 = Zapatec.Transport.translateUrl({url: sHref}); if (sHref == sAbsUrl) { // Add this url to the list of loaded Zapatec.Transport.loadedCss[sAbsUrl] = true; // onLoad callback if (typeof oArg.onLoad == 'function') { oArg.onLoad(); } return; } } } // Setup onLoad and onError events var oHandlers = Zapatec.Transport.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 Zapatec.Transport.fetch({ url: oArg.url, 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 = Zapatec.Transport.translateUrl({ url: sImgUrl, relativeTo: oArg.url }); // Convert to absolute URL sImgUrl = Zapatec.Transport.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 Zapatec.Transport.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 Zapatec.Transport.loadedCss[sAbsUrl] = true; // Preload images Zapatec.Transport.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 Zapatec.Transport.loadCssList({ urls: oArg.urls, async: oArg.async, force: oArg.force, onLoad: oArg.onLoad, onError: oArg.onError }); }; // Load CSS file Zapatec.Transport.loadCss({ url: sUrl, async: oArg.async, force: oArg.force, onLoad: funcOnLoad, onError: function(oError) { Zapatec.Transport.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) { Zapatec.Transport.imagePreloads.push(new Zapatec.PreloadImages(oArg)); };