25
Apr '20

I make a lot of use of bootstrap modals in my code, and I find it incredibly useful to load the content dynamically, rather than have everything embedded in the page just in case it may be needed. To do this I tend to have a modal container (sometimes multiple, allowing for different sizes or styles), and target it from the trigger link.

<a href="/remote/content" class="btn btn-xs btn-primary modal-toggle" data-toggle="modal" data-target="#remote-modal" title="Load remote modal">Load Modal</a>

<div class="modal fade remote-modal" id="remote-modal" role="dialog" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">

        </div>
    </div>
</div>

Then all it takes is a modal show listener that grabs the URL from the trigger, and loads the content via AJAX into the modal-content section. This also gives me the opportunity to add a new modal event that is fired when the content has completed loading; ‘loaded.bs.modal’. This new event is hugely useful as the core ‘shown.bs.modal’ event will often trigger before the content is loaded.

$('.remote-modal').on('show.bs.modal', function (e) {
    var trigger = $(e.relatedTarget);
    var modal = $(this);

    $(this).find('.modal-content').load(trigger.attr('href'), function() {
        modal.trigger('loaded.bs.modal', []);
    });
});

Simple as that.

03
Mar '20

I try to return the correct HTTP codes from my AJAX calls whenever possible, and I often use these for different purposes, for example a 401 indicating that a session has expired, and a 403 meaning that the permissions are lacking on the current session.
These can be caught and acted on very easily with jQuery:

$(document).ajaxError(function(event, jqxhr, settings, exception) {
    if(jqxhr.status == 401) {
        alert('Unathorised');
    }
    else if(jqxhr.status == 403) {
        alert('Forbidden');
    }
    else if(jqxhr.status >= 500) {
        alert('An error occurred');
    }
});
05
Mar '19

I spent some time messing about with Bootstrap popovers, and other similar tools but was finding that they tended to be overly complicated to use and skin, and also mostly wouldn’t allow form elements to be inside them. I found myself with a task where I was very tight on space and needed to add several textareas to my form. A popover was the obvious choice, but many relied on convoluted attributes on the trigger element or involved javascript handlers. Even more importantly most tend to destroy the popover when it is closed, which is clearly no use I want a form element in there.

After a bit of playing around I came up with this script myself:

CSS:

.popover-container {
    position: relative;
}

.popover-content {
    z-index: 10000;
    border: 1px solid #555555;
    padding: 8px;
    margin-top: 10px;
    width: 300px;
    position: absolute;
    left: -1000000px;
}

.popover-content.open {
    left: -150px;
}

.popover-content.open:after {
    content: '';
    display: block;
    position: absolute;
    top: -6px;
    left: 150px;
    width: 10px;
    height: 10px;
    background: #FFFFFF;
    border-right:1px solid #555555;
    border-top:1px solid #555555;
    -moz-transform:rotate(-45deg);
    -webkit-transform:rotate(-45deg);
}

JS:

$().ready(function() {

    $('.popover-container').on('click', 'button', function() {
        var parent = $(this).parents('.popover-container');
        var content = parent.find('.popover-content');

        if(content.hasClass('open')) {
            closePopover(content);
        }
        else {
            //Clear down any others first
            $('.popover-content.open').each(function() {
                closePopover($this);
            });

            content.addClass('open');

            var offset = 150 - (parent.width() / 2) - 2;

            content.css('left', '-' + offset + 'px');
        }
    });

    //Close on blur
    $('.popover-container').on('blur', '.popover-content', function() {
        closePopover($(this));
    });

    //Close on escape key
    $(document).keyup(function(e) {
        if (e.keyCode === 27) {
            $('.popover-content.open').each(function() {
                closePopover($(this));
            });
        }
    });
});

function closePopover(element) {
    element.removeClass('open');
    element.css('left', '');
}

Example HTML:

<div class="popover-container">

    <button type="button" class="btn btn-xs btn-default">
        Show Popover
    </button>

    <div class="popover-content">
        <div class="form-group">
            <p>This is my popover</p>
            <textarea name="my-textarea"></textarea>
        </div>
    </div>
</div>

Note that the CSS and JS above are based around the popover being 300px wide. You can change this to whatever you like, but bear in mind that the centering (the 150px offsets) needs to be adjusted in a corresponding way.

01
Nov '18

Ok many of you may already know this one, but it’s a relatively new one to me, and since non-HTML5 browsers are now mostly gone the way of the dinosaur, I’m much more comfortable relying on its elements for my core functionality. If you are still on IE9 or below then you deserve all you get!

This is as simple as I can make an AJAX form submission handler, that will also deal with files. It uses the HTML5 FormData object to serialise all the form data, including files, and allow us to pass it with the AJAX POST request.

$('.my-form').on('submit', function(e) {

    var form_data = new FormData($(this)[0]);

    $.ajax({
        type: 'POST',
        url: $(this).attr('action'),
        data: form_data,
        dataType: 'json',
        cache: false,
        contentType: false,
        processData: false,
        success: function(response) {
            // Wahey!
        },
        error: function() {
            // Oh dear
        }
    });

    return false;
});

The ‘cache’, ‘contentType’ and ‘processData’ flags ARE necessary to make this work properly. The ‘dataType’ is optional, but if your server doesn’t send back JSON in response to an AJAX request as standard then you need to have a word with yourself!

Also notice that the FormData object needs the DOM version of the form, not the jQuery object, hence the need for “$(this)[0]” in the constructor.

Job. Done.

27
Mar '18

So this one had me puzzled for rather a while. We all know about the bootstrap event ‘shown.bs.modal’ that fires when a modal has been loaded. However, when the modal content is loaded via AJAX, I’ve found that can still be triggered a little bit in advance of contained elements loading in the DOM.

To give a concrete example, I encountered this when trying to activate datatables on a table that I was embedding in a modal. My script was set up as follows:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').DataTable({
        //Datatable options
    });
});

This tended to work about 50% of the time, and after a lot of thought I realised that the times it didn’t run were due to the table element not being loaded into the DOM before the DataTable call is made. Even though the Bootstrap event had been fired, the DOM was yet to catch up.

Now there are various ways around this, most obviously by using ‘setTimeout’ to add a short delay before triggering subsequent logic, although this technique has always struck me as rather imprecise and risky. Fortunately, jQuery provides the ‘ready’ function which gives us all we need to make this logic work perfectly:

$('.modal').on('shown.bs.modal', function (e) {
    $('.table-class').ready(function() {
        $('.table-class').DataTable({
            //Datatable options
        });
    });
});

All we are doing here is adding a subsequent observer to the element so that we can be sure the DOM is aware of it before we try to run any further logic. Simple as that!

21
Oct '17

Cloning simple markup is usually a fairly trivial task in jQuery, using the .clone() function, however it becomes a major headache when there are form elements involved. jQuery’s clone function effectively works based on the raw HTML of the page and as such any changes that have since taken place within the DOM are ignored. This doesn’t sound like such a big deal until you realise that includes changes to all form input elements; the clone will have these all reset to their initial values.

I’ve written a little function to get round this issue, which simply ‘fixes’ the current values fo all input fields (except files) in the markup, allowing them to be cloned correctly:

function fixValues(element) {
    element.find('input[type=text]').each(function() {
        $(this).attr('value', $(this).val());
    });

    element.find('input[type=checkbox], input[type=radio]').each(function() {
        $(this).attr('checked', $(this).is(':checked') ? 'checked' : '');
    });

    element.find('select option:selected').each(function() {
        $(this).attr('selected', 'selected');
    });

    element.find('textarea').each(function() {
        $(this).html($(this).val());
    });
}

Just trigger this function on the element you want to clone, immediately before calling the jQuery clone function (or, if you’re feeling really enthusiastic, you could even extend the clone function itself) and your values should all be copied over as expected.

29
May '17

Like me, you may find the jQuery $.getJSON function really useful for quick GET-based AJAX calls, and lament the lack of a corresponding POST function. Well here it is:

$.postJSON = function(url, data, func) { $.post(url, data, func, 'json'); }

You’re welcome!

13
Nov '16

Easy one this, just copy and paste code if you’re using Bootstrap.
Whenever a tab is changed, this code will take its ID from the trigger and put into the window location hash (AKA address anchor or, if you’re really posh, the fragment identifier).

//Remember tab state
$('ul.nav-tabs > li > a').on('shown.bs.tab', function(e) {
    if($(e.target).attr('href')) {
        var id = $(e.target).attr('href').substr(1);
        window.location.hash = id;
    }
});

The second section of code ensures that whenever the page is reloaded, or the page is linked to using a valid tab ID, the identified tab will be selected automatically.

//Switch tab state
if (window.location.hash) {
    $("a[href='" + window.location.hash + "']").tab('show');
}

Done

28
Aug '13

You won’t often find me posting an IE only chunk of code here but today I’m making an exception as I have found myself writing a site specifically for use in a call-centre where I know for a fact that everyone will be using IE so I can make a few custom nice-to-haves with that in mind.

IE, for all its faults, is the only browser that will let you implement a copy to clipboard function without the need for Flash plugins. There are lots of tutorials out there that show you ways to do this, but I ended up building a very quick jQuery function that I thought I’d share.

This assumes that you have a container with a class of ‘copy_text’ and a button, link or other clickable element with the class of ‘copy_to_clipboard’:

$('.copy_to_clipboard').click(function(e) {
    e.preventDefault();

    if (!document.all)
        return; // IE only

    var copy_text = $(this).siblings('.copy_text').html();
    var new_element = document.createElement('input');
    new_element.setAttribute('type', 'text');
    new_element.setAttribute('class', 'copy_text_dynamic');
    new_element.setAttribute('value', copy_text);
    document.body.insertBefore(new_element)

    r = new_element.createTextRange();
    r.execCommand('copy');
});

Now you’ll notice this has created an input element on the fly. This is because IE’s createTextRange call only works on input elements so we have to create one and fill it with the text that we want to copy to the clipboard.

The final part of this process is making sure that this newly created input element doesn’t show up on our page. You may have already noticed that you can’t hide an element (i.e. display: none) in IE if you want to do anything with it as it doesn’t bother putting hidden elements in the DOM. That being the case, as an alternative I prefer to fix the position of the element and send it way off the side of the screen like so:

.copy_text_dynamic {
    display: block;
    position: fixed;
    left: -10000px;
}
14
Jun '12

I have no problems using jQuery plugins that are rather old, if they are well written and efficient for my purpose. However jQuery hasn’t maintained complete backwards-compatibility throughout it’s life, notably when in version 1.5 the ‘handleError’ function was removed.
It is very simple to put in a patch for older plugins that still use this function – just extend jQuery and add the function back in yourself. If you don’t care about the error messages you can just add an empty function block but I prefer to use something like this to give me a better clue about the errors I might be dealing with:

jQuery.extend({
	handleError: function( s, xhr, status, e ) {
		// If a local callback was specified, fire it
		if ( s.error )
			s.error( xhr, status, e );
		// If we have some XML response text (e.g. from an AJAX call) then log it in the console
		else if(xhr.responseText)
			console.log(xhr.responseText);
	}
});