Ajax (Code Worth Recommending Project)

Discussion in 'Javascript' started by Peter Michaux, Jan 16, 2008.

  1. There is quite a bit tentative code now in the Code Worth Recommending
    Project's repository. I think that there was some great discussion
    spawned by the gathering of this code.

    http://cljs.michaux.ca/trac/browser/trunk/src

    The last piece of code that I want to add before finalizing
    documentation and some sort of ratification is a "Ajax library". Many
    of the pieces are already in the library like form serialization.

    I have code for Ajax in a branch of the repository

    http://cljs.michaux.ca/trac/browser/branches/peterAjax/src/ajax

    and am posting the first bit of it here...

    Most JavaScript Ajax libraries are a single blob of code and when new
    features are added then everyone using the library gets the new
    features and extra bloat. I think it is possible to build and Ajax
    library as a core function that uses XHR or an image or iframe or some
    other way of initiating communication with the server.

    The following is the xhrCore function that can set the fundamental
    parts of a request (eg, headers, body, url, callback) and make a
    request. Wrappers are intended to add API sugar for the xhrCore.

    -----------------------------------------------------------

    // xhrCore('example.com/handler.php',
    // {method:'POST',
    // body:'foo=bar&asdf=qwerty',
    // onComplete:function(){alert('complete');}});

    if (typeof createXMLHttpRequest != 'undefined' &&
    (typeof isXhrSufficientForXhrCore == 'undefined' ||
    isXhrSufficientForXhrCore) &&
    String.prototype.toUpperCase) {

    var xhrCore;

    (function() {

    function empty() {}

    xhrCore = function(url, options) {
    options = options || {};

    var xhr = createXMLHttpRequest(),
    method = (options.method || 'GET').toUpperCase(),
    callback = options.onComplete,
    // does send really need null or can it be undefined?
    body = options.body || null,
    headers = options.headers || {};

    if (callback) {
    xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
    callback(xhr);
    // for IE memory leak
    xhr.onreadystatechange = empty;
    }
    };
    }

    if (method == 'GET' && body) {
    url = url + '?' + body;
    body = null;
    }

    xhr.open(method, url, true);

    if (method == "POST") {
    headers['Content-Type'] = headers['Content-Type'] ||
    'application/x-www-form-urlencoded';
    }

    for (p in headers) {
    xhr.setRequestHeader(p, headers[p]);
    }

    xhr.send(body);

    return {xhr:xhr};
    }
    })();

    }

    -----------------------------------------------------------

    Testing if a browser's xhr object is sufficient is done by the
    isXhrSufficientForXhrCore function.

    -----------------------------------------------------------

    // perhaps don't want these checks on an intranet or behind a login
    // where the browsers are known to be new.

    if (typeof createXMLHttpRequest != 'undefined') {

    var isXhrSufficientForXhrCore = (function() {

    var xhr = createXMLHttpRequest();


    // NN6.1 can create an XMLHttpRequest object but it doesn't
    // function and after creation the object's readyState
    // property is 'undefined' when it should be '0'.
    // The XHR object in NN6.1 doesn't work.
    if (xhr.readyState !== 0) {
    return false;
    }


    // IE 5.01sp2 errors when x.setRequestHeader runs.
    // The error says "object doesn't support this property"
    // This may be because xhr.open() hasn't been called.
    // yet. This is the reason for the try-catch. Since we cannot
    // determine if the xhr object has setRequestHeader then
    // we must put it down the degeradation path.
    //
    // Opera 8 XHR doesn't have setRequestHeader() and although
    // it seems to be able to make a POST request with a body
    // without this function This browser is still going to be
    // put down the degredation path. Note Opera 8.0.1 does have
    // setRequestHeader() and can POST.
    try {
    if (!xhr.setRequestHeader) {
    return false;
    }
    } catch(e) {
    return false;
    }


    // NN6.2 can't make POST requests because
    // can't have arguments to send()
    function cannotPost(xhr) {
    try {
    xhr.send('asdf');
    } catch (e) {
    // All calls to xhr.send() should error because there wasn't
    // a call to xhr.open() however the normal error is something
    // about "not initialized" as expected since xhr.open() was
    // not called. NN6.2 gives a different error indicating
    // xhr.send() cannot take arguments. Note that for NN6.2
    // in other human languages the error is still in English.

    // TODO check for toString and indexOf?
    if (-1 !== e.toString().indexOf('Could not convert' +
    // TRICKY that leading space in next string matters.
    // using line break only to make postiong to group
    possible
    ' JavaScript argument arg 0 [nsIXMLHttpRequest.send]')) {
    return true;
    }
    }
    return false;
    }

    if (cannotPost(xhr)) {
    return false;
    }


    return true;

    })();

    }

    -----------------------------------------------------------

    I'm not completely happy with how all the feature testing is packaged
    in the project so far. I'm more interested in getting correct
    algorithms right now. However if someone has a bright idea about how
    to package necessary feature tests in a library with such small
    granularity, I'd be happy to read about it.

    With the xhrCore function wrappers can be added for API sugar. Below
    is the status.js wrapper which allows the developer to specify

    -----------------------------------------------------------

    if (Function.prototype.apply &&
    String.prototype.match) {

    var statusWrapper = function(original) {

    return function(url, options) {
    options = options || {};

    var handlers = {};
    for (var p in options) {
    if (p.match(/^on(\d+|Success|Failure|Complete)/)) {
    handlers[p] = options[p];
    // do this delete because don't want inner wrappers
    // to need to work on "branches". Waste of time.
    delete options[p];
    }
    }

    options.onComplete = function(xhr) {
    var h;
    // exploit short circuting of ||
    if ((h = handlers['on'+xhr.status]) ||
    (h = handlers.onSuccess &&
    xhr.status>=200 && xhr.status<300) ||
    (h = handlers.onFailure &&
    (xhr.status<200 || xhr.status>=300)) ||
    (h = handlers.onComplete)) {
    h.apply(this, arguments);
    }
    };

    return original(url, options);
    }

    };

    }

    -----------------------------------------------------------

    With the xhrCore and the statusWrapper, a developer can use these
    building blocks to easily build an Ajax library with the API he likes.
    Here is an example...

    -----------------------------------------------------------

    // make a custom "sendAjax" function with the API
    // and no extra bloat for API features I don't use
    //
    if (typeof statusWrapper != 'undefined' &&
    typeof xhrCore != 'undefined') {
    var sendAjax = statusWrapper(xhrCore);
    }

    // Use the new sendAjax function`
    //
    function testAjaxRequest() {

    if (typeof sendAjax != 'undefined') {

    sendAjax('serverFiles/handler.php',
    {on200: function(xhr) {
    alert('on200');
    },
    onComplete:function(xhr) {
    alert('onComplete');
    }
    });
    }

    }

    -----------------------------------------------------------

    Many other wrappers and even one other core are in the branch of the
    repository I linked to at the top of this post. I'm particularly
    curious if Randy Webb's system of script insertion could be written as
    a different core. I think so. I believe that another core could be
    written for file uploads using an iframe (aka old fashioned remote
    scripting).

    I think this modular approach (some core functions and various
    optional wrappers) is exactly what is needed to avoid the hugely
    bloated Ajax libraries out there on the web.

    I look forward to reading people's thoughts.

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Jan 16, 2008
    #1
    1. Advertising

  2. Peter Michaux

    David Mark Guest

    On Jan 16, 3:11 pm, Peter Michaux <> wrote:
    > There is quite a bit tentative code now in the Code Worth Recommending
    > Project's repository. I think that there was some great discussion
    > spawned by the gathering of this code.


    I need to update a few of the tickets I posted.

    >
    > http://cljs.michaux.ca/trac/browser/trunk/src
    >
    > The last piece of code that I want to add before finalizing
    > documentation and some sort of ratification is a "Ajax library". Many
    > of the pieces are already in the library like form serialization.
    >
    > I have code for Ajax in a branch of the repository
    >
    > http://cljs.michaux.ca/trac/browser/branches/peterAjax/src/ajax
    >
    > and am posting the first bit of it here...
    >
    > Most JavaScript Ajax libraries are a single blob of code and when new
    > features are added then everyone using the library gets the new
    > features and extra bloat. I think it is possible to build and Ajax
    > library as a core function that uses XHR or an image or iframe or some
    > other way of initiating communication with the server.
    >
    > The following is the xhrCore function that can set the fundamental
    > parts of a request (eg, headers, body, url, callback) and make a
    > request. Wrappers are intended to add API sugar for the xhrCore.
    >
    > -----------------------------------------------------------
    >
    > // xhrCore('example.com/handler.php',
    > //         {method:'POST',
    > //          body:'foo=bar&asdf=qwerty',
    > //          onComplete:function(){alert('complete');}});
    >
    > if (typeof createXMLHttpRequest != 'undefined' &&
    >     (typeof isXhrSufficientForXhrCore == 'undefined' ||
    >      isXhrSufficientForXhrCore) &&
    >     String.prototype.toUpperCase) {


    That last one seems unnecessary.

    >
    >   var xhrCore;
    >
    >   (function() {
    >
    >     function empty() {}
    >
    >     xhrCore = function(url, options) {
    >       options = options || {};
    >
    >       var xhr = createXMLHttpRequest(),
    >           method = (options.method || 'GET').toUpperCase(),
    >           callback = options.onComplete,
    >           // does send really need null or can it be undefined?


    I think it needs to be null.

    >           body = options.body || null,
    >           headers = options.headers || {};
    >
    >       if (callback) {
    >         xhr.onreadystatechange = function() {
    >           if (xhr.readyState == 4) {
    >             callback(xhr);
    >             // for IE memory leak
    >             xhr.onreadystatechange = empty;
    >           }
    >         };
    >       }
    >
    >       if (method == 'GET' && body) {
    >         url = url + '?' + body;
    >         body = null;
    >       }
    >
    >       xhr.open(method, url, true);
    >
    >       if (method == "POST") {
    >         headers['Content-Type'] = headers['Content-Type'] ||
    >                              'application/x-www-form-urlencoded';
    >       }


    Add the X-Requested-With header if not specified?

    >
    >       for (p in headers) {
    >         xhr.setRequestHeader(p, headers[p]);
    >       }
    >
    >       xhr.send(body);
    >
    >       return {xhr:xhr};
    >     }
    >   })();
    >
    > }
    >
    > -----------------------------------------------------------
    >
    > Testing if a browser's xhr object is sufficient is done by the
    > isXhrSufficientForXhrCore function.
    >
    > -----------------------------------------------------------
    >
    > // perhaps don't want these checks on an intranet or behind a login
    > // where the browsers are known to be new.
    >
    > if (typeof createXMLHttpRequest != 'undefined') {
    >
    >   var isXhrSufficientForXhrCore = (function() {
    >
    >     var xhr = createXMLHttpRequest();
    >
    >     // NN6.1 can create an XMLHttpRequest object but it doesn't
    >     // function and after creation the object's readyState
    >     // property is 'undefined' when it should be '0'.
    >     // The XHR object in NN6.1 doesn't work.
    >     if (xhr.readyState !== 0) {
    >       return false;
    >     }
    >
    >     // IE 5.01sp2 errors when x.setRequestHeader runs.


    When it runs or when it is evaluated?

    >     // The error says "object doesn't support this property"
    >     // This may be because xhr.open() hasn't been called.
    >     // yet. This is the reason for the try-catch. Since we cannot
    >     // determine if the xhr object has setRequestHeader then
    >     // we must put it down the degeradation path.
    >     //
    >     // Opera 8 XHR doesn't have setRequestHeader() and although
    >     // it seems to be able to make a POST request with a body
    >     // without this function This browser is still going to be
    >     // put down the degredation path. Note Opera 8.0.1 does have
    >     // setRequestHeader() and can POST.
    >     try {
    >       if (!xhr.setRequestHeader) {


    You didn't run it here. (?)

    >         return false;
    >       }
    >     } catch(e) {
    >       return false;
    >     }
    >
    >     // NN6.2 can't make POST requests because
    >     // can't have arguments to send()
    >     function cannotPost(xhr) {
    >       try {
    >         xhr.send('asdf');
    >       } catch (e) {
    >         // All calls to xhr.send() should error because there wasn't
    >         // a call to xhr.open() however the normal error is something
    >         // about "not initialized" as expected since xhr.open() was
    >         // not called. NN6.2 gives a different error indicating
    >         // xhr.send() cannot take arguments. Note that for NN6.2
    >         // in other human languages the error is still in English.
    >
    >         // TODO check for toString and indexOf?
    >         if (-1 !== e.toString().indexOf('Could not convert' +
    >             // TRICKY that leading space in next string matters.
    >             // using line break only to make postiong to group
    > possible
    >             ' JavaScript argument arg 0 [nsIXMLHttpRequest.send]')) {
    >           return true;
    >         }
    >       }
    >       return false;
    >     }
    >
    >     if (cannotPost(xhr)) {
    >       return false;
    >     }
    >
    >     return true;
    >
    >   })();
    >
    > }


    Yes, this should be optional. NS6 is pretty much dead.

    >
    > -----------------------------------------------------------
    >
    > I'm not completely happy with how all the feature testing is packaged
    > in the project so far. I'm more interested in getting correct
    > algorithms right now. However if someone has a bright idea about how
    > to package necessary feature tests in a library with such small
    > granularity, I'd be happy to read about it.


    Can you elaborate?

    >
    > With the xhrCore function wrappers can be added for API sugar. Below
    > is the status.js wrapper which allows the developer to specify
    >
    > -----------------------------------------------------------
    >
    > if (Function.prototype.apply &&
    >     String.prototype.match) {
    >
    >   var statusWrapper = function(original) {
    >
    >     return function(url, options) {
    >       options = options || {};
    >
    >       var handlers = {};
    >       for (var p in options) {
    >         if (p.match(/^on(\d+|Success|Failure|Complete)/)) {
    >           handlers[p] = options[p];
    >           // do this delete because don't want inner wrappers
    >           // to need to work on "branches". Waste of time.
    >           delete options[p];


    I don't follow this, but I think it is a bad idea to mutate options
    (this function doesn't own that object.)

    >         }
    >       }
    >
    >       options.onComplete = function(xhr) {
    >         var h;
    >         // exploit short circuting of ||
    >         if ((h = handlers['on'+xhr.status])  ||
    >             (h = handlers.onSuccess &&
    >               xhr.status>=200 && xhr.status<300) ||
    >             (h = handlers.onFailure &&
    >               (xhr.status<200 || xhr.status>=300)) ||
    >             (h = handlers.onComplete)) {
    >           h.apply(this, arguments);


    Why the apply?

    >         }
    >       };
    >
    >       return original(url, options);
    >     }
    >
    >   };
    >
    > }
    >
    > -----------------------------------------------------------
    >
    > With the xhrCore and the statusWrapper, a developer can use these
    > building blocks to easily build an Ajax library with the API he likes.
    > Here is an example...
    >
    > -----------------------------------------------------------
    >
    > // make a custom "sendAjax" function with the API
    > // and no extra bloat for API features I don't use
    > //
    > if (typeof statusWrapper != 'undefined' &&
    >     typeof xhrCore != 'undefined') {
    >   var sendAjax = statusWrapper(xhrCore);


    This is sort of weird. If the developer includes the statusWrapper
    function in the build, then it should automatically wrap itself around
    the old function. The next included wrapper would do the same. See
    the Effects/DirectX enhancements to show/changeImage/setElementHtml on
    my builder page.

    >
    > }
    >
    > // Use the new sendAjax function`
    > //
    > function testAjaxRequest() {
    >
    >   if (typeof sendAjax != 'undefined') {
    >
    >     sendAjax('serverFiles/handler.php',
    >              {on200: function(xhr) {
    >                         alert('on200');
    >                      },


    That's clever. I might have to copy that. All in all, I like this,
    but I think I am going to keep using my old Ajax objects for the
    moment. I am going to add them to my repository (along with several
    other updates) in the next few days. At some point in the near future
    I will open it up to the group so that proposed code can be cobbled
    together and unit tested as it is discussed.

    >               onComplete:function(xhr) {
    >                            alert('onComplete');
    >                          }
    >               });
    >   }
    >
    > }
    >
    > -----------------------------------------------------------
    >
    > Many other wrappers and even one other core are in the branch of the
    > repository I linked to at the top of this post. I'm particularly
    > curious if Randy Webb's system of script insertion could be written as
    > a different core. I think so. I believe that another core could be
    > written for file uploads using an iframe (aka old fashioned remote
    > scripting).


    I'm sure both are possible. But what would be the benefit(s)?

    >
    > I think this modular approach (some core functions and various
    > optional wrappers) is exactly what is needed to avoid the hugely
    > bloated Ajax libraries out there on the web.


    Same for any other sort of JS library.

    >
    > I look forward to reading people's thoughts.
    >


    That would be a nice ability to have. Are you working on an ESP
    project too?
     
    David Mark, Jan 16, 2008
    #2
    1. Advertising

  3. On Jan 16, 2:31 pm, David Mark <> wrote:
    > On Jan 16, 3:11 pm, Peter Michaux <> wrote:


    > > // xhrCore('example.com/handler.php',
    > > // {method:'POST',
    > > // body:'foo=bar&asdf=qwerty',
    > > // onComplete:function(){alert('complete');}});

    >
    > > if (typeof createXMLHttpRequest != 'undefined' &&
    > > (typeof isXhrSufficientForXhrCore == 'undefined' ||
    > > isXhrSufficientForXhrCore) &&
    > > String.prototype.toUpperCase) {

    >
    > That last one seems unnecessary.


    Based on the rules we have come to (somewhat) agree upon it is
    unnecessary.

    > > var xhrCore;

    >
    > > (function() {

    >
    > > function empty() {}

    >
    > > xhrCore = function(url, options) {
    > > options = options || {};

    >
    > > var xhr = createXMLHttpRequest(),
    > > method = (options.method || 'GET').toUpperCase(),
    > > callback = options.onComplete,
    > > // does send really need null or can it be undefined?

    >
    > I think it needs to be null.
    >
    > > body = options.body || null,
    > > headers = options.headers || {};

    >
    > > if (callback) {
    > > xhr.onreadystatechange = function() {
    > > if (xhr.readyState == 4) {
    > > callback(xhr);
    > > // for IE memory leak
    > > xhr.onreadystatechange = empty;
    > > }
    > > };
    > > }

    >
    > > if (method == 'GET' && body) {
    > > url = url + '?' + body;
    > > body = null;
    > > }

    >
    > > xhr.open(method, url, true);

    >
    > > if (method == "POST") {
    > > headers['Content-Type'] = headers['Content-Type'] ||
    > > 'application/x-www-form-urlencoded';
    > > }

    >
    > Add the X-Requested-With header if not specified?


    I didn't know about this and it seems like a very good idea. Is this a
    real standard, ad-hoc standard that some people are using? Are headers
    starting with "X" mean something in particular.

    Do you do this and have you had no issues?

    I see that jQuery does this too with the following line which I assume
    it means it must be working for some people and some browsers
    xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");


    > > for (p in headers) {
    > > xhr.setRequestHeader(p, headers[p]);
    > > }

    >
    > > xhr.send(body);

    >
    > > return {xhr:xhr};
    > > }
    > > })();

    >
    > > }

    >
    > > -----------------------------------------------------------

    >
    > > Testing if a browser's xhr object is sufficient is done by the
    > > isXhrSufficientForXhrCore function.

    >
    > > -----------------------------------------------------------

    >
    > > // perhaps don't want these checks on an intranet or behind a login
    > > // where the browsers are known to be new.

    >
    > > if (typeof createXMLHttpRequest != 'undefined') {

    >
    > > var isXhrSufficientForXhrCore = (function() {

    >
    > > var xhr = createXMLHttpRequest();

    >
    > > // NN6.1 can create an XMLHttpRequest object but it doesn't
    > > // function and after creation the object's readyState
    > > // property is 'undefined' when it should be '0'.
    > > // The XHR object in NN6.1 doesn't work.
    > > if (xhr.readyState !== 0) {
    > > return false;
    > > }

    >
    > > // IE 5.01sp2 errors when x.setRequestHeader runs.

    >
    > When it runs or when it is evaluated?


    When the code below runs, that is the code checks for the
    xhr.setRequestHeader property, IE 5 says "Object doesn't support this
    property or method". I suppose there is something like a broken
    [[has]]. I'm surprised this is the situation since I am using multiple
    IE on a single system. I would think the xml dll file would be more in
    control of this.


    > > // The error says "object doesn't support this property"
    > > // This may be because xhr.open() hasn't been called.
    > > // yet. This is the reason for the try-catch. Since we cannot
    > > // determine if the xhr object has setRequestHeader then
    > > // we must put it down the degeradation path.
    > > //
    > > // Opera 8 XHR doesn't have setRequestHeader() and although
    > > // it seems to be able to make a POST request with a body
    > > // without this function This browser is still going to be
    > > // put down the degredation path. Note Opera 8.0.1 does have
    > > // setRequestHeader() and can POST.
    > > try {
    > > if (!xhr.setRequestHeader) {

    >
    > You didn't run it here. (?)
    >
    > > return false;
    > > }
    > > } catch(e) {
    > > return false;
    > > }

    >
    > > // NN6.2 can't make POST requests because
    > > // can't have arguments to send()
    > > function cannotPost(xhr) {
    > > try {
    > > xhr.send('asdf');
    > > } catch (e) {
    > > // All calls to xhr.send() should error because there wasn't
    > > // a call to xhr.open() however the normal error is something
    > > // about "not initialized" as expected since xhr.open() was
    > > // not called. NN6.2 gives a different error indicating
    > > // xhr.send() cannot take arguments. Note that for NN6.2
    > > // in other human languages the error is still in English.

    >
    > > // TODO check for toString and indexOf?
    > > if (-1 !== e.toString().indexOf('Could not convert' +
    > > // TRICKY that leading space in next string matters.
    > > // using line break only to make postiong to group
    > > possible
    > > ' JavaScript argument arg 0 [nsIXMLHttpRequest.send]')) {
    > > return true;
    > > }
    > > }
    > > return false;
    > > }

    >
    > > if (cannotPost(xhr)) {
    > > return false;
    > > }

    >
    > > return true;

    >
    > > })();

    >
    > > }

    >
    > Yes, this should be optional. NS6 is pretty much dead.
    >
    >
    >
    > > -----------------------------------------------------------

    >
    > > I'm not completely happy with how all the feature testing is packaged
    > > in the project so far. I'm more interested in getting correct
    > > algorithms right now. However if someone has a bright idea about how
    > > to package necessary feature tests in a library with such small
    > > granularity, I'd be happy to read about it.

    >
    > Can you elaborate?


    As you were saying before, many feature tests are being repeated and
    that makes for code bulk.


    > > With the xhrCore function wrappers can be added for API sugar. Below
    > > is the status.js wrapper which allows the developer to specify

    >
    > > -----------------------------------------------------------

    >
    > > if (Function.prototype.apply &&
    > > String.prototype.match) {

    >
    > > var statusWrapper = function(original) {

    >
    > > return function(url, options) {
    > > options = options || {};

    >
    > > var handlers = {};
    > > for (var p in options) {
    > > if (p.match(/^on(\d+|Success|Failure|Complete)/)) {
    > > handlers[p] = options[p];
    > > // do this delete because don't want inner wrappers
    > > // to need to work on "branches". Waste of time.
    > > delete options[p];

    >
    > I don't follow this, but I think it is a bad idea to mutate options
    > (this function doesn't own that object.)


    I agree and didn't like this also. If a wrapper needs to modify the
    options object, then perhaps the wrapper should make a shallow copy of
    the options object, modify that copy and send that copy to the core.
    How does that sound?



    > > }
    > > }

    >
    > > options.onComplete = function(xhr) {
    > > var h;
    > > // exploit short circuting of ||
    > > if ((h = handlers['on'+xhr.status]) ||
    > > (h = handlers.onSuccess &&
    > > xhr.status>=200 && xhr.status<300) ||
    > > (h = handlers.onFailure &&
    > > (xhr.status<200 || xhr.status>=300)) ||
    > > (h = handlers.onComplete)) {
    > > h.apply(this, arguments);

    >
    > Why the apply?


    Because in the wrapper I don't know what the "this" object will be
    when the onComplete handler runs. There is another wrapper that allows
    the developer to set the "this" object with a "scope" option. I know
    you don't like that name for that option but no alternative was
    presented when I asked.


    > > }
    > > };

    >
    > > return original(url, options);
    > > }

    >
    > > };

    >
    > > }

    >
    > > -----------------------------------------------------------

    >
    > > With the xhrCore and the statusWrapper, a developer can use these
    > > building blocks to easily build an Ajax library with the API he likes.
    > > Here is an example...

    >
    > > -----------------------------------------------------------

    >
    > > // make a custom "sendAjax" function with the API
    > > // and no extra bloat for API features I don't use
    > > //
    > > if (typeof statusWrapper != 'undefined' &&
    > > typeof xhrCore != 'undefined') {
    > > var sendAjax = statusWrapper(xhrCore);

    >
    > This is sort of weird. If the developer includes the statusWrapper
    > function in the build, then it should automatically wrap itself around
    > the old function. The next included wrapper would do the same. See
    > the Effects/DirectX enhancements to show/changeImage/setElementHtml on
    > my builder page.


    I initially had the autowrapping but then when I added a second core,
    I realized I didn't know what to automatically wrap since the names of
    the cores are different. I've been burned several times when I make a
    foundational library do things automatically and then later I want to
    load the library to just use some other part of it.

    By having developer manually create the sendAjax function allows the
    developer to compose different similar functions with different
    combinations of wrappers.


    > > }

    >
    > > // Use the new sendAjax function`
    > > //
    > > function testAjaxRequest() {

    >
    > > if (typeof sendAjax != 'undefined') {

    >
    > > sendAjax('serverFiles/handler.php',
    > > {on200: function(xhr) {
    > > alert('on200');
    > > },

    >
    > That's clever. I might have to copy that. All in all, I like this,
    > but I think I am going to keep using my old Ajax objects for the
    > moment. I am going to add them to my repository (along with several
    > other updates) in the next few days. At some point in the near future
    > I will open it up to the group so that proposed code can be cobbled
    > together and unit tested as it is discussed.
    >
    > > onComplete:function(xhr) {
    > > alert('onComplete');
    > > }
    > > });
    > > }

    >
    > > }

    >
    > > -----------------------------------------------------------

    >
    > > Many other wrappers and even one other core are in the branch of the
    > > repository I linked to at the top of this post. I'm particularly
    > > curious if Randy Webb's system of script insertion could be written as
    > > a different core. I think so. I believe that another core could be
    > > written for file uploads using an iframe (aka old fashioned remote
    > > scripting).

    >
    > I'm sure both are possible. But what would be the benefit(s)?


    In a single page it could be there is XHR for form submission, an
    iframe hack for a file upload form and also there could be use of the
    img core to send information to a third party site that just tracks
    where the user clicks, for example.



    > > I think this modular approach (some core functions and various
    > > optional wrappers) is exactly what is needed to avoid the hugely
    > > bloated Ajax libraries out there on the web.

    >
    > Same for any other sort of JS library.
    >
    >
    >
    > > I look forward to reading people's thoughts.

    >
    > That would be a nice ability to have. Are you working on an ESP
    > project too?


    :)

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca/
     
    Peter Michaux, Jan 17, 2008
    #3
  4. Peter Michaux

    David Mark Guest

    On Jan 17, 3:18 pm, Peter Michaux <> wrote:
    > On Jan 16, 2:31 pm, David Mark <> wrote:
    >
    > > On Jan 16, 3:11 pm, Peter Michaux <> wrote:
    > > > // xhrCore('example.com/handler.php',
    > > > //         {method:'POST',
    > > > //          body:'foo=bar&asdf=qwerty',
    > > > //          onComplete:function(){alert('complete');}});

    >
    > > > if (typeof createXMLHttpRequest != 'undefined' &&
    > > >     (typeof isXhrSufficientForXhrCore == 'undefined' ||
    > > >      isXhrSufficientForXhrCore) &&
    > > >     String.prototype.toUpperCase) {

    >
    > > That last one seems unnecessary.

    >
    > Based on the rules we have come to (somewhat) agree upon it is
    > unnecessary.
    >
    > > >   var xhrCore;

    >
    > > >   (function() {

    >
    > > >     function empty() {}

    >
    > > >     xhrCore = function(url, options) {
    > > >       options = options || {};

    >
    > > >       var xhr = createXMLHttpRequest(),
    > > >           method = (options.method || 'GET').toUpperCase(),
    > > >           callback = options.onComplete,
    > > >           // does send really need null or can it be undefined?

    >
    > > I think it needs to be null.

    >
    > > >           body = options.body || null,
    > > >           headers = options.headers || {};

    >
    > > >       if (callback) {
    > > >         xhr.onreadystatechange = function() {
    > > >           if (xhr.readyState == 4) {
    > > >             callback(xhr);
    > > >             // for IE memory leak
    > > >             xhr.onreadystatechange = empty;
    > > >           }
    > > >         };
    > > >       }

    >
    > > >       if (method == 'GET' && body) {
    > > >         url = url + '?' + body;
    > > >         body = null;
    > > >       }

    >
    > > >       xhr.open(method, url, true);

    >
    > > >       if (method == "POST") {
    > > >         headers['Content-Type'] = headers['Content-Type'] ||
    > > >                              'application/x-www-form-urlencoded';
    > > >       }

    >
    > > Add the X-Requested-With header if not specified?

    >
    > I didn't know about this and it seems like a very good idea. Is this a
    > real standard, ad-hoc standard that some people are using? Are headers
    > starting with "X" mean something in particular.


    I think the "X-" prefix indicates it isn't a real standard. Certainly
    some people are using it.

    >
    > Do you do this and have you had no issues?


    Yes I use it to indicate Ajax requests. Certainly Web servers don't
    pay it any mind.

    >
    > I see that jQuery does this too with the following line which I assume
    > it means it must be working for some people and some browsers
    >         xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");


    The browsers don't pay any attention to it.

    >
    > > >       for (p in headers) {
    > > >         xhr.setRequestHeader(p, headers[p]);


    Forgot to note that this should be filtered.

    > > >       }

    >
    > > >       xhr.send(body);

    >
    > > >       return {xhr:xhr};
    > > >     }
    > > >   })();

    >
    > > > }

    >
    > > > -----------------------------------------------------------

    >
    > > > Testing if a browser's xhr object is sufficient is done by the
    > > > isXhrSufficientForXhrCore function.

    >
    > > > -----------------------------------------------------------

    >
    > > > // perhaps don't want these checks on an intranet or behind a login
    > > > // where the browsers are known to be new.

    >
    > > > if (typeof createXMLHttpRequest != 'undefined') {

    >
    > > >   var isXhrSufficientForXhrCore = (function() {

    >
    > > >     var xhr = createXMLHttpRequest();

    >
    > > >     // NN6.1 can create an XMLHttpRequest object but it doesn't
    > > >     // function and after creation the object's readyState
    > > >     // property is 'undefined' when it should be '0'.
    > > >     // The XHR object in NN6.1 doesn't work.
    > > >     if (xhr.readyState !== 0) {
    > > >       return false;
    > > >     }

    >
    > > >     // IE 5.01sp2 errors when x.setRequestHeader runs.

    >
    > > When it runs or when it is evaluated?

    >
    > When the code below runs, that is the code checks for the
    > xhr.setRequestHeader property, IE 5 says "Object doesn't support this
    > property or method". I suppose there is something like a broken
    > [[has]]. I'm surprised this is the situation since I am using multiple
    > IE on a single system. I would think the xml dll file would be more in
    > control of this.


    Use isHostMethod.

    >
    > > >     // The error says "object doesn't support this property"
    > > >     // This may be because xhr.open() hasn't been called.
    > > >     // yet. This is the reason for the try-catch. Since we cannot
    > > >     // determine if the xhr object has setRequestHeader then
    > > >     // we must put it down the degeradation path.
    > > >     //
    > > >     // Opera 8 XHR doesn't have setRequestHeader() and although
    > > >     // it seems to be able to make a POST request with a body
    > > >     // without this function This browser is still going to be
    > > >     // put down the degredation path. Note Opera 8.0.1 does have
    > > >     // setRequestHeader() and can POST.
    > > >     try {
    > > >       if (!xhr.setRequestHeader) {

    >
    > > You didn't run it here. (?)

    >
    > > >         return false;
    > > >       }
    > > >     } catch(e) {
    > > >       return false;
    > > >     }

    >
    > > >     // NN6.2 can't make POST requests because
    > > >     // can't have arguments to send()
    > > >     function cannotPost(xhr) {
    > > >       try {
    > > >         xhr.send('asdf');
    > > >       } catch (e) {
    > > >         // All calls to xhr.send() should error because there wasn't
    > > >         // a call to xhr.open() however the normal error is something
    > > >         // about "not initialized" as expected since xhr.open() was
    > > >         // not called. NN6.2 gives a different error indicating
    > > >         // xhr.send() cannot take arguments. Note that for NN6..2
    > > >         // in other human languages the error is still in English.

    >
    > > >         // TODO check for toString and indexOf?
    > > >         if (-1 !== e.toString().indexOf('Could not convert' +
    > > >             // TRICKY that leading space in next string matters.
    > > >             // using line break only to make postiong to group
    > > > possible
    > > >             ' JavaScript argument arg 0 [nsIXMLHttpRequest..send]')) {
    > > >           return true;
    > > >         }
    > > >       }
    > > >       return false;
    > > >     }

    >
    > > >     if (cannotPost(xhr)) {
    > > >       return false;
    > > >     }

    >
    > > >     return true;

    >
    > > >   })();

    >
    > > > }

    >
    > > Yes, this should be optional.  NS6 is pretty much dead.

    >
    > > > -----------------------------------------------------------

    >
    > > > I'm not completely happy with how all the feature testing is packaged
    > > > in the project so far. I'm more interested in getting correct
    > > > algorithms right now. However if someone has a bright idea about how
    > > > to package necessary feature tests in a library with such small
    > > > granularity, I'd be happy to read about it.

    >
    > > Can you elaborate?

    >
    > As you were saying before, many feature tests are being repeated and
    > that makes for code bulk.


    Indeed. At the very least we need a small base module that includes
    isHostMethod, isRealObjectProperty, etc. Also, modules that are
    documented to require other modules need not feature test functions
    that are defined unconditionally.

    >
    > > > With the xhrCore function wrappers can be added for API sugar. Below
    > > > is the status.js wrapper which allows the developer to specify

    >
    > > > -----------------------------------------------------------

    >
    > > > if (Function.prototype.apply &&
    > > >     String.prototype.match) {

    >
    > > >   var statusWrapper = function(original) {

    >
    > > >     return function(url, options) {
    > > >       options = options || {};

    >
    > > >       var handlers = {};
    > > >       for (var p in options) {
    > > >         if (p.match(/^on(\d+|Success|Failure|Complete)/)) {
    > > >           handlers[p] = options[p];
    > > >           // do this delete because don't want inner wrappers
    > > >           // to need to work on "branches". Waste of time.
    > > >           delete options[p];

    >
    > > I don't follow this, but I think it is a bad idea to mutate options
    > > (this function doesn't own that object.)

    >
    > I agree and didn't like this also. If a wrapper needs to modify the
    > options object, then perhaps the wrapper should make a shallow copy of
    > the options object, modify that copy and send that copy to the core.
    > How does that sound?


    That's what I was thinking.

    >
    > > >         }
    > > >       }

    >
    > > >       options.onComplete = function(xhr) {
    > > >         var h;
    > > >         // exploit short circuting of ||
    > > >         if ((h = handlers['on'+xhr.status])  ||
    > > >             (h = handlers.onSuccess &&
    > > >               xhr.status>=200 && xhr.status<300) ||
    > > >             (h = handlers.onFailure &&
    > > >               (xhr.status<200 || xhr.status>=300)) ||
    > > >             (h = handlers.onComplete)) {
    > > >           h.apply(this, arguments);

    >
    > > Why the apply?

    >
    > Because in the wrapper I don't know what the "this" object will be
    > when the onComplete handler runs. There is another wrapper that allows
    > the developer to set the "this" object with a "scope" option. I know
    > you don't like that name for that option but no alternative was
    > presented when I asked.


    I must have missed that post. I use "context."

    >
    > > >         }
    > > >       };

    >
    > > >       return original(url, options);
    > > >     }

    >
    > > >   };

    >
    > > > }

    >
    > > > -----------------------------------------------------------

    >
    > > > With the xhrCore and the statusWrapper, a developer can use these
    > > > building blocks to easily build an Ajax library with the API he likes.
    > > > Here is an example...

    >
    > > > -----------------------------------------------------------

    >
    > > > // make a custom "sendAjax" function with the API
    > > > // and no extra bloat for API features I don't use
    > > > //
    > > > if (typeof statusWrapper != 'undefined' &&
    > > >     typeof xhrCore != 'undefined') {
    > > >   var sendAjax = statusWrapper(xhrCore);

    >
    > > This is sort of weird.  If the developer includes the statusWrapper
    > > function in the build, then it should automatically wrap itself around
    > > the old function.  The next included wrapper would do the same.  See
    > > the Effects/DirectX enhancements to show/changeImage/setElementHtml on
    > > my builder page.

    >
    > I initially had the autowrapping but then when I added a second core,
    > I realized I didn't know what to automatically wrap since the names of
    > the cores are different. I've been burned several times when I make a


    Don't use different names?

    > foundational library do things automatically and then later I want to
    > load the library to just use some other part of it.
    >
    > By having developer manually create the sendAjax function allows the
    > developer to compose different similar functions with different
    > combinations of wrappers.
    >
    > > > }

    >
    > > > // Use the new sendAjax function`
    > > > //
    > > > function testAjaxRequest() {

    >
    > > >   if (typeof sendAjax != 'undefined') {

    >
    > > >     sendAjax('serverFiles/handler.php',
    > > >              {on200: function(xhr) {
    > > >                         alert('on200');
    > > >                      },

    >
    > > That's clever.  I might have to copy that.  All in all, I like this,
    > > but I think I am going to keep using my old Ajax objects for the
    > > moment.  I am going to add them to my repository (along with several
    > > other updates) in the next few days.  At some point in the near future
    > > I will open it up to the group so that proposed code can be cobbled
    > > together and unit tested as it is discussed.

    >
    > > >               onComplete:function(xhr) {
    > > >                            alert('onComplete');
    > > >                          }
    > > >               });
    > > >   }

    >
    > > > }

    >
    > > > -----------------------------------------------------------

    >
    > > > Many other wrappers and even one other core are in the branch of the
    > > > repository I linked to at the top of this post. I'm particularly
    > > > curious if Randy Webb's system of script insertion could be written as
    > > > a different core. I think so. I believe that another core could be
    > > > written for file uploads using an iframe (aka old fashioned remote
    > > > scripting).

    >
    > > I'm sure both are possible.  But what would be the benefit(s)?

    >
    > In a single page it could be there is XHR for form submission, an
    > iframe hack for a file upload form and also there could be use of the
    > img core to send information to a third party site that just tracks
    > where the user clicks, for example.


    I see. So the cores would need different names as more than one could
    be used.
     
    David Mark, Jan 17, 2008
    #4
  5. On Jan 17, 2:19 pm, David Mark <> wrote:
    > On Jan 17, 3:18 pm, Peter Michaux <> wrote:
    >
    > > On Jan 16, 2:31 pm, David Mark <> wrote:

    >
    > > > On Jan 16, 3:11 pm, Peter Michaux <> wrote:


    [snip]

    > > > > With the xhrCore function wrappers can be added for API sugar. Below
    > > > > is the status.js wrapper which allows the developer to specify

    >
    > > > > -----------------------------------------------------------

    >
    > > > > if (Function.prototype.apply &&
    > > > > String.prototype.match) {

    >
    > > > > var statusWrapper = function(original) {

    >
    > > > > return function(url, options) {
    > > > > options = options || {};

    >
    > > > > var handlers = {};
    > > > > for (var p in options) {
    > > > > if (p.match(/^on(\d+|Success|Failure|Complete)/)) {
    > > > > handlers[p] = options[p];
    > > > > // do this delete because don't want inner wrappers
    > > > > // to need to work on "branches". Waste of time.
    > > > > delete options[p];

    >
    > > > I don't follow this, but I think it is a bad idea to mutate options
    > > > (this function doesn't own that object.)

    >
    > > I agree and didn't like this also. If a wrapper needs to modify the
    > > options object, then perhaps the wrapper should make a shallow copy of
    > > the options object, modify that copy and send that copy to the core.
    > > How does that sound?

    >
    > That's what I was thinking.


    This points out a real problem with simulating named parameters in a
    function call.

    Suppose there is a function called "delayedAlert" and it takes two
    parameters "string1" and "string2" that will be concatenated and shown
    in an alert after 1 second.

    If the parameters are just normal parameters then the implementation
    and expected results are clear.

    function delayedAlert(string1, string2) {
    setTimeout(function() {
    alert(string1 + ' ' + string2)
    },
    1000);
    }

    delayedAlert('foo', 'bar');

    // alert will show "foo bar"

    If the parameters are sent using simulated named parameters the
    correct implementation and expected results become arguable.

    OPTION 1 - early binding

    function delayedAlert(params) {
    var string1 = params.string1;
    var string2 = params.string2;
    setTimeout(function() {
    alert(string1 + ' ' + string2)
    },
    1000);
    }

    delayedAlert({string1:'foo', string2:'bar'});

    // alert will show "foo bar"


    OPTION 2 - late binding

    function delayedAlert(params) {
    setTimeout(function() {
    alert(params.string1 + ' ' + params.string2)
    },
    1000);
    }

    var o = {string1:'foo', string2:'bar'};
    delayedAlert(o); // will show "foo bar" in alert
    o.string2 = 'baz';

    // alert will show "foo baz"


    --------------

    This relates to the Ajax library.

    If the libraries policy is that the options object does not belong to
    the library, then it belongs to the user. If it belongs to the user
    then the user may think that modifying that object should result in
    the late binding behavior.

    If the libraries policy is that the options object belongs to the
    library, then the user cannot trust it to be the same after the call
    to sendAjax. The user will need to expect the early binding behavior.

    I think that the desired behavior is early binding. If the library
    does or does not "own" the options object, the documentation will need
    to say what is happening.

    [snip]

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca
     
    Peter Michaux, Jan 18, 2008
    #5
  6. On Jan 17, 2:19 pm, David Mark <> wrote:
    > On Jan 17, 3:18 pm, Peter Michaux <> wrote:
    >
    > > On Jan 16, 2:31 pm, David Mark <> wrote:

    >
    > > > On Jan 16, 3:11 pm, Peter Michaux <> wrote:


    [snip]

    > > > > for (p in headers) {
    > > > > xhr.setRequestHeader(p, headers[p]);

    >
    > Forgot to note that this should be filtered.


    What do you mean by "filtered"? Do you mean using hasOwnProperty()?

    [snip]

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca/
     
    Peter Michaux, Jan 18, 2008
    #6
  7. On Jan 17, 2:19 pm, David Mark <> wrote:
    > On Jan 17, 3:18 pm, Peter Michaux <> wrote:
    >
    > > On Jan 16, 2:31 pm, David Mark <> wrote:

    >
    > > > On Jan 16, 3:11 pm, Peter Michaux <> wrote:


    [snip]

    > > > > I'm not completely happy with how all the feature testing is packaged
    > > > > in the project so far. I'm more interested in getting correct
    > > > > algorithms right now. However if someone has a bright idea about how
    > > > > to package necessary feature tests in a library with such small
    > > > > granularity, I'd be happy to read about it.

    >
    > > > Can you elaborate?

    >
    > > As you were saying before, many feature tests are being repeated and
    > > that makes for code bulk.

    >
    > Indeed. At the very least we need a small base module that includes
    > isHostMethod, isRealObjectProperty, etc. Also, modules that are
    > documented to require other modules need not feature test functions
    > that are defined unconditionally.


    The repository of code can take many different philosophies.

    For each function there can be a number of different functionalities.
    *For example*, serializing a form that has only text inputs vs.
    serializing a form that has text and select inputs. That seems easy
    enough but then Richard Cornford pointed out that Ice Browser has a
    bug in serializing select elements and that workaround may not be
    necessary to all developers is Ice Browser is out of the target set of
    browsers. So either the current implementations bloat or the number of
    implementations goes up. Really having the number of implementations
    go up is the correct philosophy for the repository. That is what many
    implementations is all about. However if the number of implementations
    goes up then the amount of code that needs to be maintained goes up
    and there is duplicate code. This is not a desirable situation either.

    Now for each implementation there are various levels of feature
    testing. Various levels of language features can be tested vs.
    assumed, various levels of host objects can be tested vs. assumed.
    Each combination of testing level and implementation is multiplying
    the number of implementations to maintain and duplicate code. There
    could be a system of documenting which features are assumed and which
    are actually tested but this gets messy and some automated way of
    converting the documentation into tests may be require.

    For a project, I see the situation is as a bit discouraging. However
    the project has already produced some really good discussion and some
    valuable information being brought forward.

    What I'm wondering about is what the product of the project should be.
    Would it be better if the product is a set of articles rather than
    trying to make some ready to use code?

    [snip]

    --
    Peter
    Code Worth Recommending Project
    http://cljs.michaux.ca/
     
    Peter Michaux, Jan 18, 2008
    #7
  8. Peter Michaux

    David Mark Guest

    On Jan 18, 3:53 pm, Peter Michaux <> wrote:
    > On Jan 17, 2:19 pm, David Mark <> wrote:
    >
    > > On Jan 17, 3:18 pm, Peter Michaux <> wrote:

    >
    > > > On Jan 16, 2:31 pm, David Mark <> wrote:

    >
    > > > > On Jan 16, 3:11 pm, Peter Michaux <> wrote:

    >
    > [snip]
    >
    > > > > >       for (p in headers) {
    > > > > >         xhr.setRequestHeader(p, headers[p]);

    >
    > > Forgot to note that this should be filtered.

    >
    > What do you mean by "filtered"? Do you mean using hasOwnProperty()?
    >


    It isn't supported well enough. Perhaps something like:

    typeof headers.constructor.prototype[p] == 'undefined'

    Something like this should be part of the base module (e.g.
    isOwnProperty.) Perhaps hasOwnProperty could be used if it is
    present, but I seem to recall that there are problems with it in IE.
     
    David Mark, Jan 18, 2008
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Peter Michaux
    Replies:
    45
    Views:
    394
    Peter Michaux
    Dec 7, 2007
  2. Peter Michaux
    Replies:
    16
    Views:
    250
    Peter Michaux
    Nov 26, 2007
  3. David Mark
    Replies:
    152
    Views:
    853
  4. David Mark

    isFunction (Code Worth Recommending Project)

    David Mark, Dec 8, 2007, in forum: Javascript
    Replies:
    25
    Views:
    253
    Peter Michaux
    Dec 12, 2007
  5. Peter Michaux
    Replies:
    0
    Views:
    160
    Peter Michaux
    Dec 13, 2007
Loading...

Share This Page