Skip to content

Instantly share code, notes, and snippets.

@tuespetre
Last active March 2, 2016 14:53
Show Gist options
  • Save tuespetre/5e62a6bf1245eddb4784 to your computer and use it in GitHub Desktop.
Save tuespetre/5e62a6bf1245eddb4784 to your computer and use it in GitHub Desktop.
A snippet to use for AJAX; robustness in the face of 401 and other errors

Handles 401 errors for OAuth users following the 'code flow' (redirects) and notifies the user if there is a fatal error. You wouldn't want the server giving your users 401s outright though -- only when the request is AJAX (X-Requested-With: XMLHttpRequest.)

Uses jQuery (obviously) as well as Bootstrap modals. I'm not using PJAX just yet but I will include it when I do.

In the following example, I've placed the following elements as the last children of the <body> element. Note the addition of the empty data-remote attribute on #dynamic-modal -- this prevents Bootstrap from performing a redundant load the first time the modal is used.

<div class="modal fade special" id="error-modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Error</h4>
            </div>
            <div class="modal-body">
                An error has occurred while trying to perform the requested action.
                Please try again or reload the page.
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-link" data-dismiss="modal">
                    Close
                </button>
                <button type="button" class="btn btn-primary" onclick="window.location.reload()">
                    Reload page
                </button>
            </div>
        </div>
    </div>
</div>
<div class="modal fade" id="dynamic-modal" tabindex="-1" role="dialog" data-remote="">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
        </div>
    </div>
</div>

Here's an example 'edit-in-place comment' as it would appear in a full page load:

<div data-ajax="component">
  <div class="author">Bob</div>
  <div class="comment">Hello</div>
  <a href="edit" data-ajax>Edit</a>
</div>

...and once you click 'edit', it is replaced with:

<form method="post" action="/new-comment" data-ajax="component">
  <div class="author">Bob</div>
  <div class="comment">
    <textarea name="comments" rows="5">Hello</textarea>
  </div>
  <button type="submit">Save</button> or
  <a href="display" data-ajax>Cancel</a>
</form>

And a small example for the #dynamic-modal (it's exactly the same as the Bootstrap docs):

<a href="/some/resource" data-toggle="modal" data-target="#dynamic-modal">Some link</a>
'use strict';
// If unauthenticated, attempt once to reauthenticate before
// giving up an invoking an error handler.
$.ajaxSetup({
statusCode: {
401: function (request, status, error) {
var $settings = this;
var auth = $('link[rel~=reauthenticate]').prop('href');
var opts = { xhrFields: { withCredentials: true }, statusCode: { 401: null } };
if ($settings.type == 'GET') {
$settings.contentType = null;
}
$settings.retried = true;
$.ajax(auth, opts)
.done(function () {
$.ajax($settings);
})
.fail(function () {
if (typeof $settings.error === 'function') {
$settings.error(request, status, error);
}
});
}
}
});
// a[data-ajax]
$(document).on('click', 'a[data-ajax]', function (event) {
var $this = $(this);
$.ajax({
type: "GET",
url: $this.prop('href'),
success: function (component) {
$this.closest('[data-ajax~=component]').replaceWith(component);
},
error: function (request) {
if (request.status != 401 || this.retried) {
$('#error-modal').modal('show');
}
}
});
event.preventDefault();
});
// form[data-ajax]
$(document).on('submit', 'form[data-ajax]', function (event) {
var $this = $(this),
method = $this.prop('method'),
options = {
url: $this.prop('action'),
success: function (component) {
$this.closest('[data-ajax~=component]').replaceWith(component);
},
error: function (request) {
if (request.status != 401 || this.retried) {
$('#error-modal').modal('show');
}
}
};
if (method === "post") {
$.extend(options, {
type: "POST",
data: new FormData(this),
processData: false,
contentType: false
});
}
else {
$.extend(options, {
type: "GET",
data: $this.serialize()
});
}
$.ajax(options);
event.preventDefault();
});
// #dynamic-modal (show)
$(document).on('show.bs.modal', '#dynamic-modal', function (event) {
var $link = $(event.relatedTarget);
var $modal = $(this);
var $content = $modal.find('.modal-content');
$.ajax({
type: "GET",
url: $link.prop('href'),
success: function (partial) {
$content.html(partial);
},
error: function (request) {
if (request.status != 401 || this.retried) {
$modal.modal('hide');
$('#error-modal').modal('show');
}
}
});
});
// #dynamic-modal (submit)
$(document).on('submit', '#dynamic-modal', function (event) {
var $form = $(event.target);
var $modal = $(this);
var $dialog = $modal.find('.modal-dialog');
$.ajax({
type: "POST",
url: $form.prop('action'),
data: $form.serialize(),
success: function (partial) {
$dialog.html(partial);
},
error: function (request) {
if (request.status != 401 || this.retried) {
$modal.modal('hide');
$('#error-modal').modal('show');
}
}
});
event.preventDefault();
});
// Ensure that all browers include the clicked submit button's
// value when submitting a form via XHR.
$(document).on('click', 'button[type=submit]', function (e) {
var $this = $(this),
name = $this.prop('name');
if (!name) {
return;
}
var form = $this.prop('form'),
$input = $(form).find('input[type=hidden][name=' + name + ']');
if (!$input.length) {
$input = $('<input type="hidden" name="' + name + '" />');
$(form).prepend($input);
}
$input.val($this.val());
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment