// Simple Set Clipboard System
// Author: Joseph Huckaby

var ZeroClipboard = {
	
	version: "1.0.4",
	clients: {}, // registered upload clients on page, indexed by id
	moviePath: 'ZeroClipboard.swf', // URL to movie
	nextId: 1, // ID of next movie
	
	$: function(thingy) {
		// simple DOM lookup utility function
		if (typeof(thingy) == 'string') thingy = document.getElementById(thingy);
		if (!thingy.addClass) {
			// extend element with a few useful methods
			thingy.hide = function() { this.style.display = 'none'; };
			thingy.show = function() { this.style.display = ''; };
			thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; };
			thingy.removeClass = function(name) {
				this.className = this.className.replace( new RegExp("\\s*" + name + "\\s*"), " ").replace(/^\s+/, '').replace(/\s+$/, '');
			};
			thingy.hasClass = function(name) {
				return !!this.className.match( new RegExp("\\s*" + name + "\\s*") );
			}
		}
		return thingy;
	},
	
	setMoviePath: function(path) {
		// set path to ZeroClipboard.swf
		this.moviePath = path;
	},
	
	dispatch: function(id, eventName, args) {
		// receive event from flash movie, send to client		
		var client = this.clients[id];
		if (client) {
			client.receiveEvent(eventName, args);
		}
	},
	
	register: function(id, client) {
		// register new client to receive events
		this.clients[id] = client;
	},
	
	getDOMObjectPosition: function(obj) {
		// get absolute coordinates for dom element
		var info = {
			left: 0, 
			top: 0, 
			width: obj.width ? obj.width : obj.offsetWidth, 
			height: obj.height ? obj.height : obj.offsetHeight
		};

		while (obj) {
			info.left += obj.offsetLeft;
			info.top += obj.offsetTop;
			obj = obj.offsetParent;
		}

		return info;
	},
	
	Client: function(elem) {
		// constructor for new simple upload client
		this.handlers = {};
		
		// unique ID
		this.id = ZeroClipboard.nextId++;
		this.movieId = 'ZeroClipboardMovie_' + this.id;
		
		// register client with singleton to receive flash events
		ZeroClipboard.register(this.id, this);
		
		// create movie
		if (elem) this.glue(elem);
	}
};

ZeroClipboard.Client.prototype = {
	
	id: 0, // unique ID for us
	ready: false, // whether movie is ready to receive events or not
	movie: null, // reference to movie object
	clipText: '', // text to copy to clipboard
	handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor
	cssEffects: true, // enable CSS mouse effects on dom container
	handlers: null, // user event handlers
    title: 'Copy to clipboard',
    pos_top: 0,
    pos_left: 0,
    url: '',
	
	glue: function(elem) {
		// glue to DOM element
		// elem can be ID or actual DOM element object
		this.domElement = ZeroClipboard.$(elem);
		
		// float just above object, or zIndex 99 if dom element isn't set
		var zIndex = 9990;
		if (this.domElement.style.zIndex) {
			zIndex = parseInt(this.domElement.style.zIndex) + 1;
		}
		
		// find X/Y position of domElement
		var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
		
		// create floating DIV above element
		this.div = document.createElement('div');
        //this.div.title = this.title;
		var style = this.div.style;
		style.position = 'absolute';
		style.left = '' + box.left + 'px';
		style.top = '' + box.top + 'px';
		style.width = '' + box.width + 'px';
		style.height = '' + box.height + 'px';
		style.zIndex = zIndex;

        this.pos_top = box.top;
        this.pos_left = box.left;

		// style.backgroundColor = '#f00'; // debug
		
		var body = document.getElementsByTagName('body')[0];
		body.appendChild(this.div);
		
		this.div.innerHTML = this.getHTML( box.width, box.height );
	},
	
	getHTML: function(width, height) {
		// return HTML for movie
		var html = '';
		var flashvars = 'id=' + this.id + 
			'&width=' + width + 
			'&height=' + height;

		if (navigator.userAgent.match(/MSIE/)) {
			// IE gets an OBJECT tag
			var protocol = location.href.match(/^https/i) ? 'https://' : 'http://';
			html += '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="'+protocol+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="'+width+'" height="'+height+'" id="'+this.movieId+'" align="middle"><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="false" /><param name="movie" value="'+ZeroClipboard.moviePath+'" /><param name="loop" value="false" /><param name="menu" value="false" /><param name="quality" value="best" /><param name="bgcolor" value="#ffffff" /><param name="flashvars" value="'+flashvars+'"/><param name="wmode" value="transparent"/></object>';
		}
		else {
			// all other browsers get an EMBED tag
			html += '<embed id="'+this.movieId+'" src="'+ZeroClipboard.moviePath+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="'+width+'" height="'+height+'" name="'+this.movieId+'" align="middle" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+flashvars+'" wmode="transparent" />';
		}
		return html;
	},
	
	hide: function() {
		// temporarily hide floater offscreen
		if (this.div) {
			this.div.style.left = '-2000px';
		}
	},
	
	show: function() {
		// show ourselves after a call to hide()
		this.reposition();
	},
	
	destroy: function() {
		// destroy control and floater
		if (this.domElement && this.div) {
			this.hide();
			this.div.innerHTML = '';
			
			var body = document.getElementsByTagName('body')[0];
			try { body.removeChild( this.div ); } catch(e) {;}
			
			this.domElement = null;
			this.div = null;
		}
	},
	
	reposition: function(elem) {
		// reposition our floating div, optionally to new container
		// warning: container CANNOT change size, only position
		if (elem) {
			this.domElement = ZeroClipboard.$(elem);
			if (!this.domElement) this.hide();
		}
		
		if (this.domElement && this.div) {
			var box = ZeroClipboard.getDOMObjectPosition(this.domElement);
			var style = this.div.style;
			style.left = '' + box.left + 'px';
			style.top = '' + box.top + 'px';
            this.pos_top = box.top;
            this.pos_left = box.left;
		}
	},
	
	setText: function(newText) {
		// set text to be copied to clipboard
		this.clipText = newText;
		if (this.ready) this.movie.setText(newText);
	},
	
	addEventListener: function(eventName, func) {
		// add user event listener for event
		// event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel
		eventName = eventName.toString().toLowerCase().replace(/^on/, '');
		if (!this.handlers[eventName]) this.handlers[eventName] = [];
		this.handlers[eventName].push(func);
	},
	
	setHandCursor: function(enabled) {
		// enable hand cursor (true), or default arrow cursor (false)
		this.handCursorEnabled = enabled;
		if (this.ready) this.movie.setHandCursor(enabled);
	},
	
	setCSSEffects: function(enabled) {
		// enable or disable CSS effects on DOM container
		this.cssEffects = !!enabled;
	},
	
	receiveEvent: function(eventName, args) {
		// receive event from flash
		eventName = eventName.toString().toLowerCase().replace(/^on/, '');
				
		// special behavior for certain events
		switch (eventName) {
			case 'load':
				// movie claims it is ready, but in IE this isn't always the case...
				// bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function
				this.movie = document.getElementById(this.movieId);
				if (!this.movie) {
					var self = this;
					setTimeout( function() { self.receiveEvent('load', null); }, 1 );
					return;
				}
				
				// firefox on pc needs a "kick" in order to set these in certain cases
				if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) {
					var self = this;
					setTimeout( function() { self.receiveEvent('load', null); }, 100 );
					this.ready = true;
					return;
				}
				
				this.ready = true;
				this.movie.setText( this.clipText );
				this.movie.setHandCursor( this.handCursorEnabled );
				break;
			
			case 'mouseover':
				if (this.domElement && this.cssEffects) {
					this.domElement.addClass('hover');
					if (this.recoverActive) this.domElement.addClass('active');
				}
				break;
			
			case 'mouseout':
				if (this.domElement && this.cssEffects) {
					this.recoverActive = false;
					if (this.domElement.hasClass('active')) {
						this.domElement.removeClass('active');
						this.recoverActive = true;
					}
					this.domElement.removeClass('hover');
				}
				break;
			
			case 'mousedown':
				if (this.domElement && this.cssEffects) {
					this.domElement.addClass('active');
				}
				break;
			
			case 'mouseup':
				if (this.domElement && this.cssEffects) {
					this.domElement.removeClass('active');
					this.recoverActive = false;
				}
				break;
		} // switch eventName
		
		if (this.handlers[eventName]) {
			for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) {
				var func = this.handlers[eventName][idx];
			
				if (typeof(func) == 'function') {
					// actual function reference
					func(this, args);
				}
				else if ((typeof(func) == 'object') && (func.length == 2)) {
					// PHP style object + method, i.e. [myObject, 'myMethod']
					func[0][ func[1] ](this, args);
				}
				else if (typeof(func) == 'string') {
					// name of function
					window[func](this, args);
				}
			} // foreach event handler defined
		} // user defined handler for event
	}
	
};

/*** v5_deals_clipboard */
$(document).ready(function () {
    var clip = null;
    var cclip = new Array();
    ZeroClipboard.setMoviePath('/flash/ZeroClipboard.swf');
    permalink_clip();

    add_coupon_clip('badge', 'alt');

    $('span.printable').mouseover( function(event) {
        var text = "Click to use this coupon";
        if (this.childNodes[1]) {
            text = "Click to copy this code to your clipboard and go to site";
        }
        x = event.pageX;
        y = event.pageY;
        $("body").append("<div id='tooltip'>"+text+"</div>");
        $("#tooltip").css("left", x).css("top", y);
    } );
    
    $('span.printable').mouseout( function(event) {
        $("#tooltip").remove();
    } );

    $('span.printable').mouseup( function(event) {
        obja = this.parentNode.parentNode.parentNode.getElementsByTagName('a');
        len = obja.length;
        link = obja[len-1].href;
        //var tracker = obja[len-1].onclick;
        //tracker(event);
        window.open(link, '_blank');
    } );
});

function hide_tooltip() {
    $("#tooltip").remove();
}

function show_tooltip(event, text) {
    x = event.pageX;
    y = event.pageY;
    $("body").append("<div id='tooltip'>"+text+"</div>");
    $("#tooltip").css("left", x).css("top", y);
}

function getDOMObjectPosition(obj) {
    // get absolute coordinates for dom element
    var info = {
        left: 0, 
        top: 0, 
        width: obj.width ? obj.width : obj.offsetWidth, 
        height: obj.height ? obj.height : obj.offsetHeight
    };

    while (obj) {
        info.left += obj.offsetLeft;
        info.top += obj.offsetTop;
        obj = obj.offsetParent;
    }

    return info;
}

function permalink_clip() {
    // setup single ZeroClipboard object for all our elements
    clip = new ZeroClipboard.Client();
    clip.setHandCursor( true );
    clip.title = "Click to copy this deal's link to your clipboard";    
    clip.func = undefined;

    clip.addEventListener( 'complete', function(client) { if(clip.func)(clip.func()); alert("The URL has been copied to your clipboard")} );
    
    clip.addEventListener( 'mouseover', function(client) { 
        $("body").append("<div id='tooltip'>"+client.title+"</div>");
        $("#tooltip").css("left", client.pos_left + 10).css("top", client.pos_top + 10);
    } );

    clip.addEventListener( 'mouseout', function() { 
        $("#tooltip").remove();
    } );
    
    // assign a common mouseover function for all elements using jQuery
    $('a.permalink').mouseover( function(event) {
        
        // set the clip text to our innerHTML
        clip.func = this.onclick;
        clip.setText( this.href );
        
        // reposition the movie over our element
        // or create it if this is the first time
        if (clip.div) {
            clip.receiveEvent('mouseout', null);
            clip.reposition(this);
        }
        else clip.glue(this);
        
        // gotta force these events due to the Flash movie
        // moving all around.  This insures the CSS effects
        // are properly updated.
        clip.receiveEvent('mouseover', null);

        return false;
    } );

    $('a.permalink').mouseout( function(event) {
        // gotta force these events due to the Flash movie
        // moving all around.  This insures the CSS effects
        // are properly updated.
        clip.receiveEvent('mouseout', null);
        return false;
    } );
}


function reuse_coupon_clip(elems, from) {
    id=elems.id;
    func=elems.onclick;
    
    cclip = new ZeroClipboard.Client();
    cclip.setHandCursor( true );

    if (from) {
        cclip.setText( elems.getAttribute(from) );
    }
    else {
        cclip.setText( elems.text );
    }
    cclip.url = elems.getAttribute('href');

    if (cclip.url && cclip.url != '') {
        cclip.title = "Click to copy this code to your clipboard and go to site";
    }
    else {
        cclip.title = "Click to copy this code to your clipboard";
    }
    
    cclip.addEventListener( 'mouseover', function(client) { 
        $("body").append("<div id='tooltip'>"+client.title+"</div>");
        $("#tooltip").css("left", client.pos_left + 10).css("top", client.pos_top + 10);
    } );

    cclip.addEventListener( 'mouseout', function() { 
        $("#tooltip").remove();
    } );
    
    cclip.addEventListener( 'complete', function(client, text) { 
        func();
        alert("The coupon code has been copied to your clipboard.");
        if (client.url && client.url != '') {
            window.open(client.url, '_blank');
        }
    } );
    
    cclip.glue( elems.getAttribute('id') );

    return cclip;
}

function add_coupon_clip(className, from) {
    // assign a common mouseover function for all elements using jQuery
    $('a.badge').mouseover( function(event) {
        cclip = reuse_coupon_clip(this, from);
        //alert(this.id);
        cclip.receiveEvent('mouseout', null);
        cclip.reposition(this);
        cclip.receiveEvent('mouseover', null);

        return false;
    } );

    $('a.badge').mouseout( function(event) {
        cclip && cclip.receiveEvent('mouseout', null);

        return false;
    } );
    return false;
}

/*
function old_add_coupon_clip(className, from) {
    elems = $('.' + className);
    url = '';

    cclip = new Array();
    for (i=0; i<elems.length; i++) {
        id=elems[i].id;
        func=elems[i].onclick;
        cclip[id] = new ZeroClipboard.Client();
        cclip[id].setHandCursor( true );

        if (from) {
            cclip[id].setText( elems[i].getAttribute(from) );
        }
        else {
            cclip[id].setText( elems[i].text );
        }
        cclip[id].url = elems[i].getAttribute('href');

        if (cclip[id].url && cclip[id].url != '') {
            cclip[id].title = "Click to copy this code to your clipboard and go to site";
        }
        else {
            cclip[id].title = "Click to copy this code to your clipboard";
        }
        
        cclip[id].addEventListener( 'mouseover', function(client) { 
            $("body").append("<div id='tooltip'>"+client.title+"</div>");
            $("#tooltip").css("left", client.pos_left + 10).css("top", client.pos_top + 10);
        } );

        cclip[id].addEventListener( 'mouseout', function() { 
            $("#tooltip").remove();
        } );
        
        cclip[id].addEventListener( 'complete', function(client, text) { 
            func();
            alert("The coupon code has been copied to your clipboard.");
            if (client.url && client.url != '') {
                window.open(client.url, '_blank');
            }
        } );

        cclip[id].glue( elems[i].getAttribute('id') );
    }

    // assign a common mouseover function for all elements using jQuery
    $('a.badge').mouseover( function(event) {
        cclip[this.id].receiveEvent('mouseout', null);
        cclip[this.id].reposition(this);
        cclip[this.id].receiveEvent('mouseover', null);

        return false;
    } );

    $('a.badge').mouseout( function(event) {
        cclip[this.id].receiveEvent('mouseout', null);

        return false;
    } );
    return false;
}
*/


/*** v5.js */

function loadStoresFlyout() {
    $.get(
        '/ajax/v5stores.html',
        function(data) {
            $('#browseStoresFlyout').html(data);
        }
    );
}

/*instead of rating.js*/

function loader_show() {
    var loader = document.getElementById('ajax');
    loader.style.display = 'block';
}


function loader_hide() {
    document.getElementById('ajax').style.display = 'none';
}


function vote_request(url, deal_id, value, reload, popup) {
    if (url == '') {
        url = '/coupons-deals/rating.html'
    }
    set_check_cookie();
    AjaxEngine.makeReplaceCall(
        'rating',
        {
            'url'        : url,
            'parameters' : {
                'cmd'     : 'vote',
                'popup'   : popup,
                'deal_id' : deal_id,
                'value'   : value
            },
            'timeout'    : 20000,
            'onLoading'  : function() { loader_show(); },
            'onComplete' : function() { loader_hide();  if (reload) { window.location.reload(true); } },
            'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
            'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
        }
   );
}

/* instead of deals.js */

function $el(id) {
    return document.getElementById(id);
}

function toggleStoreTip() {
    if($el('more_tips').style.display!='block'){
        $el('more_tips').style.display='block';
        $el('read_more').innerHTML='Read Less';
    }
    else{
        $el('more_tips').style.display='none';
        $el('read_more').innerHTML='Read More';
    } 
    return false;
} 

$(document).ready(function(){
    if($el("read_more")) 
        $el("read_more").onclick = toggleStoreTip;
});

function prepareStoreTip(){
    if($('.tips')[0].scrollHeight>90){
        $('.more').show();$('#read_more_tip').click(function(){
            if($('.tips').hasClass('expanded')) {
                $('.tips').removeClass('expanded');
                this.innerHTML='Read More'}
            else {
                $('.tips').addClass('expanded');
                this.innerHTML='Read Less'
            };
        return false;
        });
    }
}

$(document).ready(function(){
    if ($el("read_more_tip")) {
        prepareStoreTip()
    }
});


/* used from deals lists */
function v5_post_a_comment(deal_id, location) {
    id = deal_id;

    var params = {
        'cmd' : 'v5_post_comment',
        'DealID'  : deal_id
    }

    if (location) params['location'] = location;

    comment_temp  = document.getElementById('commentinput_' + id).value;
    if ( comment_temp.split(" ").join("").split("\n").join("").length >= 1 && comment_temp != 'Post your comment here') {
        params['comment'] = comment_temp;
    }
    
    vote = 0;
    for(i=1;i<=3;i++) {
        if (document.getElementById('vote_' + i + '_' + id).checked) {
            vote = document.getElementById('vote_' + i + '_' + id).value;
        } 
    }
    if (vote != 0) {
        params['vote'] = vote;
    }

    if (params['comment'] || params['vote']) {
        AjaxEngine.makeReplaceCall(
            'comments_page',
            {
                'parameters' : params,
                'url'        : '/coupons-deals/deal.html',
                'timeout'    : 10000,
                'onLoading'  : function() { loader_show(); },
                'onComplete' : function() { loader_hide(); },
                'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
                'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
            },
            undefined,
            undefined,
            500
        );
    } else {
        alert('Please enter your comment or press Cancel to abort.')
    }
}

// ===================================================================
// Author: Matt Kruse <matt@ajaxtoolbox.com>
// WWW: http://www.AjaxToolbox.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// ===================================================================
// Modified by: Radu Galesanu
// Added a few enhancements to the AjaxRequest class
// Added AjaxResponse class
// Added AjaxEngine class
// ===================================================================

/**
 * The AjaxRequest class is a wrapper for the XMLHttpRequest objects which 
 * are available in most modern browsers. It simplifies the interfaces for
 * making Ajax requests, adds commonly-used convenience methods, and makes 
 * the process of handling state changes more intuitive.
 * An object may be instantiated and used, or the Class methods may be used 
 * which internally create an AjaxRequest object.
 */
function AjaxRequest() {
	var req = new Object();
	
	// -------------------
	// Instance properties
	// -------------------

	/**
	 * Timeout period (in ms) until an async request will be aborted, and
	 * the onTimeout function will be called
	 */
	req.timeout = null;
	
	/**
	 *	Since some browsers cache GET requests via XMLHttpRequest, an
	 * additional parameter called AjaxRequestUniqueId will be added to
	 * the request URI with a unique numeric value appended so that the requested
	 * URL will not be cached.
	 */
	req.generateUniqueUrl = true;
	
	/**
	 * The url that the request will be made to, which defaults to the current 
	 * url of the window
	 */
	req.url = window.location.href;
	
	/**
	 * The method of the request, either GET (default), POST, or HEAD
	 */
	req.method = "GET";
	
	/**
	 * Whether or not the request will be asynchronous. In general, synchronous 
	 * requests should not be used so this should rarely be changed from true
	 */
	req.async = true;
	
	/**
	 * The username used to access the URL
	 */
	req.username = null;
	
	/**
	 * The password used to access the URL
	 */
	req.password = null;
	
	/**
	 * The parameters is an object holding name/value pairs which will be 
	 * added to the url for a GET request or the request content for a POST request
	 */
	req.parameters = new Object();
	
	/**
	 * The sequential index number of this request, updated internally
	 */
	req.requestIndex = AjaxRequest.numAjaxRequests++;
	
	/**
	 * Indicates whether a response has been received yet from the server
	 */
	req.responseReceived = false;
	
	/**
	 * The name of the group that this request belongs to, for activity 
	 * monitoring purposes
	 */
	req.groupName = null;
	
	/**
	 * The query string to be added to the end of a GET request, in proper 
	 * URIEncoded format
	 */
	req.queryString = "";
	
	/**
	 * After a response has been received, this will hold the text contents of 
	 * the response - even in case of error
	 */
	req.responseText = null;
	
	/**
	 * After a response has been received, this will hold the XML content
	 */
	req.responseXML = null;
	
	/**
	 * After a response has been received, this will hold the status code of 
	 * the response as returned by the server.
	 */
	req.status = null;
	
	/**
	 * After a response has been received, this will hold the text description 
	 * of the response code
	 */
	req.statusText = null;
	
	/**
	 * An internal flag to indicate whether the request has been completed
	 */
	req.completed = false;
	
	/**
	 * An internal flag to indicate whether the request has been aborted
	 */
	req.aborted = false;
	
	/**
	 * The XMLHttpRequest object used internally
	 */
	req.xmlHttpRequest = null;

	// --------------
	// Event handlers
	// --------------
	
	/**
	 * A function reference assigned will be called if the request is aborted
	 */
	req.onAbort = null; 
	
	/**
	 * If a timeout period is set, and it is reached before a response is 
	 * received, a function reference assigned to onTimeout will be called
	 */
	req.onTimeout = null; 
	
	/**
	 * A function reference assigned will be called when readyState=1
	 */
	req.onLoading = null;

	/**
	 * A function reference assigned will be called when readyState=2
	 */
	req.onLoaded = null;

	/**
	 * A function reference assigned will be called when readyState=3
	 */
	req.onInteractive = null;

	/**
	 * A function reference assigned will be called when readyState=4
	 */
	req.onComplete = null;

	/**
	 * A function reference assigned will be called after onComplete, if 
	 * the statusCode=200
	 */
	req.onSuccess = null;

	/**
	 * A function reference assigned will be called after onComplete, if 
	 * the statusCode != 200
	 */
	req.onError = null;
	
	/**
	 * If this request has a group name, this function reference will be called 
	 * and passed the group name if this is the first request in the group to 
	 * become active
	 */
	req.onGroupBegin = null;

	/**
	 * If this request has a group name, and this request is the last request 
	 * in the group to complete, this function reference will be called
	 */
	req.onGroupEnd = null;

	// Get the XMLHttpRequest object itself
	req.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
	if (req.xmlHttpRequest==null) { return null; }
	
	// -------------------------------------------------------
	// Attach the event handlers for the XMLHttpRequest object
	// -------------------------------------------------------
	req.xmlHttpRequest.onreadystatechange = 
	function() {
		if (req==null || req.xmlHttpRequest==null) { return; }
		if (req.xmlHttpRequest.readyState==1) { req.onLoadingInternal(req); }
		if (req.xmlHttpRequest.readyState==2) { req.onLoadedInternal(req); }
		if (req.xmlHttpRequest.readyState==3) { req.onInteractiveInternal(req); }
		if (req.xmlHttpRequest.readyState==4) { req.onCompleteInternal(req); }
	};
	
	// ---------------------------------------------------------------------------
	// Internal event handlers that fire, and in turn fire the user event handlers
	// ---------------------------------------------------------------------------
	// Flags to keep track if each event has been handled, in case of 
	// multiple calls (some browsers may call the onreadystatechange 
	// multiple times for the same state)
	req.onLoadingInternalHandled = false;
	req.onLoadedInternalHandled = false;
	req.onInteractiveInternalHandled = false;
	req.onCompleteInternalHandled = false;
	req.onLoadingInternal = 
		function() {
			if (req.onLoadingInternalHandled) { return; }
			AjaxRequest.numActiveAjaxRequests++;
			if (AjaxRequest.numActiveAjaxRequests==1 && typeof(window['AjaxRequestBegin'])=="function") {
				AjaxRequestBegin();
			}
			if (req.groupName!=null) {
				if (typeof(AjaxRequest.numActiveAjaxGroupRequests[req.groupName])=="undefined") {
					AjaxRequest.numActiveAjaxGroupRequests[req.groupName] = 0;
				}
				AjaxRequest.numActiveAjaxGroupRequests[req.groupName]++;
				if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==1 && typeof(req.onGroupBegin)=="function") {
					req.onGroupBegin(req.groupName);
				}
			}
			if (typeof(req.onLoading)=="function") {
				req.onLoading(req);
			}
			req.onLoadingInternalHandled = true;
		};
	req.onLoadedInternal = 
		function() {
			if (req.onLoadedInternalHandled) { return; }
			if (typeof(req.onLoaded)=="function") {
				req.onLoaded(req);
			}
			req.onLoadedInternalHandled = true;
		};
	req.onInteractiveInternal = 
		function() {
			if (req.onInteractiveInternalHandled) { return; }
			if (typeof(req.onInteractive)=="function") {
				req.onInteractive(req);
			}
			req.onInteractiveInternalHandled = true;
		};
	req.onCompleteInternal = 
		function() {
			if (req.onCompleteInternalHandled || req.aborted) { return; }
			req.onCompleteInternalHandled = true;
			AjaxRequest.numActiveAjaxRequests--;
			if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
				AjaxRequestEnd(req.groupName);
			}
			if (req.groupName!=null) {
				AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
				if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
					req.onGroupEnd(req.groupName);
				}
			}
			req.responseReceived = true;
			req.status = req.xmlHttpRequest.status;
			req.statusText = req.xmlHttpRequest.statusText;
			req.responseText = req.xmlHttpRequest.responseText;
			req.responseXML = req.xmlHttpRequest.responseXML;
			if (typeof(req.onComplete)=="function") {
				req.onComplete(req);
			}
			if (req.xmlHttpRequest.status==200) {
				if (typeof(req.onSuccess)=="function") {
					req.onSuccess(req);
				}
			}
			else if (typeof(req.onError)=="function") {
				req.onError(req);
			}

			// Clean up so IE doesn't leak memory
			delete req.xmlHttpRequest['onreadystatechange'];
			req.xmlHttpRequest = null;
			req.completed = true;
		};
	req.onCancelInternal = 
		function(handler) {
			if (req!=null && req.xmlHttpRequest!=null && !req.onCompleteInternalHandled) {
				req.aborted = true;
				req.xmlHttpRequest.abort();
				AjaxRequest.numActiveAjaxRequests--;
				if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
					AjaxRequestEnd(req.groupName);
				}
				if (req.groupName!=null) {
					AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
					if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
						req.onGroupEnd(req.groupName);
					}
				}
				if (typeof(handler)=="function") {
					handler(req);
				}
			// Opera won't fire onreadystatechange after abort, but other browsers do. 
			// So we can't rely on the onreadystate function getting called. Clean up here!
			delete req.xmlHttpRequest['onreadystatechange'];
			req.xmlHttpRequest = null;
			}
		};
	req.onAbortInternal = 
		function() {
			req.onCancelInternal(req.onAbort);
		};
	req.onTimeoutInternal = 
		function() {
			req.onCancelInternal(req.onTimeout);
		};

	// ----------------
	// Instance methods
	// ----------------
	/**
	 * The process method is called to actually make the request. It builds the
	 * querystring for GET requests (the content for POST requests), sets the
	 * appropriate headers if necessary, and calls the 
	 * XMLHttpRequest.send() method
	*/
	req.process = 
		function() {
			if (req.xmlHttpRequest!=null) {
				// Some logic to get the real request URL
				if (req.generateUniqueUrl && req.method=="GET") {
					req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex;
				}
				var content = null; // For POST requests, to hold query string
				for (var i in req.parameters) {
					if (req.queryString.length>0) { req.queryString += "&"; }
					req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]);
				}
				if (req.method=="GET") {
					if (req.queryString.length>0) {
						req.url += ((req.url.indexOf("?")>-1)?"&":"?") + req.queryString;
					}
				}
				req.xmlHttpRequest.open(req.method,req.url,req.async,req.username,req.password);
				if (req.method=="POST") {
					if (typeof(req.xmlHttpRequest.setRequestHeader)!="undefined") {
						req.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
					}
					content = req.queryString;
				}
				if (req.timeout>0) {
					setTimeout(req.onTimeoutInternal,req.timeout);
				}
				req.xmlHttpRequest.send(content);
                                if (!req.async) {
					req.onCompleteInternal(req);
                                }
			}
		};

	/**
	 * An internal function to handle an Object argument, which may contain
	 * either AjaxRequest field values or parameter name/values
	 */
	req.handleArguments = 
		function(args, params) {
			if (typeof(args)!="undefined" && args!=null) {
				for (var i in args) {
					req[i] = args[i];
				}
			}
			if (typeof(params)!="undefined" && params!=null) {
				req.parameters = params;
			}
		};

	/**
	 * Returns the results of XMLHttpRequest.getAllResponseHeaders().
	 * Only available after a response has been returned
	 */
	req.getAllResponseHeaders =
		function() {
			if (req.xmlHttpRequest!=null) {
				if (req.responseReceived) {
					return req.xmlHttpRequest.getAllResponseHeaders();
				}
				alert("Cannot getAllResponseHeaders because a response has not yet been received");
			}
		};

	/**
	 * Returns the the value of a response header as returned by 
	 * XMLHttpRequest,getResponseHeader().
	 * Only available after a response has been returned
	 */
	req.getResponseHeader =
		function(headerName) {
			if (req.xmlHttpRequest!=null) {
				if (req.responseReceived) {
					return req.xmlHttpRequest.getResponseHeader(headerName);
				}
				alert("Cannot getResponseHeader because a response has not yet been received");
			}
		};

	return req;
}

// ---------------------------------------
// Static methods of the AjaxRequest class
// ---------------------------------------

/**
 * Returns an XMLHttpRequest object, either as a core object or an ActiveX 
 * implementation. If an object cannot be instantiated, it will return null;
 */
AjaxRequest.getXmlHttpRequest = function() {
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		// Based on http://jibbering.com/2002/4/httprequest.html
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				return new ActiveXObject("Microsoft.XMLHTTP");
			} catch (E) {
				return null;
			}
		}
		@end @*/
	}
	else {
		return null;
	}
};

/**
 * See if any request is active in the background
 */
AjaxRequest.isActive = function() {
	return (AjaxRequest.numActiveAjaxRequests>0);
};

/**
 * Make a GET request. Pass an object containing request arguments and an
 * object containing the request parameters.
 */
AjaxRequest.get = function(args,params) {
	return AjaxRequest.doRequest("GET",args,params);
};

/**
 * Make a POST request. Pass an object containing request arguments and an
 * object containing the request parameters.
 */
AjaxRequest.post = function(args,params) {
	return AjaxRequest.doRequest("POST",args,params);
};

/**
 * The internal method used by the .get() and .post() methods
 */
AjaxRequest.doRequest = function(method,args,params) {
	if (typeof(args)!="undefined" && args!=null ||
            typeof(params)!="undefined" && params!=null) {
		var myRequest = new AjaxRequest();
		myRequest.method = method;
		myRequest.handleArguments(args,params);
		myRequest.process();
                return myRequest;
	}
	return null;
}	;

/**
 * Abort a request. Pass the object returned by the AjaxRequest.get  and
 * AjaxRequest.post calls.
 */
AjaxRequest.abort = function(req) {
	req.onAbortInternal();
};

/**
 * Submit a form. The requested URL will be the form's ACTION, and the request 
 * method will be the form's METHOD.
 * Returns true if the submittal was handled successfully, else false so it 
 * can easily be used with an onSubmit event for a form, and fallback to 
 * submitting the form normally.
 */
AjaxRequest.submit = function(theform, args, params) {
	var myRequest = new AjaxRequest();
	if (myRequest==null) { return false; }
	var serializedForm = AjaxRequest.serializeForm(theform);
	myRequest.method = theform.method.toUpperCase();
	myRequest.url = theform.action;
	myRequest.handleArguments(args,params);
	myRequest.queryString = serializedForm;
	myRequest.process();
	return true;
};

/**
 * Serialize a form into a format which can be sent as a GET string or a POST 
 * content.It correctly ignores disabled fields, maintains order of the fields 
 * as in the elements[] array. The 'file' input type is not supported, as 
 * its content is not available to javascript. This method is used internally
 * by the submit class method.
 */
AjaxRequest.serializeForm = function(theform) {
	var els = theform.elements;
	var len = els.length;
	var queryString = "";
	this.addField = 
		function(name,value) { 
			if (queryString.length>0) { 
				queryString += "&";
			}
			queryString += encodeURIComponent(name) + "=" + encodeURIComponent(value);
		};
	for (var i=0; i<len; i++) {
		var el = els[i];
		if (!el.disabled) {
			switch(el.type) {
				case 'text': case 'password': case 'hidden': case 'textarea': 
					this.addField(el.name,el.value);
					break;
				case 'select-one':
					if (el.selectedIndex>=0) {
						this.addField(el.name,el.options[el.selectedIndex].value);
					}
					break;
				case 'select-multiple':
					for (var j=0; j<el.options.length; j++) {
						if (el.options[j].selected) {
							this.addField(el.name,el.options[j].value);
						}
					}
					break;
				case 'checkbox': case 'radio':
					if (el.checked) {
						this.addField(el.name,el.value);
					}
					break;
			}
		}
	}
	return queryString;
};

// -----------------------
// Static Class variables
// -----------------------

/**
 * The number of total AjaxRequest objects currently active and running
 */
AjaxRequest.numActiveAjaxRequests = 0;

/**
 * An object holding the number of active requests for each group
 */
AjaxRequest.numActiveAjaxGroupRequests = new Object();

/**
 * The total number of AjaxRequest objects instantiated
 */
AjaxRequest.numAjaxRequests = 0;


/******************************************************\
                 A J A X  R E S P O N S E
\******************************************************/

/**
 * The AjaxResponse class is a convenient way to dispatch AJAX XML responses
 * It dispatches the following XML response format:
 *   <Response>
 *     <RequestStatus>
 *       <Error>0/1</Error>
 *       <Code>code<Code>
 *       <Message>message</Message>
 *     <RequestStatus>
 *     <Instructions>
 *       <Instruction index="index">
 *         <Command>command</Command>
 *         [additional instruction tags]
 *       </Instruction>
 *       [ ... more Instruction tags ... ]
 *     </Instructions>
 *   </Response>
 *
 * The Instruction commands and their additional tags are thus:
 *
 *   <Command>setInnerHTML</Command> - updates innerHTML for given objects
 *   <Object id="id">innerHTML</Object>
 *   [ ... Object tags ... ]
 *
 *   <Command>setCSSAttributes</Command> - sets CSS attribute values
 *   <Attribute object_id="id" name="name">value</Attribute>
 *   [ ... more Attribute objects ... ]
 *
 *   <Command>setCookie</Command> - rets or clears one cookie
 *   <CookieName>name</CookieName>
 *   <CookieValue>value</CookieValue>
 *   <CookiePath>path</CookiePath>
 *
 *   <Command>addScript</Command> - appends a script
 *   <Script>source</Script>
 *
 *   <Command>evalScript</Command> - runs a script without appending it
 *   <Script>source</Script>
 *
 *   <Command>setInputValues</Command> - sets form input values
 *   <Form>form</Form>
 *   <Input name="name">value</Input>
 *   [ ... more Input tags ... ]
 *
 *   <Command>setData</Command> - sets key values in a hash that will be passed
 *                                to the onSuccess handler
 *   <Variable name="name">value</Variable>
 *   [ ... more Variable tags ... ]
 */

var AjaxResponse = {};

/**
 * A convenient way to specify default handlers for the handleResponse method.
 * NOTE: These handlers are NOT the handlers for the AjaxRequest call.
 * The handleResponse method is called when a successful AjaxRequest call is
 * made, as part of its onSuccess handler.
 * The possible handlers are:
 *   - onFatalError: called when an unexpected error occurs while parsing the
 *                   XML response
 *   - onError: called when the Error is set to 1 in the RequestStatus section
 *   - onInfo : called when the Error is set to 0 and the Message is not empty
 *   - onSuccess: called when Error is 0; passes the response DOM object and
 *                the hash built from the setData commands
 */
AjaxResponse.defaultHandlers = {
  'onFatalError' : function(msg) {alert(msg);},
  'onError' : function(msg) {alert(msg);},
  'onInfo' : function(msg) {alert(msg);},
  'onSuccess' : function(response, data) {}
};

AjaxResponse.setDefaultHandler = function(type, handler) {
  AjaxResponse.defaultHandlers[type] = handler;
}

/**
 * A few DOM parsing shortcuts
 */
AjaxResponse.getNodes = function(o, n) {
  try {
    return o.getElementsByTagName(n);
  } catch(e) {}
};

AjaxResponse.getNode = function(o, n) {
  return AjaxResponse.getNodes(o, n)[0];
};

AjaxResponse.getValue = function(o) {
  try {
    return o.firstChild.nodeValue;
  } catch(e) {
    return '';
  }
}

AjaxResponse.getNodeValue = function(o, n) {
  return AjaxResponse.getValue(AjaxResponse.getNode(o, n));
};

/*
 * A common error handler
 */
AjaxResponse.handleResponseError = function(handler, message, code) {
  if (!message.length) {
    message = 'A fatal error has occurred while processing your request';
  }
  if (code.length) {
    handler(message + ' ' + code + '.');
  } else {
    handler(message);
  }
};

/*
 * The XML Instruction interpreter (the AjaxResponse core)
 */
AjaxResponse.handleInstruction = function(node, response_data) {
  var command = AjaxResponse.getNodeValue(node, 'Command');
  switch (command) {
    case 'setInnerHTML':
      var object = AjaxResponse.getNodes(node, 'Object');
      for (var i=0; i<object.length; i++) {
        var id = object[i].getAttribute('id');
        var inner_html = AjaxResponse.getValue(object[i]);
        if (document.getElementById(id).innerHTML != inner_html) {
          document.getElementById(id).innerHTML = inner_html;
        }
      }
      break;
      
    case 'setCSSAttributes':
      var attribute = AjaxResponse.getNodes(node, 'Attribute');
      for (var i=0; i<attribute.length; i++) {
        var object_id = attribute[i].getAttribute('object_id');
        var name = attribute[i].getAttribute('name');
        var value = AjaxResponse.getValue(attribute[i]);
        document.getElementById(object_id).style[name] = value;
      }
      break;
      
    case 'setCookie':
      var cookie_name = AjaxResponse.getNodeValue(node, 'CookieName');
      var cookie_value = AjaxResponse.getNodeValue(node, 'CookieValue');
      var cookie_path = AjaxResponse.getNodeValue(node, 'CookiePath');
      if (cookie_name.length != 0 || cookie_value.length != 0) {
        var cookie = cookie_name + "=" + escape(cookie_value);
        if (cookie_path.length) {
          cookie += '; path=' + cookie_path;
        }
        // TODO: add all cookie params here
        document.cookie = cookie;
      }
      break;
      
    case 'addScript':
      var script = AjaxResponse.getNodeValue(node, 'Script');
      var head = AjaxResponse.getNode(document, 'head');
      var s = document.createElement('script');
      head.appendChild(s);
      s.text = script;
      break;
      
    case 'evalScript':
      var script = AjaxResponse.getNodeValue(node, 'Script');
      eval(script);
      break;
      
    case 'setInputValues':
      var form = AjaxResponse.getNodeValue(node, 'Form');
      var input = AjaxResponse.getNodes(node, 'Input');
      for (var i=0; i<input.length; i++) {
        var name = input[i].getAttribute('name');
        var value = AjaxResponse.getValue(input[i]);
        document.forms[form].elements[name].value = value;
      }
      break;

    case 'setTitle':
      var title = AjaxResponse.getNodeValue(node, 'Title');
      document.title = title;
      break;
      
    case 'setData':
      var variable = AjaxResponse.getNodes(node, 'Variable');
      for (var i=0; i<variable.length; i++) {
        var name = variable[i].getAttribute('name');
        var value = AjaxResponse.getValue(variable[i]);
        response_data[name] = value;
      }
      break;
  }
};

/*
 * The handler that should be called from an AjaxRequest onSubmit handler
 * Arguments:
 *   - the AjaxRequest object
 *   - the AjaxRequest arguments
 *   - the ajaxResponse handlers
 */
AjaxResponse.handleResponse = function(req, args, phandlers) {
  var handlers = {};
  for (var i in AjaxResponse.defaultHandlers) {
    if (typeof(phandlers) != 'undefined' && typeof(phandlers[i]) != 'undefined') {
      handlers[i] = phandlers[i];
    } else {
      handlers[i] = AjaxResponse.defaultHandlers[i];
    }
  }
  var response_data = {};

  var code = '';
  var message = '';

  try {
    if (req.responseXML.normalize) {
      req.responseXML.normalize();
    }
    var response = AjaxResponse.getNode(req.responseXML, 'Response');
    var request_status = AjaxResponse.getNode(response, 'RequestStatus');
    var error = AjaxResponse.getNodeValue(request_status, 'Error');
    try {
      message = AjaxResponse.getNodeValue(request_status, 'Message');
      code = AjaxResponse.getNodeValue(request_status, 'Code');
    } catch (e) {}
    if (error == '1') {
      AjaxResponse.handleResponseError(handlers.onError, message, code);
      return false;
    }

    var instructions = AjaxResponse.getNodes(response, 'Instructions');
    if (instructions.length) {
      var instruction = AjaxResponse.getNodes(instructions[0], 'Instruction');
      for (var i=0; i<instruction.length; i++) {
        var node = instruction[i];
        if (node.getAttribute('index') - 0 != i) {
          throw "Error";
        }
        AjaxResponse.handleInstruction(node, response_data);
      }
    }

    if (error == '0') {
      handlers.onSuccess(response, response_data);
      if (message.length) {
        handlers.onInfo(message);
      }
    }

    return false;
  } catch (e) {
    if (!message.length) {
      if (typeof(e.message) != 'undefined') {
        message = e.message;
        if (typeof(e.line) != 'undefined') {
          message += '[' + e.line + ']';
        }
        message += '<hr>';
      }
      message += req.responseText.substring(0, 200);
    }
    AjaxResponse.handleResponseError(handlers.onFatalError, message, code);
    return true;
  }
};


/******************************************************\
                  A J A X   E N G I N E
\******************************************************/

/**
 * The AjaxEngine class is a collection of static methods used for grouping
 * multiple AjaxRequest calls. It addresses the issue of double/multiple
 * clicks that should, or should not, generate multiple server calls and
 * other common issues such as priorities, throttling etc.
 * In addition to AjaxRequest's arguments and parameters, AjaxEngine defines
 * an id for a type of request, a set of default handlers that can be set for
 * the AjaxRequest calls and an additional set of handlers to be passes _TO_
 * the default handlers.
 */
var AjaxEngine = {};

/**
 * Default handlers are a convenient way to set AjaxRequest common handlers
 * (such as onError, onGroupBegin and onGroupEnd handlers) one time and use
 * them for each call automatically. Default handlers will be called with the
 * following arguments: the XMLHttpRequest object, the AjaxRequest arguments
 * and the additional handlers given at call time.
 */
AjaxEngine.defaultHandlers = {};

/**
 * Method for setting a default handler (AjaxRequest argument)
 */
AjaxEngine.setDefaultHandler = function(type, handler) {
  AjaxEngine.defaultHandlers[type] = handler;
};

/**
 * Object to store the request info
 */
AjaxEngine.callsPool = {};

/*
 * Method to clear the info for a request type
 */
AjaxEngine.initializeCallInfo = function(id) {
  AjaxEngine.callsPool[id] = {
    'timeout' : null,
    'request' : null,
    'args' : null,
    'params' : null,
    'handlers' : null
  }
};

/*
 * The call to AjaxRequest
 */
AjaxEngine.makeAjaxRequestCall = function(id) {
  AjaxEngine.callsPool[id].request = 
    AjaxRequest.post(AjaxEngine.callsPool[id].args, AjaxEngine.callsPool[id].params);
};

/*
 * Closure to set a handler
 */
AjaxEngine.storeDefaultHandler = function(i, id) {
  return function(req) {
    AjaxEngine.defaultHandlers[i](
      req,
      AjaxEngine.callsPool[id].args,
      AjaxEngine.callsPool[id].handlers
    );
  }
}

/*
 * The general call method. An AjaxEngine call receives the following arguments:
 *   - id: method type, or group (should be the same for calls originating
 *     from the same event)
 *   - args: arguments to pass to the AjasRequest object
 *   - params: XMLHttpRequest query parameters
 *   - handlers: handlers to be passed to the default handlers
 *   - timeout: delay for the call (used for throttling, with "REPLACE" calls)
 */
AjaxEngine.makeCall = function(id, args, params, handlers, timeout) {
  AjaxEngine.initializeCallInfo(id);
  AjaxEngine.callsPool[id].args = args;
  for (var i in AjaxEngine.defaultHandlers) {
    if (typeof(AjaxEngine.callsPool[id].args[i] != 'function')) {
      AjaxEngine.callsPool[id].args[i] = AjaxEngine.storeDefaultHandler(i, id);
    }
  }
  AjaxEngine.callsPool[id].params = params;
  AjaxEngine.callsPool[id].handlers = handlers;
  if (typeof(timeout) != 'undefined' && parseInt(timeout) > 0) {
    AjaxEngine.callsPool[id].timeout = setTimeout('AjaxEngine.makeAjaxRequestCall("' + id + '")', timeout);
  } else {
    AjaxEngine.makeAjaxRequestCall(id);
  }
}


/*
 * "INSERT" calls are one-time only calls. While an "INSERT" call is active,
 * all subsequent request of the same type will be ignored. Typically,
 * this is used for double submit protection.
 * Thumb rule: use "INSERT" calls when you INSERT data in your system
 * based on user's input (signups, post comments, upload files).
 */
AjaxEngine.makeInsertCall = function(id, args, params, handlers, timeout) {
  if (typeof(AjaxEngine.callsPool[id]) != 'undefined' &&
      AjaxEngine.callsPool[id].req != null &&
      !AjaxEngine.callsPool[id].req.completed &&
      !AjaxEngine.callsPool[id].req.aborted) {
    // Another request in progress, ignore current request
    return;
  }
  AjaxEngine.makeCall(id, args, params, handlers, timeout);
}

/*
 * "REPLACE" calls are made every time they are issued. If a request
 * of the same type is in progress it will be cancelled. Typically, this
 * is used for stateful calls such as autocompletion, login etc.
 * Thumb rule: use "REPLACE" calls when you FETCH data from your system
 * based on the user's input (content updates) OR when you UPDATE content
 * in your system based on user's input (edit).
 */
AjaxEngine.makeReplaceCall = function(id, args, params, handlers, timeout) {
  AjaxEngine.cancelPendingCall(id);
  AjaxEngine.makeCall(id, args, params, handlers, timeout);
};

/*
 * Cancels a pending call
 */
AjaxEngine.cancelPendingCall = function(id) {
  if (typeof(AjaxEngine.callsPool[id]) != 'undefined') {
    if (AjaxEngine.callsPool[id].timeout != null) {
      clearTimeout(AjaxEngine.callsPool[id].timeout);
    }
    if (AjaxEngine.callsPool[id].request != null) {
      AjaxRequest.abort(AjaxEngine.callsPool[id].request);
    }
  }
  AjaxEngine.initializeCallInfo(id);
};

/******************************************************\
                  B I N D I N G S
\******************************************************/

/**
 * A convenient binding between AjaxRequest's onSuccess handler, AjaxResponse's handleResponse
 * method and AjaxResponse's handlers
 */
                  
AjaxEngine.setDefaultHandler('onSuccess', function(req, args, handlers) {AjaxResponse.handleResponse(req, args, handlers);});

/*** searchbox.js */

function trim(str_v)
{
    if (typeof(str_v) == "undefined" || null == str_v) {
        return str_v;
    }

    str_v = str_v.replace(/^\s+/, "");
    str_v = str_v.replace(/\s+$/, "");

    return str_v;
}


function cleanup_keywords(kwd, double_escape)
{
    if (typeof(kwd) == "undefined") {
        return "";
    }

    kwd = trim(kwd);
    kwd = kwd.replace(/ /g, "+");
    if (double_escape) {
        kwd = kwd.replace(/\//g, "%2F");
        kwd = kwd.replace(/#/g, "%23");
        kwd = kwd.replace(/&/g, "%26");
    }
    kwd = escape(kwd);

    return kwd;
}


function https_on() {
    return (window.location.toString().search(/^https:\/\//) == 0);
}

var default_search_text = '';
var dealio_sections = "{'products':1, 'coupons-deals':1}";

function _placeholder(event, element) {
    if (typeof(default_search_text) == 'undefined') {
        return;
    }
    if (event.type == 'focus' && element.value == default_search_text) {
        element.value = '';
    } else if (event.type == 'blur' && element.value == '') {
        element.value = default_search_text;
    }
}


function get_form_url(section, kwd)
{
    var url = "";
    if (typeof(section) == "undefined") {
        return url;
    }

    if (typeof(kwd) == "undefined") {
        kwd = "";
    } else {
        kwd = trim(kwd);
    }

    if (kwd == "") {
        url = dealio_urls["base"] + section;
        if (section == "products") {
            url += ".html";
        } else {
            url += "/top/30days/";
        }
    } else if (typeof(dealio_sections[section]) != "undefined") {
        kwd = cleanup_keywords(kwd, true);
        url = dealio_urls["base"] + "search/" + section + "/" + kwd + "/";
    } else if (section == "yahoo") {
        url = dealio_urls[section];
    }
    else {
        kwd = cleanup_keywords(kwd, true);
        url = "/search/" + section + "/" + kwd + "/";
    }

    return url;
}


function get_form(name) {
    return document.getElementById(name);
}


function searchbox_form_submit(name, command, url) {
    var form = get_form(name);
    if (typeof(form) == 'undefined') {
        return;
    }
    form.cmd.value = command;
    if (url && "" != url) {
        form.action = url;
    }

    form.submit();
}


//If user hits back after a search maintain color
function keepColor() {
    t = document.getElementById('searchBox');
    if (typeof(default_search_text) == 'undefined' || typeof(t) == "undefined") {
        return;
    }
    if(t.value != default_search_text){
        t.className = '';
    }
}


function search_submit(from_button, search_section)
{
    if (typeof(default_search_text) == 'undefined') {
        return false;
    }

    var search_section_obj = document.getElementById("search_section");
    var sbox = document.getElementById("searchBox");

    if (!search_section_obj || !sbox) {
        return false;
    }

    var cmd;
    if (sbox.value == default_search_text) {
        sbox.value = "";
        cmd = "browse";
    } else {
        cmd = "search";
    }

    //by default search in products, unless user selects another tab
    if (search_section) {
        search_section_obj.value = search_section;
    }

    var form_url = '';
    if (https_on()) {
        form_url = dealio_urls['redirect']+'?location=' + escape(get_form_url(search_section_obj.value, sbox.value));
    } else {
        form_url = get_form_url(search_section_obj.value, sbox.value); 
    }


    searchbox_form_submit('search_form', cmd, form_url);

    return false;
}


function search_submit_bottom(from_button, search_section)
{
    var sbox = document.getElementById("searchBox");
    var sbox_bottom = document.getElementById("searchBox_bottom");

    if (!sbox || !sbox_bottom) {
        return false;
    }
    
    sbox.value = sbox_bottom.value;

    if (typeof(search_section) == "undefined") {
        search_section = false;
    }

    search_submit(from_button, search_section);

    return false;
}


function _adjust_button(event, search_section)
{
    if (event && event.keyCode == 13) {
        if (typeof(search_section) == "undefined") {
            search_section = false;
        }
        search_submit(false, search_section);
    }

    return false;
}


function adjust_button_bottom(event, search_section)
{
    if (event && event.keyCode == 13) {
        if (typeof(search_section) == "undefined") {
            search_section = false;
        }
        search_submit_bottom(false, search_section);
    }

    return false;
}

/*** flyouts.js */

$(document).ready(function(){
    if ($('#browseStoresTab')) {
        $('#browseStoresTab').hover(
        function(){
            if (document.getElementById('browseStoresFlyout').innerHTML < 1) {
                loadStoresFlyout();
            }
            $('#browseStoresFlyoutTab').show();
            $('#browseStoresFlyout').fadeIn('fast');
        }, 
        function(){
            $('#browseStoresFlyoutTab').hide();
            $('#browseStoresFlyout').hide();
        }
        );
    }
});


/*** ratings.js */

function vote_request(url, deal_id, value, reload, popup) {
    if (url == '') {
        url = '/coupons-deals/rating.html'
    }
    set_check_cookie();
    AjaxEngine.makeReplaceCall(
        'rating',
        {
            'url'        : url,
            'parameters' : {
                'cmd'     : 'vote',
                'popup'   : popup,
                'deal_id' : deal_id,
                'value'   : value
            },
            'timeout'    : 20000,
            'onLoading'  : function() { loader_show(); },
            'onComplete' : function() { loader_hide();  if (reload) { window.location.reload(true); } },
            'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
            'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
        }
    );
}

function v5_vote_request(deal_id, value, reload) {
    url = '/coupons-deals/rating.html';
    
    set_check_cookie();
    AjaxEngine.makeReplaceCall(
        'rating',
        {
            'url'        : url,
            'parameters' : {
                'cmd'     : 'v5_vote',
                'popup'   : 0,
                'deal_id' : deal_id,
                'value'   : value
            },
            'timeout'    : 20000,
            'onLoading'  : function() { loader_show(); },
            'onComplete' : function() { loader_hide();  if (reload) { window.location.reload(true); } },
            'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
            'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
        }
    );
}



function loader_show() {
    document.getElementById("feedbackBox").style.display = 'none';
    document.getElementById('ajax').style.display = 'block';
}


function loader_hide() {
    document.getElementById('ajax').style.display = 'none';
}


function vote_hover(choice, event, element) {
		if (event.type == 'mouseover')  {
			if (choice == 'positive'){
    	        element.style.backgroundPosition = '0px -178px';
   			}
			else if (choice == 'negative'){
				element.style.backgroundPosition = '0px -218px';
			}
		}
    	else if (event.type == 'mouseout') {
		    if (choice == 'positive'){
        	 if (element.className.indexOf('selected') == -1) {
                element.style.backgroundPosition = '0px -158px';
        	 }
			}
			else{
            if (element.className.indexOf('selected') == -1) {
                element.style.backgroundPosition = '0px -198px';
            }
			  
			}
    	}
}


function vote_lightbox(deal_id) {
    var div_lightbox = document.getElementById('slug');
    if ((div_lightbox == null) || (typeof(div_lightbox) == 'undefined')) {
        return;
    }

    var li_container = document.getElementById('vote_' + deal_id).parentNode;
    var div_summary  = li_container.getElementsByTagName('div')[5];
    var pos_summary  = find_pos(div_summary);
    div_lightbox.style.display = 'block';
}


function find_pos(element) {
    var offset_left = 0;
    var offset_top  = 0;
    if (element.offsetParent) {
        offset_left = element.offsetLeft;
        offset_top  = element.offsetTop;
        while (element = element.offsetParent) {
            offset_left += element.offsetLeft;
            offset_top  += element.offsetTop;
        }
    }
    return [offset_left, offset_top];
}
$().ready(function(){
  toggleMore();
  $('.dealComments').each(function(i,obj){if(obj.innerHTML==''){obj.style.display='none'}})
  if($('#item_popup').length > 0){
     open_item_popup();
  }
});


function open_item_popup(){
    $.blockUI( { 
        message: $('#item_popup'),
        overlayCSS: { backgroundColor:'#999',cursor:'default'},
        css: {
            width: '600px',
            left: '20%',
            textAlign: 'left',
            padding: '5px 5px 5px 20px',
            border: 'none',
            '-moz-border-radius': '5px',
            '-webkit-border-radius': '5px',
            top: '10%',
            cursor: 'default'
            }
        }
    );
    $('.blockOverlay').attr('title','Click to unblock').click(
        function(){
            $.unblockUI();
            popup_close();
        }
    );
    $('#close_item_popup').click(
        function(){
            $.unblockUI();
            popup_close();
        }
    );
}

function popup_close() {
    var ids = ['vote', 'comments', 'openLink'];
    for (i in ids) {
        src  = document.getElementById(ids[i] + '_popup_' + popup_item_id);
        dest = document.getElementById(ids[i] + '_' + popup_item_id);

        if(dest) dest.innerHTML = src.innerHTML;
    }
}

function get_form(name) {
    return document.getElementById(name);
}


function validate_field(type, value, error_message, min_length) {
    value = trim_string(value);

    switch (type) {
        case 'email':
            if (!is_valid_email(value)) {
                message += error_message;
            }
            break;

        case 'string':
            if ((min_length > 0) && (value.length < min_length)) {
                message += error_message;
            }
            break;
    }
}


function form_submit(name, command) {
    var form = get_form(name);
    if (typeof(form) == 'undefined') return;

    form.cmd.value = command;
    form.submit();
}

function enableSubmit(id){
  if($('#commentinput_'+id).val()!=''){
    $('#input_button_'+id).attr('disabled',false);
  }else{
    $('#input_button_'+id).attr('disabled',true);
  }
  }
function enableSubmitRadio(e,id){
    $('#input_button_'+id).attr('disabled','');
  if($(e).val()!=0){
    $('#input_button_'+id).attr('disabled',false);
  }else{
    if($('#commentinput_'+id).val()==''){
    $('#input_button_'+id).attr('disabled',true);
    }
  }
}
function command_handler(section, name, command) {
    if (validate_form(section)) {
        form_submit(name, command);
    }
}

function reposition_all() {
  clip.hide();
  for (i in cclip) {
    cclip[i].hide()
  }
}

function closeInline(id){
	$('#openLink_'+id).show();
    $('#closeLink_'+id).hide();
	$('#dealExpand_'+id).slideUp();
	$('#couponDesc_'+id).slideDown();
    //reposition_all();
	return false;
}


function openInline(id){
    $('#closeLink_'+id).show();
    $('#openLink_'+id).hide();
    $('#couponDesc_'+id).slideUp();
	$('#dealExpand_'+id).slideDown();
    //reposition_all();
	return false;
}


function toggleMoreById(id){
	if(document.getElementById('toggleMore_'+id).style.display=='block'){
	   $('#couponDesc_'+id).removeClass('suppress');
	   $('#dealExpand_'+id).removeClass('suppress');
	   $('#toggleMore_'+id).hide();
	   return false;
	}else{
	   $('#couponDesc_'+id).addClass('suppress');
	   $('#dealExpand_'+id).addClass('suppress');
	   $('#toggleMore_'+id).show();
	   return false;
	}
}

function toggleMore(){
	var t;
    $('div.moreArrow').hover(
	   function(){
	   	$('div.toggleMore').hide(0);
	   	clearTimeout(t);
        $('.couponDesc').addClass('suppress');
        $('.dlink').addClass('suppress');
        $('.dealExpand').addClass('suppress');
        $('#'+this.id+' div.toggleMore').show(0);
    },
	function(){
		var id=this.id;
        t = setTimeout(function(){
			$('.couponDesc').removeClass('suppress');
            $('.dlink').removeClass('suppress');
            $('.dealExpand').removeClass('suppress');
            $('#'+id+' div.toggleMore').hide(0);
		},250);
    });
  }

function rate_deal_by_id(rate_type, rate_value, deal_id, url) {
    if ((rate_type != 2) && (rate_type != 3)) {
        alert('Operation not allowed');
        return;
    }

    toggleMoreById(deal_id);

    AjaxEngine.makeReplaceCall(
        'comments_page',
        {
            'url' : url,
            'parameters' : {
                'cmd'         : 'rate_deal',
                'location'    : 1,
                'rate_type'   : rate_type,
                'rate_value'  : rate_value,
                'DealID'      : deal_id
            },
            'timeout'    : 20000,
            'onLoading'  : function() { loader_show(); },
            'onComplete' : function() { loader_hide(); },
            'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
            'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
        },
        undefined,
        undefined,
        500
    );

}

function getFullDescription(deal_id, version, url) {
    if (document.getElementById('IsFullDesc_' + deal_id).innerHTML != '') {
        return 
    }
    
    AjaxEngine.makeReplaceCall(
        'deal_details',
        {
            'url' : url,
            'parameters' : {
                'cmd'       : 'get_details',
                'location'  : 1,
                'Version'   : version,
                'DealID'    : deal_id
            },
            'timeout'    : 20000,
            'onLoading'  : function() { loader_show(); },
            'onComplete' : function() { loader_hide(); },
            'onError'    : function() { loader_hide(); alert('An error has occured. Please try again.'); },
            'onTimeout'  : function() { loader_hide(); alert('The request has timed out. Please try again.'); }
        },
        undefined,
        undefined,
        500
    );
}


function create_cookie(name, value, days) {
    var expires = "";
    if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days * 24 * 60 * 60 * 1000));
        expires = "; expires="+date.toGMTString();
    }
    document.cookie = name + "=" + value + expires +"; path=/";
}

function get_cookie(c_name) {
  if (document.cookie.length>0) {
      c_start=document.cookie.indexOf(c_name + "=");
      if (c_start!=-1) {
          c_start=c_start + c_name.length+1; 
          c_end=document.cookie.indexOf(";",c_start);
          if (c_end==-1) c_end=document.cookie.length;
          return unescape(document.cookie.substring(c_start,c_end));
      } 
  }
  return "";
}

function set_check_cookie()
{
    create_cookie("c_check_cookie", "check_cookie", 1024);
}


