All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.smartclient.debug.public.sc.client.communications.Comm.js Maven / Gradle / Ivy

The newest version!
/*
 * Isomorphic SmartClient
 * Version SC_SNAPSHOT-2011-08-08 (2011-08-08)
 * Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
 * "SmartClient" is a trademark of Isomorphic Software, Inc.
 *
 * [email protected]
 *
 * http://smartclient.com/license
 */

 






//>	@class Comm
// Provide background communication with an HTTP server
//<

// create the Comm singleton object
isc.ClassFactory.defineClass("Comm");

// add properties to the Comm object
isc.Comm.addClassProperties( {

    //>	@classAttr	isc.Comm.sendMethod (string : isc.Comm.sendMethod : I)
	//			communication method
	//		@group	communication
	//<
	sendMethod:	"POST",

	//>	@type	SendMethod
	//			@visibility external
	//			@group	communication
	// @value	"GET"     GET method (regular URL encoding)		
	// @value	"POST"    POST method (form field encoding)
	//<

	// Sequence number for forms created in IE to send data to the server.  Used to give each
    // form a unique name.
	_generatedFormNumber : 0

});


isc.Comm.addClassMethods({

// XMLHttpRequest vs IFrame Comm
//
// Advantages:
// - doesn't add to history in older Moz
// - no request for empty.html in HTTPS
// - doesn't add to document.frames and potentially screw up numbering
// - ability to detect load with a server-initiated callback
// - synchronous option
//
// Disadvantages / neutral
// - can't handle upload fields
// - is subject to concurrent requests limit in IE (test by adding Thread.sleep(5000) to
//   IDACall - responses come in closely-spaced pairs)
// - no persistent connections in IE (server push), see:
//    http://jszen.blogspot.com/2005/03/xmlhttp-and-readystate.html
// - no compression in IE 5.5
//
// XMLHttpRequest behavior with file:// URL - works on FF, Safari, and IE but on all these
// platforms the URL must be relative or it doesn't work.  SO e.g. ../foo.html is ok, but
// /foo.html is not.  Haven't tested specifying the full path (e.g. c:\foo\bar.html).  
//
// For XMLHttpRequest, FF uses the baseURL of the window object that started thread that
// eventually calls XMLHttpRequest.send() as the baseURL for resolving the relative reference.
// This means that if you send an RPC using XMLHttpRequest out of the eval area of the
// Developer Console, your base URL will be /isomorphic/system/helpers - because that's where
// Log.html came from.  You can use a timeout to restart the thread in the appropriate context
// - just be aware of this oddity.

_fireXMLCallback : function (request, callback) {
    

    isc.EH._setThread("XRP");

    // NOTE: last param tells fireCalllback to trap errors in Moz
    isc.Class.fireCallback(callback, "xmlHttpRequest", [request], null, true);

    isc.EH._clearThread();
},

// ==========================================================================================
// IMPORTANT: If you update the XMLHttp code here, also update FileLoader.js
// ==========================================================================================
_getStateChangeHandler : function () {
    return function () {
        var request = arguments.callee.request;
 
        // IE 5.5 manages to call this handler after readyState changes to 4 and we process the
        // request, which would cause the whole RPC reply logic to run more than once which
        // breaks badly.  If we run more than once, the request will be null becase we null it out
        // below, so just trap this case and return.
        if (!request) return;
        
        if (request.readyState != 4) return;

        arguments.callee.request = null;

        // Bizarre IE-only bug: in the middle of a readyStateChange thread, we draw a
        // new widget, and on touching the newly created handle in
        // Canvas._browserDoneDrawing(), IE decides it's a good time to interrupt the current
        // thread and fire readyStateChange from another xmlHttpRequest that has arrived in the
        // meantime - this can actually be seen in the stack trace, which shows this handler
        // being called (impossibly) by _browserDoneDrawing().  
        // So far as we know, IE is not willing to similarly interrupt a timer thread, so we
        // fire the callback on a 0ms timer instead, which should be order-preserving.
        // NOTE: do not use a closure here or a leak will be introduced
        //isc.Comm._fireXMLCallback(request, arguments.callee.callback);
        isc.Timer.setTimeout({ target:isc.Comm, methodName:"_fireXMLCallback",
                               args:[request, arguments.callee.callback] }, 0);
    }
},


// Upon shipping XML3.0, Microsoft started installing XML parsers in "side by side mode"
// meaning the previous version is left intact and a new version is installed parallel to
// the existing one.  Prior to this the latest XML parser being installed by some piece of
// software would overwrite the previous verison and potentially break the software that
// depended on that version.  The catch is that to get the later versions of the parser, you
// need to specify a version-specific prefix.
//
// We use a version independent prefix to get the 2.0 parser in IE6.0 and IE5.5 and then
// fall back on version-specific prefixes if that fails for some reason.  But we expect
// MSXML2.XMLHTTP or Microsoft.XMLHTTP to actually work.
//
// MSXML2 prefix specifies the version independent control.  The "Microsoft" and "MSXML"
// prefixes are old-style version independent controls.  MSXML3 is version specific 3.0 parser.
//                     IE6+               IE5.5               pre-IE5.5 style  IE6+
xmlHttpConstructors : ["MSXML2.XMLHTTP", "Microsoft.XMLHTTP", "MSXML.XMLHTTP", "MSXML3.XMLHTTP"],

// ==========================================================================================
// IMPORTANT: If you update this function, also update its copy in FileLoader.js
// ==========================================================================================
createXMLHttpRequest : function () {
    
    if (isc.Browser.isIE) {

        var xmlHttpRequest;

        // We prefer the  ActiveX version of XMLHttpRequest if it's available because IE7's
        // native implementation has some quirks - for example it doesn't allow requests to
        // file:// URLs no matter what overrides you set in IE's options panel.  Also there
        // are scattered reports of the native implementation being less performant.
        if (this.preferNativeXMLHttpRequest) {
            xmlHttpRequest = this.getNativeRequest();
            if (!xmlHttpRequest) xmlHttpRequest = this.getActiveXRequest();
        } else {
            xmlHttpRequest = this.getActiveXRequest();        
            if (!xmlHttpRequest) xmlHttpRequest = this.getNativeRequest();
        }
   
        if (!xmlHttpRequest) isc.rpc.logWarn("Couldn't create XMLHttpRequest");
        return xmlHttpRequest;
    } else {
        // Moz, Safari
        return new XMLHttpRequest();
    }
},

getNativeRequest : function () {
   var xmlHttpRequest;
    if (isc.Browser.version >= 7) {
        isc.rpc.logDebug("Using native XMLHttpRequest");
        xmlHttpRequest = new XMLHttpRequest();
    }
    return xmlHttpRequest;
},

getActiveXRequest : function () {
    var xmlHttpRequest;

    if (!this._xmlHttpConstructor) {
        for (var i = 0; i < this.xmlHttpConstructors.length; i++) {
            try {
                var cons = this.xmlHttpConstructors[i];
                xmlHttpRequest = new ActiveXObject(cons);
                // cache selected constructor
                if (xmlHttpRequest) {
                    this._xmlHttpConstructor = cons;
                    break;
                }
            } catch (e) { }
        }
    } else {
        xmlHttpRequest = new ActiveXObject(this._xmlHttpConstructor);    
    }

    if (xmlHttpRequest) isc.rpc.logDebug("Using ActiveX XMLHttpRequest via constructor: " + this._xmlHttpConstructor);
    return xmlHttpRequest;
},

// _transactionCallbacks - array of callbacks for outstanding transactions
// fired from the various 'reply' methods
_transactionCallbacks:[],

// scriptInclude transport
// ---------------------------------------------------------------------------------------

sendScriptInclude : function (request) {
    var URL = request.URL,
        fields = request.fields,
        data = request.data,
        callbackParam = request.callbackParam,
        transaction = request.transaction
    ;
    
    // create a function for server-generated code to call, and capture the
    // transactionNum by having the function share this scope
    var cbName = "_scriptIncludeReply_"+transaction.transactionNum;
    this[cbName] = function () {
        // copy arguments to a real array
        var values = arguments.length == 1 ? arguments[0] : [];
        if (arguments.length > 1) {
            for (var i = 0; i < arguments.length; i++) values[i] = arguments[i];
        }
        isc.Comm.performScriptIncludeReply(transaction.transactionNum, values);
    }
    var callback = "isc.Comm."+cbName;

    var callbackParamObj = {};
    callbackParamObj[callbackParam ? callbackParam : "callback"] = callback;
    URL = isc.rpc.addParamsToURL(URL, fields);
    URL = isc.rpc.addParamsToURL(URL, callbackParamObj);
    if (transaction) transaction.mergedActionURL = URL;

    isc.rpc.logInfo("scriptInclude call to: " + URL);
    
    // store the transactionCallback to fire when the server returns
    this._transactionCallbacks[transaction.transactionNum] = transaction.callback;

    
    var document = this.getDocument(),
        body = this.getDocumentBody(),
        scriptElement = document.createElement("script");
    scriptElement.src = URL;
    body.appendChild(scriptElement);
},


performScriptIncludeReply : function (transactionNum, values) {
    // destroy auto-generated function for this transaction
    delete this["_scriptIncludeReply_"+transactionNum];
    var callback = this._transactionCallbacks[transactionNum];
    delete this._transactionCallbacks[transactionNum];
    
    this.logDebug("scriptInclude reply for transactionNum: " + transactionNum +
                  ", data: " + this.echoLeaf(values), "xmlBinding");

    this.fireCallback(callback, "transactionNum,results,wd", [transactionNum, values]);
},


// send via an xmlHttpRequest
sendXmlHttpRequest : function (request) {

    var URL = request.URL,
        fields = request.fields,
        httpMethod = request.httpMethod,
        contentType = request.contentType,
        headers = request.httpHeaders,
        data = request.data,
        transaction = request.transaction,
        blocking = request.blocking != null ? request.blocking : false
    ;


    
    
    this._transactionCallbacks[transaction.transactionNum] = transaction.callback;
    
    
    // set up a callback to notify us when the request completes
    var callback = "isc.Comm.performXmlTransactionReply(" +transaction.transactionNum+
                    ", xmlHttpRequest)";
                    
    if (!httpMethod) httpMethod = "POST";
    var xmlHttpRequest = this.createXMLHttpRequest();

    var loadFunc;
    if (isc.Browser.isIE) {
        
        loadFunc = this._getStateChangeHandler();
        loadFunc.request = xmlHttpRequest;
        loadFunc.callback = callback;

    } else {
        // we'll install this function to fire onreadystatechange
        loadFunc = function () {
        
        if (xmlHttpRequest.readyState != 4) return;
        isc.Comm._fireXMLCallback(xmlHttpRequest, callback);
        }   
    }
    xmlHttpRequest.onreadystatechange = loadFunc;

    if (isc.rpc.logIsDebugEnabled()) {
        this.lastXmlHttpRequest = xmlHttpRequest; // HACK for debugging
    }

    if (httpMethod == "POST" || httpMethod == "PUT") {
        // if data was passed in, use that as the body and encode any fields into the query
        // params
        if (data) {
            // assume the body being posted is XML if contentType is unset
            contentType = contentType || "text/xml";             
            URL = isc.rpc.addParamsToURL(URL, fields);
        } else {
            // send fields like a form post
            contentType = contentType || "application/x-www-form-urlencoded; charset=UTF-8";
            data = isc.SB.create();
            var first = true;
            for (var fieldName in fields) {
                if (!first) data.append("&");
                var value = fields[fieldName];
                data.append(isc.rpc.encodeParameter(fieldName, value));
                first = false;
            }
            data = data.toString();                        
        }
        if (isc.rpc.logIsDebugEnabled()) {
            isc.rpc.logDebug("XMLHttpRequest POST to " + URL + " contentType: " + contentType 
                             + " with body -->"+decodeURIComponent(data)+"<--");
        }
        xmlHttpRequest.open(httpMethod, URL, !blocking);
        xmlHttpRequest.setRequestHeader("Content-Type", contentType);
        this._setHttpHeaders(xmlHttpRequest, headers);
        if (transaction) {
            transaction.xhrHeaders = headers;
            transaction.xhrData = data;
        }
        if (data != null && !isc.isA.String(data)) {
            this.logWarn("Non-string data object passed to sendXML as request.data:"+ this.echo(data) +
                        " attempting to convert to a string.");
            data = data.toString ? data.toString() : "" + data;
        }
        xmlHttpRequest.send(data);
    } else {  // httpMethod == GET, DELETE, HEAD
        var urlWithFields = isc.rpc.addParamsToURL(URL, fields);
        xmlHttpRequest.open(httpMethod, urlWithFields, !blocking);

        // If bypassCache is set, use if-modified-since header to prevent cacheing of
        // XMLHttp GET responses.
        if (request.bypassCache) {
              
            xmlHttpRequest.setRequestHeader("If-Modified-Since", "Thu, 01 Jan 1970 00:00:00 GMT");
        }

        this._setHttpHeaders(xmlHttpRequest, headers);

        if (isc.rpc.logIsDebugEnabled()) {
            isc.rpc.logDebug("XMLHttpRequest GET from " + URL + 
                             " with fields: " + isc.Log.echoAll(fields) + 
                             " full URL string: " + urlWithFields);
        }
        
        xmlHttpRequest.send(null);
    }
    return xmlHttpRequest;
},




performXmlTransactionReply : function (transactionNum, xmlHttpRequest) {
    //!OBFUSCATEOK
    var callback = this._transactionCallbacks[transactionNum]
    delete this._transactionCallbacks[transactionNum];
    this.fireCallback(callback, "transactionNum,results,wd", [transactionNum, xmlHttpRequest]);
},

_setHttpHeaders : function (request, headers) {
    if (headers == null) return;
    for (var headerName in headers) {
        var headerValue = headers[headerName];
        if (headerValue != null) request.setRequestHeader(headerName, headerValue);
    }
}

});





© 2015 - 2024 Weber Informatics LLC | Privacy Policy