So far it's not too painful. All that's left is the client-side JavaScript to handle the process. Of course, this is where all the work is done. (Well...there is server-side code, but it is not covered. You can use any scripting language to return the HTML described on the Remote Data Format page previously.)

We want to make this method look identical to using the xmlHttpRequest object. This way code can be written later that attempts to use the XHR object and transparently falls back to using an iframe when needed. The API for the RPC can be shared. To do this, the iframe is extend by adding properties and methods that match the XHR object's methods and properties plus a few others to glue it all together.

There are some extra elements in the code to work with the IE activeX XmlHttpRequest object. These are not needed for RPCs with iframes. They are there so the two methods can be combined letting the code automatically and transparently pick the method based on browser support.

Unlike the original code shown for remote scripting with and iframe, which created the iframe at the time of the RPC, this method creates the iframe at the time the page is loaded. Two functions are involved: createIframe and createExtIframe. Function createIframe, called in createExtIframe, is responsible for actually creating an iframe. The function createExtIframe is responsible for extending the the iframe once it is created to match the xmlHttpRequest object.

Properties and methods are added simply by initializing them—an advantage of object oriented languages that are prototype based over class base ones. I like to initialize all properties up front even though they can be added at any time. This way there isn't a problem with missing properties later. Here is the code that handles this.

var HTTPObj = null; //hidden iframe or xmlHttpRequest object
var HTTPtarget = null; //This global is to accommodate another method
addEvent(window, "load", createXMLHttp);
function createExtIframe() {
    /* Create Iframe if needed */
    var iframeObj = document.getElementById("rs-iframe");
    if (!iframeObj) {
        iframeObj = createIframe();
    }
    /* Now that we have an iframe extend it */
    if (iframeObj) {
        iframeObj.backbutton = false;
        iframeObj.RPCType = "iframe";
        iframeObj.targetId = "";
        iframeObj.status = 0;
        iframeObj.nexturl = "";
        iframeObj.responseText = "";
        try {
            iframeObj.readyState = 0;
        } catch (e) {
            /* IE already has this property as a
             * read only text with the values of
             * uninitialized, loading, loaded, interactive, and complete
             * so we just catch the error and go on.
             */
        }
        iframeObj.callBack = callb;
        iframeObj.open = openMethod;
        iframeObj.send = sendMethod;
        iframeObj.iwindow = null;
        /* IE has an onreadystatechange event,
         * but initializing it is not a issue
         */
        iframeObj.onreadystatechange = "";
    } else {
        iframeObj = null;
    }
    HTTPObj = iframeObj;
}//eof createExtIframe

addEvent is a user function that binds createXMLHttp to the window onload event and is discussed in the article Adding an Event Handler.

createExtIframe, is straight forward JavaScript. It calls for the iframe to be created if it doesn't exist and then adds several properties and methods by assignment. A try-catch error trap is used to handle iframes that have a readOnly readyState property (IE for example). An error is thrown when the assignment is made, which is ignored by trapping it and the code continues running. The last step assigns the extended iframe to the global variable HTTPObj.You may recognize some of the new properties and methods as xmlHttpRequest object properties and there are a couple of extras. Let's look at the them.

There is a test for the iframe because it maybe necessary to code it in the HTML page to support some browsers. Only one line is required:
<div id="iframe-div><iframe id="rs-iframe"></iframe></div>.

backbutton
This is for future enhancement to control how the history is affected.
RPCType
This is a flag that is appended to the data sent to the server so the server code can branch to format the response for an iframe or xmlHttpRequest object. This way the same server code can be used for either method. A matching property is added to the xmlHttpRequest object (except for IE where activeX objects cannot be extend).
targetId
The id of the element that will display the final results. To keep compatibility, a targetId property is added to the xmlHttpRequest object (except for IE where activeX objects cannot be extend).
readyState
This exists in IE where it will contain "complete" when the process is done. In other browsers, the call back function sets it to the number "4" indicating the process is complete. This serves no functional value, but supports compatibility with the xmlHttpRequest method, where the property does serve a function. The iframe's page calls callBack signaling the load is complete.
status
This flag lets the response handler know the download is complete and the data has been stored in responseText. In IE, iframes have the event onreadystatechange and by manipulating this flag we prevent processing until callBack runs putting the data in responseText. callBack calls the event handler.
nexturl
This is the full URL to be called
responseText
The return data as a string.
iwindow
This is a handle to the iframe's window object needed to request the url.

The following methods are added.

callBack
This is the method the iframe's page calls to set those properties that are populate by the return process and it triggers the onreadystate event.
open
Open writes the URL to nexturl, and initializes status and readyState. This way of handling the process is used for compatibity with the xmlHttpRequest object.
send
Send makes the HTTP request by writing nexturl to the iframe's window location object.
onreadystatechange
This is assigned the response handler that will process the returned data. IE iframes have this event, which is triggered during the different stages of download, but processing is blocked until it is called by callBack, which moves the returned to data into responseText.

Here is the createIframe code.

function createIframe() {
    /* Routine to create hidden iframe
     * Discovered that wrapping the iframe in
     * a div with display none works well in
     * most, if not all, common browsers.
     *
     * Note this test iframeObj.nodeType == "undefined"
     * is to trap some browsers that create a useless iframe.
     * For example Konqueror 3.2 and earlier.
     */
    var iframeObj = null;
    try {
        //Create Iframe and put it at bottom of page
        var tmpFrame;
        //IE 6.0 fix using wrapper div with display none
        var tmpDiv = document.createElement("div");
        tmpDiv.setAttribute("id", "iframe-div");
        tmpFrame = document.createElement("iframe");
        tmpFrame.setAttribute("id", "rs-iframe");
        tmpFrame.setAttribute("name", "rs-iframe");
        if (typeof document.body.getAttribute("className") == 'string')
            tmpFrame.setAttribute("className", "hidden-frame");
        else
            tmpFrame.setAttribute("class", "hidden-frame");
        tmpDiv.appendChild(tmpFrame);
        document.body.appendChild(tmpDiv);
        if (typeof document.frames != "undefined") {
            /* Required for IE 5 on Mac and throws error on IE 5.0 PC
             * which we need to branch to a different process.
             */
            iframeObj = document.frames["rs-iframe"];
        }
        if (!iframeObj || typeof iframeObj.nodeType == "undefined") {
           /* Most browsers yield null or good iframe reference,
            * but some return an object with no properties so nodeType test.
            */
            iframeObj = document.getElementById("rs-iframe");
        }
    }
    catch (e) {
       /* Hack to handle IE 5.0 on PC, which doesn't want to 'automate'
        * adding an iframe.
        *
        * Reguired:
        * 1. HTML string for iframe and NOT an element object.
        * 2. use iframe src attribute to load first RPC; otherwise only subsequent calls work
        * 3. A block container to hold iframe
        * 4. Attach container and iframe to any block inside body but NOT the body.
        */
        /* Put iframe in container (innerHTML must be used)
         * and append to any div in page (this may exist from above)
         */
        var iframeHTML='<iframe id="rs-iframe" name="rs-iframe" class="hidden-frame"><\/iframe>';
        if (!document.getElementById("iframe-div")) {
            tmpDiv = document.createElement("div");
            tmpDiv.setAttribute("id", "iframe-div");
            tmpDiv.innerHTML = iframeHTML;
            document.getElementsByTagName('DIV')[0].appendChild(tmpDiv);
        } else {
            document.getElementById("iframe-div").innerHTML = iframeHTML;
        }
        iframeObj = document.getElementById("rs-iframe");
    }
    /* How did we do? Return if alternate page should load */
    if (iframeObj && typeof iframeObj.nodeType == "undefined") {
        iframeObj = null;
    }
    return iframeObj;
}//eof createIframe

The in-code comments seem sufficient to explain the steps in creating the iframe. So there is no need to say anymore. Now we should take a look at the code for the methods added to the iframe.

function callb(txt) {
    try {
        this.readyState = 4;
    } catch (e) {
        /* IE already has this property as a
         * read only text with the values of
         * uninitialized, loading, loaded, interactive, and complete
         * so we just catch the error and go on.
         */
    }
    this.status = 200;
    this.responseText = txt;
    this.onreadystatechange();
}//eof callb
function openMethod(method, url, asyc) {
    /* The method argument is ignored,
     * but is here to match the prototype
     * of the open method of the
     * xmlHttpRequest object
     */
    this.nexturl = url;
    this.status = 0;
    try {
        this.readyState = 0;
    } catch (e) {
        /* IE already has this property as a
         * read only text with the values of
         * uninitialized, loading, loaded, interactive, and complete
         * so we just catch the error and go on.
         */
    }
}//eof openMethod
function getWindowHandle(iframeObj) {
    var iframeWin = null;
    /* Get handle to iframe window */
    if (iframeObj.contentWindow) { // IE5.5+, Mozilla, NN7
        iframeWin = iframeObj.contentWindow;
    }
    else if (iframeObj.contentDocument) { // NN6, Konqueror
        iframeWin = iframeObj.contentDocument.defaultView;
    }
    else if (iframeObj.Document) { // IE5
        iframeWin = iframeObj.Document.parentWindow;
    }
    return iframeWin;
} //eof get WindowHandle
function sendMethod() {
    this.iwindow = getWindowHandle(this);
    if (this.iwindow) {
        if (this.backButton) {
            // This puts the url in history and
            // preserves the back button
            this.iwindow.location = this.nexturl;
        } else {
            //This replaces the history entry "breaking" the back button.
            this.iwindow.location.replace(this.nexturl);
        }
    }
}//eof sendMethod

Notice that callb, which is assigned to callBack, populates readyState and status with defaults for a finished and successful HTTP request. The openMethod, which is assigned to open, is used to reset them at the beginning of each RPC. The returned string data is stored in responseText and then onreadystatechange is called. This looks just like the xmlHttpRequest object to the response handler, which is assigned to onreadystatechange. Onreadystatechange is an iframe event in some versions of IE. Consequently, the handler will be triggered early. It should ignore calls until status is 200 when the responseText variable has been set.

The send method uses the replace method of the iframe's window location object accessed through the iwindow property added to the iframe. Using the replace method keeps all but one url out of the history; thus, the back button won't step through every transaction.

IE 5.0 requires that the iframe's window reference be regenerated everytime a new page is loaded into the iframe. Otherwise, it will crashes on subsequent RPCs. For other browsers iwindow can be set in createExIframe when all the other properties are initialized.

From here on, the JavaScript is the same script used for RPC with the xmlHttpRequest object. We have the onclick event handler, do_rpc, a stock onreadystatechange event handler, and a couple of utility functions for formating the URL.

function do_rpc(url, target_elem, data, handler) {
    /* 1. test if the browser has the necessary
     *    features for the RPC and to handle the response data.
     * 2. Do any preprocessing for the URL
     *    e.g. assembling a query string from form fields
     * 3. Make call to server
     * 4. return if alternate page in form action
     *    or link's href should load
     */
    var doDefault = true;
    if (HTTPObj) {
        /* Store ID of block receiving results.
         * This try-catch code block isn't needed for
         * RPCs using iframes. It is here because we
         * want to be able to use this with the xmlHttpRequest
         * method where it is needed for IEs activeX.
         */
        try {
            HTTPObj.targetId  = target_elem;
        } catch (e) {
            HTTPtarget = target_elem;
        }
        var formattedURL = formatURL(url, data); //Needed for IE later
        HTTPObj.open("GET", formattedURL, true);
        HTTPObj.onreadystatechange = handler;
        HTTPObj.send(null);
        doDefault = false;
    }
    return doDefault;
}//eof
function handleResponse() {
    if (HTTPObj.readyState == 4 || HTTPObj.readyState == "complete" ) {
        if (HTTPObj.status == 200) {
            var id = (HTTPObj.targetId) ? HTTPObj.targetId : HTTPTarget;
            var targ = document.getElementById(id);
            targ.innerHTML = HTTPObj.responseText;
        } else if (HTTPObj.readyState != "complete") {
            //handle error for use with xmlHttpRequest
        }
    }
} //eof handleResponse
function getRPCType() {
    /* This is so the server side
     * code can support different RPC
     * methods from different platforms
     * We tell it what type of RPC is
     * being made.
     */
    var RPCType = null;
    if (HTTPObj.RPCType) {
        RPCType = HTTPObj.RPCType;
    } else {
        /* For IE Active X */
        RPCType = "ajaxg";
    }
    return "type=" + RPCType;
}//eof getRPCType
function formatURL(url, data) {
    /* Assemble query string and append to url
     *
     * If no HTTObj.type then this is being used
     * with IEs ActiveX xmlHttpRequest object.
     * Not and issue when just using iframes.
     * Type indicates to the server code
     * if iframe or XMLHttpRequest. The server code
     * is the same for both processes.
     */
    var type = getRPCType();
    if (data) {
        switch (typeof data) {
        case "object":
            //Get string from form like method get
            if (data.tagName.toLowerCase() == "form") {
                url += form2query(data);
            }
            url += "&" + type;
            break;
        case "string":
            //formated string supplied
            if (!/^[?]/.test(data) ) {
                data = '?' + data;
            }
            url += data + "&" + type;
            break;
        default:
            url += "?" + type; //use what is passed with url
        }
    } else {
        url += "?" + type;
    }
    return url;
}//eof formatURL
function form2query(frm) {
    /* To string together fieldname
     * value pairs from form elements
     * with name property set.
     *
     * Format ?name=value&name=value ...
     */
    var qry = "";   //final query string
    var pair = "?"; //format one name/value pair
    var field;      //form field being processed
    for (var i = 0; i < frm.elements.length; i++) {
        field = frm.elements[i];
        if (typeof field.name != "undefined" && field.name != "") {
            switch (field.type) {
            case "select-one":
                pair += field.name + "=" + field.options[field.selectedIndex].value;
                break;
            case "radio":
            case "checkbox":
                if (field.checked) {
                    pair += field.name + "=" + encode_str(field.value);
                }
                break;
            default:
                pair += field.name + "=" + encode_str(field.value);
            }
            if (pair.length > 1) {
                /* Test in case first element
                 * is unchecked radio or checkbox
                 */
                qry += pair;
                pair = "&";
            }
        }
    }
    return qry;
}//eof form2query
function encode_str(strg){
    if (window.encodeURIComponent)
        return encodeURIComponent(strg);
    else if (window.escape)
        return escape(strg);
    else
        return strg.replace(/\s/g, "+");
}//eof encode_str

The method handleResponse that is shown is bare bones for demonstration. The examples on the following pages include versions coded to work in the examples.

Handling the RPC type and formatted url are complicated by the need to remain compatible with IE's ActiveX xmlHttpRequest object. A property cannot be added to an activeX object. The code shown walks around some pitfalls.

The last functions shouldn't require any more explaination so we are done with the JavaScript