﻿
/// <reference name="MicrosoftAjax.js"/>
/// <reference name="vnetLinkedlist.js"/>

// Copyright (c) Ralph Varjabedian http://varjabedian.net
//
// WebServiceAjaxCallsHelper.js v 1.0.9
//
// A file containing a manager for WebService Ajax calls, offering several benefits
//      1. Limit the number of active calls at once (queuing manager)
//      2. Multiple calls to the same webservice are ignored (latest/first call considered, other ignored) as long as the call is not done
//      3. Ability to provide common implementation for success/failure ajax calls
//      4. Ajax calls through multiple subdomains to increase performance, break out of the 2 connections per domain limitation
//
// July 2008
// 
// Tested on: IE7, FF 2.0, Opera 9.5, Win Safari 3
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/******* Switches *******/
var WSAjaxCallsClass_ProxyScriptLocation = "/scripts/WebServiceAjaxCallsHelperIframe.aspx";
var WSAjaxCallsClass_CallsPerIFrame = 1; // IE has problems with 2 per iframe, which it should not. sticking to 1 for now
/******* Switches *******/

WSAjaxCallsClass = new Object();

WSAjaxCallsClass.Initialize = function(commonSuccessFunction, commonFailureFunction, randomizingSubdomain, maxCalls, ignoreMultipleCalls, ignoreMultipleCallsFavorLast)
{
/// <summary>Creates an instance of the class WSAjaxCallsClass. You can only create one instance.</summary>
/// <param name="commonSuccessFunction">A common success function to callback</param>
/// <param name="commonFailureFunction">A common failure function to callback</param>
/// <param name="randomizingSubdomain">A randomizing subdomain if you want to use multiple subdomains, else pass null</param>
/// <param name="ignoreMultipleCalls">If the class should ignore multiple calls to the same function before the call is done</param>
/// <param name="ignoreMultipleCallsFavorLast">If the previous parameter is false, then you choose how to ignore the multiple calls. Favor the last call or the first call</param>

    if (this.Initialize.arguments.length != 6)        
        throw "You must pass to WSAjaxCallsClass 6 arguments";
        
    if (typeof(this.initialized) != "undefined")
        throw "Already initialized";

    this.initialized = true;        
        
    this.IsWSAjaxCallsClass = true;
    this.commonSuccessFunction = commonSuccessFunction;
    this.commonFailureFunction = commonFailureFunction;
    this.randomizingSubdomain = randomizingSubdomain;
    this.ignoreMultipleCalls = ignoreMultipleCalls;
    this.ignoreMultipleCallsFavorLast = ignoreMultipleCallsFavorLast;
    this.AllIFramesReady = false;        
    
    this.activeCallsNow = 0;
    this.maximumActiveCalls = maxCalls;        
    
    this.linkedList = new LinkedListClass();

    this.original_Sys_Net_WebRequestManager_executeRequest = Sys$Net$XMLHttpExecutor$executeRequest;
    Sys$Net$XMLHttpExecutor$executeRequest = Sys.Net.XMLHttpExecutor.prototype.executeRequest = this.Sys_Net_WebRequestManager_executeRequest;
    
    this.original_Sys_Net_WebServiceProxy_invoke = Sys.Net.WebServiceProxy.invoke;
    Sys$Net$WebServiceProxy$invoke = Sys.Net.WebServiceProxy.invoke = this.Sys_Net_WebServiceProxy_invoke;

    this.SetDocumentDomain();
    
    if (this.randomizingSubdomain != null)
        this.PreCreateIFrames();
}

//WSAjaxCallsClass.trace = function(text) { }
WSAjaxCallsClass.trace = function(text) { Sys.Debug.trace(text); }

WSAjaxCallsClass.Sys_Net_WebRequestManager_executeRequest = function()
{
    var This = this;
    var url = this._webRequest._url;
    var space = url.indexOf(" ");
    if (space != -1)
    {
        var token = parseInt(url.substring(0, space));
        this._webRequest._url = WSAjaxCallsClass.randomizingSubdomain.replace("?", token+1) + url.substring(space + 1);
        return WSAjaxCallsClass.DelegateCallToIframe.call(WSAjaxCallsClass, token, This);
    }
    return WSAjaxCallsClass.original_Sys_Net_WebRequestManager_executeRequest.call(This);
}

WSAjaxCallsClass.Sys_Net_WebServiceProxy_invoke = function(servicePath, methodName, useGet, params, onSuccess, onFailure, userContext, timeout)
{
    WSAjaxCallsClass.QueueCall.apply(WSAjaxCallsClass, WSAjaxCallsClass.Sys_Net_WebServiceProxy_invoke.arguments);
}

WSAjaxCallsClass.SetDocumentDomain = function()
{
    document.initialDomain = document.domain;
    
    var x = window.location.href.toLowerCase().replace("http://", "").replace("https://", "");
    var a = x.indexOf("/");
    if (a != -1)
        x = x.substring(0, a);
    a = x.indexOf(":");
    if (a != -1)
        x = x.substring(0, a);
    a = x.lastIndexOf(".");
    if (a != -1)
    {
        a = x.lastIndexOf(".", a - 1);
        if (a != -1)
            x = x.substring(a + 1);
    }

    document.domain = x;
}

WSAjaxCallsClass.QueueCall = function()
{
    WSAjaxCallsClass.trace("WSAjaxCallsClass.QueueCall: queuing call for WS:[" + this.QueueCall.arguments[0] + "] method: [" + this.QueueCall.arguments[1] + "]");
    this.AddToQueue(this.QueueCall.arguments);
    this.SignalQueue();
}

WSAjaxCallsClass.signalQueueTimeoutID = 0;

WSAjaxCallsClass.SignalQueue = function()
{
    var thisObject = this;
    clearTimeout(this.signalQueueTimeoutID);
    this.signalQueueTimeoutID = setTimeout(function() { thisObject._SignalQueue(); }, 0);
}

WSAjaxCallsClass.AddToQueue = function(args)
{
    if (this.ignoreMultipleCalls && this.linkedList.HasNodes())
    {
        var traverse = this.linkedList.GetListStart();
        while (traverse)
        {
            if (traverse.object[0] == args[0] && traverse.object[1] == args[1]) // servicePath and methodName
            {
                WSAjaxCallsClass.trace("WSAjaxCallsClass.AddToQueue replaced function " + args[1].toString());
                if (this.ignoreMultipleCallsFavorLast)
                    traverse.object = args;
                return;
            }
            traverse = traverse.next;        
        }
    }
    WSAjaxCallsClass.trace("WSAjaxCallsClass.AddToQueue added function " + args[1].toString());
    this.linkedList.Add(args);
}

WSAjaxCallsClass._SignalQueueActive = false;

WSAjaxCallsClass._SignalQueue = function()
{
    if (this._SignalQueueActive)
        return;
    this._SignalQueueActive = true;
        
    while (this.CanDoCallNow() && this.HasWaitingCalls())
    {
        var node = this.linkedList.GetListStart();
        if (this.CallNow.apply(this, node.object))
            node.Remove();
    }
    
    this._SignalQueueActive = false;
}

WSAjaxCallsClass.HasWaitingCalls = function()
{
    return this.linkedList.HasNodes();
}

WSAjaxCallsClass.CanDoCallNow = function()
{
    return (this.activeCallsNow < this.maximumActiveCalls);
}

WSAjaxCallsClass.PreCreateIFrames = function()
{
    if (typeof(WSAjaxCallsClass.IframeCollection) == "undefined")
        WSAjaxCallsClass.IframeCollection = new Object();
    var i;            
    for (i = 1; i <= Math.ceil(WSAjaxCallsClass.maximumActiveCalls/WSAjaxCallsClass_CallsPerIFrame); i++)
    {
        var domain = this.randomizingSubdomain.replace("?", i);
        WSAjaxCallsClass.CreateIFrame(domain);
    }
}

WSAjaxCallsClass.CreateIFrame = function(domain)
{
    if (WSAjaxCallsClass.IframeCollection[domain] == null)
    {
        var subframe = document.createElement("iframe");

        var atr = document.createAttribute('height');
        atr.value = "0";
        subframe.setAttributeNode(atr);
        
        atr = document.createAttribute('width');
        atr.value = "0";
        subframe.setAttributeNode(atr);
        
        try
        {
            atr = document.createAttribute('style');
            atr.value = "display:none; visibility:hidden;";
            subframe.setAttributeNode(atr);
        } catch (e) {}
        
        try { subframe.setAttribute("style", "display:none; visibility:hidden;"); }
        catch (e) {}

        subframe.setAttribute("width", 0);
        subframe.setAttribute("height", 0);
        
        document.body.appendChild(subframe);
        subframe.src = domain + WSAjaxCallsClass_ProxyScriptLocation;
        if (!WSAjaxCallsClass.IframeCollection.count)
            WSAjaxCallsClass.IframeCollection.count = 0;
        WSAjaxCallsClass.IframeCollection[WSAjaxCallsClass.IframeCollection.count++] = 
            WSAjaxCallsClass.IframeCollection[domain] = 
                { subframe : subframe, window : subframe.contentWindow, isBusyCount : 0, isReady : false } ;
    }
}

WSAjaxCallsClass.OneIFrameIsReady = function(iframeWindow)
{
    var i;
    for (i = 0; i < WSAjaxCallsClass.IframeCollection.count; i++)
    {
        if (WSAjaxCallsClass.IframeCollection[i].window == iframeWindow)
        {
            WSAjaxCallsClass.IframeCollection[i].isReady = true;
            WSAjaxCallsClass.trace("WSAjaxCallsClass.OneIFrameIsReady: new iframe is ready src: " + WSAjaxCallsClass.IframeCollection[i].subframe.src);
            break;
        }
    }
    this.SignalQueue();
    var allReady = Math.ceil(WSAjaxCallsClass.maximumActiveCalls/WSAjaxCallsClass_CallsPerIFrame) == WSAjaxCallsClass.IframeCollection.count;
    if (allReady)
    {
        for (i = 0; i < WSAjaxCallsClass.IframeCollection.count; i++)
        {
            if (WSAjaxCallsClass.IframeCollection[i].isReady == false)
            {
                allReady = false;
                break;
            }
        }
    }
    if (allReady)
    {
        WSAjaxCallsClass.AllIFramesReady = true;
        WSAjaxCallsClass.trace("WSAjaxCallsClass.OneIFrameIsReady: All iframes is ready");
        if (WSAjaxCallsClass.AllIFramesAreReady)
            WSAjaxCallsClass.AllIFramesAreReady();
    }
    if (WSAjaxCallsClass.NewIFrameReady)
        WSAjaxCallsClass.NewIFrameReady(allReady);
}

WSAjaxCallsClass.IframeTriggerMethod = 0;

WSAjaxCallsClass.DelegateCallToIframe = function(token, executeThis)
{
    if (typeof(this.IframeCollection) == "undefined")
        throw "Iframes not created!";
    
    if (this.IframeCollection[token] == null)
        throw "Iframe for token does not exist";

    WSAjaxCallsClass.trace("WSAjaxCallsClass.DelegateCallToIframe: delegating call to [" + 
        executeThis._webRequest._url + "] by using token " + token);

    var iframe = this.IframeCollection[token];
    var x = function() { iframe.window.docall(executeThis); }
    try
    {
        if (WSAjaxCallsClass.IframeTriggerMethod == 0)
            x(); // opera gives access violation here.
        else
            iframe.window.setTimeout(x, 0);
    }
    catch (e)
    {
        WSAjaxCallsClass.trace("WSAjaxCallsClass.DelegateCallToIframe: error calling x(), switching to second method");
        WSAjaxCallsClass.IframeTriggerMethod = 1;
        iframe.window.setTimeout(x, 0);
    }    
}

WSAjaxCallsClass.CallNow = function(servicePath, methodName, useGet, params, onSuccess, onFailure, userContext, timeout)
{
    this.activeCallsNow++;
    
    var token = -1;
    
    WSAjaxCallsClass.trace("WSAjaxCallsClass.CallNow: servicePath: [" + servicePath + "] methodName: [" + methodName + "]");
    if (this.randomizingSubdomain)
    {
        var weHaveFree = false;
        for (token = 0; token < WSAjaxCallsClass.IframeCollection.count; token++)
        {
            if (WSAjaxCallsClass.IframeCollection[token].isReady == false)
                continue;
            if (WSAjaxCallsClass.IframeCollection[token].isBusyCount < WSAjaxCallsClass_CallsPerIFrame)
            {
                weHaveFree = true;
                break;
            }
        }
        if (!weHaveFree)
            return false;
        
        WSAjaxCallsClass.IframeCollection[token].isBusyCount++; // reserve now
        servicePath = token + " " + servicePath; // a hack for passing the token so that the invoke can know to delegate this
        WSAjaxCallsClass.trace("WSAjaxCallsClass.CallNow: delegating call to servicePath: [" + servicePath + "] token: [" + token + "]");
    }

    if (this.BeforeAjaxCallStarts != null)
        this.BeforeAjaxCallStarts(servicePath, methodName, params, userContext);
    
    var newContext = { userContext : userContext, onSuccess : onSuccess, onFailure : onFailure, servicePath : servicePath, params : params, token : token };
    
    this.original_Sys_Net_WebServiceProxy_invoke.call(Sys.Net.WebServiceProxy, 
        servicePath, methodName, useGet, params, WSAjaxCallsClass.WebserviceCallSuccess, WSAjaxCallsClass.WebserviceCallFailed, newContext, timeout);
    
    return true;
}

WSAjaxCallsClass.WebserviceCallSuccess = function(result, context, methodName)
{
    WSAjaxCallsClass.trace("WSAjaxCallsClass.WebserviceCallSuccess: result [" + result + "] methodName: [" + methodName + "]");
    WSAjaxCallsClass.activeCallsNow--;
    if (context.token != -1)
        WSAjaxCallsClass.IframeCollection[context.token].isBusyCount--;
    WSAjaxCallsClass.SignalQueue();
    
    if (WSAjaxCallsClass.AfterAjaxCallEnds != null)
        WSAjaxCallsClass.AfterAjaxCallEnds(context.servicePath, methodName, context.params, context.userContext, true, result);

    if (WSAjaxCallsClass.commonSuccessFunction != null)
    {
        if (!WSAjaxCallsClass.commonSuccessFunction(result, context.userContext, methodName))
            return;
    }    
    context.onSuccess(result, context.userContext, methodName);
}

WSAjaxCallsClass.WebserviceCallFailed = function(result, context, methodName)
{
    WSAjaxCallsClass.trace("WSAjaxCallsClass.WebserviceCallFailed: result [" + result + "] methodName: [" + methodName + "]");
    WSAjaxCallsClass.activeCallsNow--;
    if (context.token != -1)
        WSAjaxCallsClass.IframeCollection[context.token].isBusyCount--;
    WSAjaxCallsClass.SignalQueue();
    
    if (WSAjaxCallsClass.AfterAjaxCallEnds != null)
        WSAjaxCallsClass.AfterAjaxCallEnds(context.servicePath, methodName, context.params, context.userContext, false, result);

    if (WSAjaxCallsClass.commonFailureFunction != null)
    {
        if (!WSAjaxCallsClass.commonFailureFunction(result, context.userContext, methodName))
            return;
    }    
    context.onFailure(result, context.userContext, methodName);
}

WSAjaxCallsClass.GetRandomizingSubdomainFromDocument = function()
{
/// <summary>A helper function to return a randomizing subdomain string. It uses the location of the current document. If the top level domain is http://www.example.com then the returned string would be http://?.example.com </summary>
    var x = window.location.href.toLowerCase();
    x = x.replace("www.", "");
    var a = x.indexOf("://");
    a += 3;
    x = x.substring(0, a) + "?." + x.substring(a, x.indexOf("/", a));
    return x;
}

