//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Date: 13 Feb 2009 // // SFAJAX (StoreFinder Ajax) javascript API. // Provides StoreFinder subscribers with embedded store locator functionality on their website. // // Copyright (C) 2008 by Daniel Vidoni, Sydney Australia, All rights reserved. // Web: www.storefinder.com.au // // You may not distribute this code in any manner, modified or otherwise, without the express, written // consent from Daniel Vidoni. // // You may not make modifications or install this file on your servers. // // You are permitted to link to this code for the purpose of providing your website with store finder functionality. // // In all cases copyright and header must remain intact. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// var isIE = (window.ActiveXObject) ? true : false; // browser detection //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // begin global private variables (do not access or modify from outside scope of this file) // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // A javascript, in a normal html document, opens on an http connection: document.location.protocol equals "http" in Konqueror. // However in Netscape and Explorer it equals "http:". This can breaks things, therefore we do a case insensitive search for the letter 's' // to determine whether we are in secure mode or not. var _sslProtocol = (document.location.protocol.search(/s/i) == -1) ? false : true; // proxy directory, filename and behavior var _proxyDir = "./"; var _proxy = "sfajaxproxy.php"; _proxy += (_sslProtocol) ? "?SSL=true" : "?SSL=false"; // storefinder address var _storefinderRootURL = (_sslProtocol) ? "https://anchor.net.au/secure/" : "http://www."; _storefinderRootURL += "storefinder.com.au"; var _nnRootURL = "http://www.nearlynude.com.au"; var _maps = ''; // map link option ('SFMAPS', 'GOOGLEMAPS' or 'NOMAPS' for no link) var _xmlHttp = null; // XMLHttpRequest object var _resultsHTMLTarget; // results container var _dymHTMLTarget; // 'did you mean' container var _resultHTMLTemplate = ""; // template to use for rendering results (optional) var _retailerID; // identifies account holder var _retailerText = "Locations"; // outlet type to display var _resultsShuffle = 0; // true or false (1 or 0) whether or not to shuffle results var _radius = -1; // search radius in kilometres (-1 means infinite) var _numResults = 3; // maximum number of results to display var _query = ""; // query string to pass to engine var _marketing = ""; // optional marketing information var _country = "AU"; // default country //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // begin handy public functions (can be called safely at any time from beyond the scope of this file) // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Emulate getElementById on document.all only browsers. // Requires that IDs are unique to the page and do not coincide with // NAME attributes on other elements. if( (!document.getElementById) && document.all ) { document.getElementById = function(id) { return document.all[id]; }; } /* if (document.getElementByID) { // Open standards method document.getElementById = function(id) { return document.getElementById(id); }; } else if(document.all) { // IE method document.getElementById = function(id) { return document.all[id]; }; } */ // IsDefined: // returns true if variable defined function IsDefined(variable) { return ( typeof(variable) == "undefined" ) ? false : true; } // IsNumeric: Regular expression that validates a value is numeric // Return true or false function IsNumeric(x) { // compare the argument to the RegEx. The 'match' function returns 0 if the value doesn't match // Note: this WILL allow a number that ends in a decimal: -452. return !!x.match(/^[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?$/); } // PopupURL: opens URL in a non-resizable pop up window (800x700 default size) function PopupURL(url, w, h) { if (!IsDefined(w)) w = 800; if (!IsDefined(h)) h = 770; urlWindow = window.open ( url, 'Popup','toolbar=0,location=0,directories=0,status=1,menubar=0,scrollbars=1,resizable=0,height='+h+',width='+w); urlWindow.focus(); } // StripWhitespace: remove whitespace characters from a string // returns new formatted string function StripWhitespace(s) { // strip newLine, carriage return and tab characters from xml string return s.replace(/[\n\r\t]/g, ''); } // ReplaceAll: replaces all occurences of 'from' with 'to' in string 'str' // returns str function ReplaceAll( str, from, to ) { var idx = str.indexOf( from ); while ( idx > -1 ) { str = str.replace( from, to ); idx = str.indexOf( from ); } return str; } // CreateNewXMLHttpObject: instantiate and return the best version of the XMLHttpRequest object // returns null on fail function CreateNewXMLHttpObject() { var xmlHttp = null; try { xmlHttp = new XMLHttpRequest(); // Firefox, Opera 8.0+, Safari } catch (e) { // Internet Explorer try { xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); // explorer 6 } catch (e) { try { xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); // explorer 5.5 } catch(e) { alert("CreateNewXMLHttpObject: " + e.message); return null; } } } return xmlHttp; } // ParseXML: parse XML to data object // returns null on fail function ParseXML(xmlResponseText) { var xmlDoc; // xmlResponseText = xmlResponseText.replace(/&/g, "&"); // correcting non-XML compliance by HTML encoding '&' to '&' var xmlStr = StripWhitespace(xmlResponseText); // remove problematic characters from XML string try { if (isIE) { // Internet Explorer xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async = false; xmlDoc.loadXML( xmlStr ); if (xmlDoc.parseError.errorCode != 0) { alert("Parser Error in line " + xmlDoc.parseError.line + " " + "position " + xmlDoc.parseError.linePos + "\n" + "Error Code: " + xmlDoc.parseError.errorCode + "\n" + "Error Reason: " + xmlDoc.parseError.reason + "\n" + "Error Line: " + xmlDoc.parseError.srcText); document.getElementById('sf_locality').focus(); return null; } } else { // Non Explorer Dom parser var xmlDoc = new DOMParser().parseFromString( xmlStr, "text/xml" ); if (xmlDoc.documentElement.nodeName=="parsererror") { alert("Parser Error: " + xmlDoc.documentElement.childNodes[0].nodeValue); document.getElementById('sf_locality').focus(); return null; } } } catch(e) { alert(e.message); return null; } return xmlDoc; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // begin private functions (internal use only) // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // _Echo: sends string to destination html target // if no destination defined it will go to the 'sf_results' html element function _Echo(str, dst) { if (IsDefined(dst)) { dst.innerHTML += str; return; } _resultsHTMLTarget.innerHTML += str; } // _GetText: browser safe retrieval of text from a node // returns node value function _GetText(node) { return (isIE) ? node.text : node.textContent; } // _FindAttribValue: iterates through attribute list searching for string and returns its value // returns null on fail function _FindAttribValue(attList, str) { for (a=0; a" + itemStr + ""); break; case 'URL': prefix = ( itemStr.search(/http:\/\//i)==-1 ) ? "http://" : ""; _Echo(name + "" + itemStr + ""); break; case 'LINK': urlvars = itemStr.split(";"); prefix = ( urlvars[1].search(/http:\/\//i)==-1 ) ? "http://" : ""; _Echo(name + "" + urlvars[0] + ""); break; case 'IMAGE': prefix = ( itemStr.search(/http:\/\//i)==-1 ) ? "http://" : ""; _Echo( name + "" ); break; case 'IMAGELINK': urlvars = itemStr.split(";"); alert(urlvars[0] + " " + urlvars[1]); prefixImg = ( urlvars[0].search(/http:\/\//i)==-1 ) ? "http://" : ""; prefixLnk = ( urlvars[1].search(/http:\/\//i)==-1 ) ? "http://" : ""; _Echo( " " ); break; default: _Echo(name + _GetText(resItem)); break; } } // _DisplayResult: renders one entire self-contained result panel function _DisplayResult(resultFields) { // load results details var dist = _GetDistance(resultFields); var bizName = _GetLocationName(resultFields); var addr1 = _GetStreetAddress(resultFields); var addr2 = _GetAddress2(resultFields); var suburb = _GetSuburb(resultFields); var pcode = _GetPostcode(resultFields); var addrStr1 = (addr1) ? addr1 + ", " : ""; // address 1 is optional var addrStr2 = (addr2) ? addr2 + ", " : ""; // address 2 is optional // render distance distStr = (dist<=1) ? "Within 1 km" : "Approx. " + dist + " kms"; _Echo("" + distStr + " 
"); // distance _Echo("" + bizName + "
"); // business name // render address if (addr1) _Echo("" + addrStr1 + "
"); if (addr2) _Echo("" + addrStr2 + "
"); _Echo("" + suburb + "  " + pcode + "
"); // suburb and postcode // render map link if (addr1) { if (_maps=='GOOGLEMAPS') { // google maps button link _Echo("" + "View Google Map" + "
" + "
"); } else if (_maps=='SFMAPS') { // storefinder maps button link _Echo("" + "View Map" + ""); } } // continue with additional optional fields... _Echo("
"); for (j=6; j  "); _DisplayUserDefinedResultItem(resultFields[j], true); _Echo("
"); } } _Echo("

"); } // _DisplayResultUsingTemplate: renders one entire self-contained result panel using loaded templates function _DisplayResultUsingTemplate(resultFields) { if (!_resultHTMLTemplate) { alert("_DisplayResultUsingTemplate: no template loaded"); return false; } var scratchTemplate = _resultHTMLTemplate; scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_DISTANCE %>", _GetDistance(resultFields)) scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_LOCATION_NAME %>", _GetLocationName(resultFields)); scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_STREET_ADDR %>", _GetStreetAddress(resultFields)); scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_ADDR2 %>", _GetAddress2(resultFields)); scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_SUBURB %>", _GetSuburb(resultFields)); scratchTemplate = ReplaceAll( scratchTemplate, "<% SF_POSTCODE %>", _GetPostcode(resultFields)); for (j=6; j", _GetText(resultFields[j]) ); } _Echo(scratchTemplate + "
"); return true; } // _StateChangedResults_CB: callback function processes results of AJAX call function _StateChangedResults_CB() { if (!(_xmlHttp.readyState==4 || _xmlHttp.readyState=="complete")) return; // clean results html _resultsHTMLTarget.innerHTML = ""; _dymHTMLTarget.innerHTML = ""; _ShowLoadingIcon(false); // parse XML to data object // var xmlDoc = _xmlHttp.responseXML; var xmlDoc = ParseXML(_xmlHttp.responseText); if (!xmlDoc) return; // check for store finder error values err = xmlDoc.getElementsByTagName("error"); if (err.length!=0) { errStr = _GetText(err[0].childNodes[0]); _Echo("
\"Error\"" + errStr + "
"); document.getElementById('sf_locality').select(); return; } // check for locality results localities = xmlDoc.getElementsByTagName("locality"); if (localities.length!=0) { for (i=0; i" + loc + ", " + state + "  " + pc + "
", _dymHTMLTarget); } document.getElementById("sf_didyoumean").style.display = 'block'; document.getElementById('sf_locality').focus(); return; } // render results (if any) results = xmlDoc.getElementsByTagName("result"); if (results.length!=0) { // extract and show header information headerItem = xmlDoc.getElementsByTagName("header")[0].childNodes; loc = _GetText(headerItem[0]); state = _GetText(headerItem[1]); pc = _GetText(headerItem[2]); _country = _GetText(headerItem[3]); if (_maps=="") _maps = _GetText(headerItem[4]); if (loc) document.getElementById('sf_locality').value = loc; else if (pc) document.getElementById('sf_locality').value = pc; _Echo("
Nearest " + _retailerText + "
to " + loc + " " + state + " " + pc + "

"); // render results for (i=0; i"); else _Echo("\"\""); _DisplayResult(results[i].childNodes); } } } else { _Echo("
No results found.
"); document.getElementById('sf_locality').focus(); } // add footer // _Echo( "

" + // "Provided by " + // " StoreFinder

" ); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // begin public SFAJAX functions ( API ) // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // SFAJAXInitialise: initialises relevant variables for safe functioning of SFAJAX // returns false on fail, true on success function SFAJAXInitialise(retailerID, retailerText, maps, resultsShuffle) { _resultsHTMLTarget = document.getElementById("sf_results"); _resultsHTMLTarget.innerHTML = ""; _dymHTMLTarget = document.getElementById("sf_didyoumean"); _dymHTMLTarget.innerHTML = ""; if (!IsNumeric(retailerID) || retailerID=="" || retailerID==0) { alert("SFAJAXInitialise: " + retailerID + " ID Invalid."); return false; } _retailerID = retailerID; if (IsDefined(retailerText)) _retailerText = retailerText; if (IsDefined(resultsShuffle)) _resultsShuffle = resultsShuffle; if (IsDefined(maps)) _maps = maps; if (!_InitialiseXmlHttpObject()) return false; document.getElementById('sf_locality').focus(); return true; } // SFAJAXSetProxyLocation: sets a new directory/location on the server for the proxy file. // returns previous proxy file directory function SFAJAXSetProxyLocation(dir) { var prevDir = _proxyDir; if (!IsDefined(dir) || !dir) alert("SFAJAXSetProxyLocation: No directory name specified."); else _proxyDir = dir; return prevDir; } // SFAJAXLoadFile: load a file from a local domain url. // NB: Only works for files within current domain. // returns null on fail, file content on success function SFAJAXLoadFile(fileName) { if (!IsDefined(fileName) || !fileName) { alert("SFAJAXLoadFile: No file name specified."); return false; } if (!_InitialiseXmlHttpObject()) return null; _xmlHttp.open("GET", fileName, false); // synchronous ajax get _xmlHttp.send(null); return _xmlHttp.responseText; } // SFAJAXLoadResultTemplate: load a template file from a local domain url. // NB: Only works for files within current domain. // returns false on fail, true on success function SFAJAXLoadResultTemplate(fileName) { if (!IsDefined(fileName) || !fileName) { alert("SFAJAXLoadResultTemplate: No file name specified."); return false; } if (!_InitialiseXmlHttpObject()) return false; _xmlHttp.open("GET", fileName, false); // synchronous ajax get _xmlHttp.send(null); _resultHTMLTemplate = _xmlHttp.responseText; return true; } // SFAJAXLoadFileIntoHTMLElement: load an html file containing the form widget into the (optional) destination html id tag. // NB: Only works for files within current domain. // returns false on fail, true on success function SFAJAXLoadFileIntoHTMLElement(formFileName, dstID) { if (!IsDefined(formFileName) || !formFileName) { alert("SFAJAXLoadFileIntoHTMLElement: No file name specified."); return false; } if (!IsDefined(dstID)) { alert("SFAJAXLoadFileIntoHTMLElement: No destination element specified."); return false; } fileContent = SFAJAXLoadFile(formFileName); if (!fileContent) return false; _Echo(fileContent, document.getElementById(dstID)); return true; } // SFAJAXShowResults: setup AJAX call using passed arguments // returns false on fail, true on success function SFAJAXShowResults(location, query, radius, numResults, market) { if (_xmlHttp==null) { // ensure xml system instantiated alert("SFAJAXShowResults: StoreFinder AJAX module not initialised!"); document.getElementById('sf_locality').select(); return false; } if (!IsDefined(location) || location=="") { alert("Please enter a 3 or 4 digit postcode, or locality name."); document.getElementById('sf_locality').select(); return false; } if (location.search(',')!=-1 || location.search('/')!=-1) { alert("Please enter either a town, suburb or postcode."); document.getElementById('sf_locality').select(); return false; } if (!IsNumeric(location) && location.length<3) { // interesting note: shortest town name in Australia is 'Me' alert("Please enter a valid town or suburb name."); document.getElementById('sf_locality').select(); return false; } if (IsNumeric(location) && (location<100 || location>9999)) { alert("Please enter a valid 3 or 4 digit postcode."); document.getElementById('sf_locality').select(); return false; } if (IsDefined(query)) _query = query; if (IsDefined(radius)) _radius = parseInt(radius,10); if (IsDefined(numResults)) _numResults = numResults; if (IsDefined(market)) _marketing = market; _ShowLoadingIcon(true); // For security reasons the XMLHttpRequest Object only works within a domain ! // This means that calls to locations outside the domain leads to security errors such as: // [ Exception... "Access to restricted URI denied" code: "1012" nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI) ] // We use a serverside page proxy that works around the cross domain issue by taking the request on the users server // and sending it from there to the StoreFinder site, then return the value via the proxy back to the user. var timestamp = new Date(); var urlStr = _proxyDir + _proxy + "&RetailerID=" + _retailerID + "&Postcode=" + encodeURIComponent(location) + "&Marketing=" + encodeURIComponent(_marketing) + "&Radius=" + _radius + "&Results=" + _numResults + "&ResultsShuffle=" + _resultsShuffle + "&Query=" + encodeURIComponent(_query) + "&TimeStamp=" + timestamp.getTime(); // avoid caching _xmlHttp.open("GET", urlStr, true); _xmlHttp.onreadystatechange = _StateChangedResults_CB; _xmlHttp.send(null); return true; } // SFAJAXInstallDefaultWidget: dynamically generates HTML form code inserted at page load time and // initialises SFAJAX system. function SFAJAXInstallDefaultWidget(retailerID, retailerText, maps, resultsShuffle) { document.write("
" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + // populated with place name matches "" + "" + "" + // populated with results output and error messages "" + "
 " + "  Enter your town or suburb or postcode: 
" + "" + "" + // "" + // "" + "" + "" + "" + "" + "
" + "
" + "
" + "
"); // add footer document.write("

" + "Provided by " + " StoreFinder

" ); SFAJAXInitialise(retailerID, retailerText, maps, resultsShuffle); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // END OF SFAJAX MODULE. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////