//=======================================================================
// Copyright (c) 2005 Help Center Live. All Rights Reserved
// This file is part of Aardvark.
// Aardvark is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// Aardvark is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Help Center Live; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// Contributors: Michael Bird
// File Comments:
// The Aardvark class allows variables to be transferred between client and
// server asynchronously using the XMLHttpRequest object, or the fallback method
// if the XMLHttpRequest object is not supported.
function Aardvark(object) {
// Define the global variables this class uses
// We need to know which object to assign the received variables to
this.object = object;
// This is the object for the XMLHttpRequest class
this.xmlhttp = new XMLHttpRequest();
// Macs dont like images using the Image() class, so we
// need to detect if the browser is a mac and act accoringly
this.mac = navigator.platform.indexOf('Mac');
// MS IE doesnt like image event handlers
this.msie = navigator.userAgent.indexOf("MSIE");
// The iamge object for sending our data
this.image = new Image();
// Boolean whether to use the fallback method instead of XMLHttpRequest
this.fallback = false;
// The URL that the HTTP request will be sent to
this.url = '';
// An array of variables which can be added to using the add() method and
// are sent across when the send() method is called
this.variables = new Array();
// An array of received variables
this.vars = new Array();
// This is the hostname of the web page the class has been called from
this.connect_host = '';
// This is the hostname of the web page the class is located at
this.install_host = '';
// Boolean if the XMLHttpRequest object has been defined.
this.initiated = false;
// Boolean if the url has already been built
this.url_built = false;
// Contains the function that will be executed when a response has been
// received from the server
this.response = '';
// Data garbage collection.. the default is to reset the variable output
// buffer after sending the request, however you may want to add variables
// to the buffer, send a request then add more and send another request
// in which case this should be set to 'append' which can be done
// via the gc arguement in the send() function or by setting it directly
this.gc = 'reset';
// XMLHttpRequest can't sent HTTP requests to remote hosts, so check that
// the URL is not remote.
this.validateurl = function()
{
// Get the url of the request
this.connect_host = this.url;
// Get the url of the current page
this.install_host = document.location.toString();
// Use regular expressions to filer out the host names
this.connect_host = this.connect_host.replace(/(.*?)\/\/(.*?)\/(.*)/i, "$1//$2");
this.install_host = this.install_host.replace(/(.*?)\/\/(.*?)\/(.*)/i, "$1//$2");
this.connect_host = this.connect_host.replace(/(.*?)\/\/(.*?)/i, "$2");
this.install_host = this.install_host.replace(/(.*?)\/\/(.*?)/i, "$2");
// If the host we are connecting to is not the same as the one
// we are connecting from, dont use the XMLHttpRequest object
if (this.connect_host !== this.install_host) {
// Make sure the fallback method is used
this.fallback = true;
}
}
// Add variables to the array, so that they can be sent when the
// 'send' method is called.
// --
// [required] variable: The variable name
//
// [required] data: The data that the variable contains
// --
this.add = function(variable, data)
{
// JavaScript arrays are 0 index, so the length
// of the array will be (index + 1). This is useful
// as we will need to refer to the new array value
// that is about to be created
var i = this.variables.length;
// Push the array (adds a new value to the array)
this.variables.push(Array());
// Add two array values within this array that will hold
// the variable info
this.variables[i].push('variable');
this.variables[i].push('data');
// Assign the variable info to the array
this.variables[i]['variable'] = escape(variable);
this.variables[i]['data'] = escape(data);
}
// Build the URL using the variables stored by the 'add()' function
// --
// [required] url: The address that the variables will be built onto
// --
this.build = function(url)
{
// Only build the url if it has not been built before - we don't
// want any duplicate values
if (!this.url_built) {
// Extract domain from location
url_location = document.location.toString();
domain = url_location.match( /:\/\/([^\/:]+)/ );
domain = domain[1]?domain[1]:'';
// Add domain to set correct cookie domain
this.add('domain', escape(domain));
// If there are values in the variable output buffer
if (this.variables.length > 0) {
// Loop through them
for (var i = 0; i < this.variables.length; i++) {
if (url.indexOf('?') > -1) {
// If the URL already has variables in it then add to them
url += '&aardvark_'+this.variables[i]['variable']+'='+this.variables[i]['data'];
} else {
// If the URL has no variables in it, start the string off
url += '?aardvark_'+this.variables[i]['variable']+'='+this.variables[i]['data'];
}
}
}
// Make the built URL accessable globally
this.url = url;
// Set the flag to indicate we have just built the URL
this.url_built = true;
}
}
// Garbage collection
this.garbage = function()
{
// Find what type of garbage collection we need to do
switch (this.gc) {
// Append the variable output buffer
case 'append':
// Leave this.variables alone
break;
// Reset the variable output buffer
case 'reset':
default:
// Reset the array
this.variables = new Array();
break;
}
}
// Send the request to the server
// --
// [required] url: The address that the request will be sent to
//
// [optional] response: The javascript function that will be invoked when the request
// completes and incoming variables all sorted out nicely
//
// [optional] gc: Garbage collection..
// 'reset': Resets the variable output buffer
// 'append': Keeps the current varible output buffer and
// appends new variables to the end of it
// --
this.send = function(url, response, gc)
{
// Make the response parameter global
this.response = response;
// Check to see if we need to override the garbage collection
if (gc !== '') {
this.gc = gc;
}
// Build the URL from the variable output buffer (this.variables array)
this.build(url);
// Check to see if the URL we are requesting is on the same domain as this file.
// If not, we cant use XMLHttpRequest
this.validateurl();
// The data has now nicely been formatted into a URL, so lets deal with the garbage
this.garbage();
// If we dont need to use the fallback method and its not an SSL connection, then
// use the XMLHttpRequest object
if (!this.fallback && this.url.substring(0, 5) !== 'https') {
// Define the XMLHttpRequest object
this.xmlhttp = new XMLHttpRequest();
// We need to know when the request has completed, so invoke this
// function when the request's state changes
this.xmlhttp.onreadystatechange = function()
{
// Define the content variable
var content = '';
// Define the headers array
var headers = new Array();
// Define the loop index
var i = 0;
// Catch exceptions
try {
// If the request's state is 4 (completed)..
if (eval(object+".xmlhttp.readyState") == 4) {
// If the HTTP request returned a 200 OK response then continue
if (eval(object+".xmlhttp.status") == 200) {
// IE doesnt lump all the Set-Cookie's into one header so we need
// to pick them out
if (navigator.appName == 'Microsoft Internet Explorer') {
// Get the headers
headers = eval(object+".xmlhttp.getAllResponseHeaders()");
// Split up the headers so they are separate
headers = headers.split("\n");
// Loop through them
for (i = 0; i < headers.length; i++) {
// If its a Set-Cookie header then thats good..
if (headers[i].substring(0, 11) == 'Set-Cookie:') {
headers[i] = headers[i].substring(11, headers[i].length)
// We dont need the expiry date or path, so split it up
headers[i] = headers[i].split("; ");
// The first bit contains the name and data
if (headers[i][0].substring(0, 1) == ' ') {
content += headers[i][0].substring(1, headers[i][0].length) + '; ';
} else {
content += headers[i][0] + '; ';
}
}
}
} else {
// All other browers make it simple for us..
headers = eval(object+".xmlhttp.getResponseHeader('Set-Cookie')");
// Split up the header into each cookie
headers = headers.split("path=/");
// Loop through the cookies
for (i = 0; i < headers.length; i++) {
// We dont need the expiry date or path, so split it up
headers[i] = headers[i].split("; ");
// The first bit contains the name and data
if (headers[i][0] !== '') {
// Safari separates its cookies with a comma
if (headers[i][0].substring(0, 2) == ', ') {
content += headers[i][0].substring(2, headers[i][0].length) + '; ';
// Other browsers (FireFox etc) with a line break
} else if (headers[i][0].substring(0, 1) == "\n") {
content += headers[i][0].substring(1, headers[i][0].length) + '; ';
} else {
content += headers[i][0] + '; ';
}
}
}
}
// Change them into an easier to deal with format identical to
// the format returned with the js document.cookie
content = content.replace(/, /gm, "; ");
content = content.replace(/\n/gm, "; ");
// Parse the contents
eval(object+".parse('"+content+"')");
}
// If the request's state is 0 (undefined)..
} else if (eval(object+".xmlhttp.readyState") == 0) {
// Change to the fallback method
eval(object+".fallback = true");
// Try again
eval(object+".send("+object+".url, '"+response+"', '"+gc+"')");
}
} catch(e) {
// An error occured, return false
return false;
}
};
// Set the XMLHttpRequest to make a GET request to this.url is asynchronous mode
this.xmlhttp.open("GET", this.url, true);
// Send the request
this.xmlhttp.send(null);
// The fallback method if we can't use XMLHttpRequest
} else {
// If the browser is a mac, we can't use the Image() object
if (navigator.platform.indexOf('Mac') > -1) {
// Set the div to include the image, forcing the browser to send the request
document.getElementById('aardvark_div_'+object).innerHTML = '';
// make it avaiable globally
this.image = document.getElementById('aardvark_img_'+object);
} else {
// Set this.image to a new image
this.image = new Image();
// Send it to the request url
this.image.src = this.url;
}
// We know the image is going to produce errors because no image is actually returned
// Allow the browser time to deal with the cookies in the response then parse them
if (this.msie > -1) {
setTimeout(object+".parse(document.cookie)", 1500);
} else {
this.image.onerror = setTimeout(object+".parse(document.cookie)", 1200);
}
}
}
this.parse = function(content)
{
// Allow for new URL's
this.url_built = false;
// Define the input buffer containing incoming variables
var variables = new Array();
// Split up the contents of the HTTP response into nice chunks of variables
content = content.split("; ");
// Define the arr index
var arr = 0;
// The number of variables we have
var clen = content.length;
// Used to store the variable name and data of the current variable in the loop
var rcontent = '';
// Define the loop index
var i = 0;
// Define the variable that stores the incoming variable name
var varname = '';
// Define the variable that stores the incoming variable data
var vardata = '';
// Loop through the incoming variables
for (i = 0; i < clen; i++) {
// Split the variable we are working with into two bits, the name and the data
rcontent = content[i].split('=');
// If the name starts with aardvark_ then it belongs to us
if (rcontent[0].substring(0, 9) == 'aardvark_') {
// Make sure the name is not nothing
if (rcontent[0].substring(9, rcontent[0].length) !== '') {
// Put the variable name and data into two local variables so that code is cleaner
varname = unescape(rcontent[0].substring(9, rcontent[0].length));
vardata = unescape(rcontent[1]);
// If the variable doesnt exist already then create it
if (!eval("this.vars."+varname)) {
// Set the global 'incoming variable' array to include the variable
this.vars.push(varname);
}
if (vardata.toString() != 'undefined') {
// Assign the data to the 'incoming variable' array
eval("this.vars."+varname+" = '"+vardata+"'");
}
// We also want to send an array of the variables we have just received from
// the request to the function that will be invoked as that function may
// not know what variables it is getting
arr = variables.length;
variables.push(Array());
variables[arr].push('variable');
variables[arr].push('data');
variables[arr]['variable'] = this.object+'.'+varname;
variables[arr]['data'] = vardata;
// We're done with the cookie, remove it
document.cookie = rcontent[0]+'=; expires=01/01/1970 00:00:00; path=/;';
}
}
}
// All incoming variables dealt with, invoke the resonse function
if (this.response !== '') {
eval(this.response);
}
}
// Mac's cant use the Image() object, so write the image onto the page instead inside this div
if (navigator.platform.indexOf('Mac') > -1) {
document.write('