Showing posts with label jquery. Show all posts
Showing posts with label jquery. Show all posts

Tuesday, June 14, 2016

Modern AJAX in jQuery

In the dark ages, I wrote my own wrappers for jQuery.getJSON because it had the function signature:

$.getJSON(url [, data] [, success])

And I wanted to provide an error handler.  So, our corporate library (represented by LIB) has a function like:

LIB.getJSON(url, data, success [, error])

I also didn’t know if I could rely on getting a response body from error events, so where possible, errors are returned as 200 OK {Error:true,ErrorMsg:"boom"} and the LIB.getJSON catches these and invokes the error callback instead of the success one.

(One more sub-optimal design choice: like, almost the whole point of LIB.getJSON is to pass an error handler and let the user know “okay. we are not loading anymore.”  But notice that the error handler is still considered optional for some reason.)

If I were designing this from scratch today, the service would return errors as HTTP error responses, and I’d use the “new” (added in 1.5, after I started jQuery’ing) Deferred methods.

function success (data, txt, xhr) {
    // ...
}
function error (xhr, txt, err) {
    var r;
    try {
        r = $.parseJSON(xhr.responseText);
    } catch (e) {}
    // ...
}
$.getJSON(url, data)
    .then(success, error);

Result: a more RESTful design, with less glue.

I’d probably still have an “error adapter generator” function which converts all the possibilities for xhr/txt/err down to a single message string, and pass newErrorHandler(showErrorUI) as the error callback into .then().  But the point is, there’s no need to have LIB.getJSON as a whole anymore, to both accept an error callback and filter ‘successfully returned error’ values.

Monday, October 20, 2014

jQuery event handlers and Content-Security-Policy

I wrote a while back about the efficiency angle of using .on() to set up event handlers, but I recently discovered a surprising additional feature: when an AJAX request to the server returns HTML, jQuery (or bootstrap?) carefully lifts out the embedded <script> elements, and runs their content via eval().  This doesn't work with a Content-Security-Policy in place, because eval is forbidden.

In my case, the script just wanted to bind a handler to all the newly-added elements that were also provided in the response.

Switching to binding the handler to the modal itself and filtering the interesting children meant that not only did I get the memory and CPU improvements of binding a single handler one time, but it eliminated the script—and the need to eval it—from the response.

Before, event handlers were bound with this code in the response, via jQuery secretly calling eval():
<script type="text/javascript">
$('#clientlist a').click(activateClient);
</script>

Afterwards, the response contained no script, and the event handlers were bound natively in my page initialization code:
$('#clientmodal').on('click', '#clientlist a', activateClient);

The content of #clientmodal was replaced whenever the modal was opened, but the modal itself (and its event handler) remained in place.

Monday, January 13, 2014

Event Handler Optimization

I've been coding with jQuery for a long time.  I'm pretty sure 1.3 was new when I started.

One of the things that had always been "good enough" for me was to attach event handlers en masse directly to the elements involved: $('a.history').click(popupHistory);

It so happens that if the page is a table of 50 customers in the search result, each with a history link, then 50 handlers get bound.  jQuery takes care of all the magic, but it can't really optimize your intent.  Newer versions of jQuery—that is, 1.7 and up—support a more intelligent event-binding syntax which is a beautiful combination of two things:
  1. A single handler on an element containing all of the interesting ones, invoked as the event bubbles.
  2. A selector for the handler to filter interesting children.  The handler is only called if the event occurs within a child matching that selector.
These features are part of .on().  For an idea how this works:

<div id="box"></div>
<script type="text/javascript">
$('#box').on('click', 'a.history', function (e) {
popupHistory($(e.target).closest('[data-customer]').attr('data-customer'));
});
// populate the #box with HTML somehow, e.g.:
$('#box').html('<div data-customer="3">Customer! <a class="history" href="#">View Order History</a></div>');
</script>

Not only does this attach one event handler under the hood, but that event handler will be run for any present or future children.  The parent must be there for .on() to bind the handler to (otherwise, you'd have an empty jQuery set, and nothing would happen), but children will be detected by that handler when the event fires.

In the presence of dynamically added/removed elements, then, it's a double optimization: not only does only one handler get bound in the first place, but the handlers don't have to be changed on a per-element basis when children are added or removed.