Taming Javascript
As part of my work on the Build Brighton Member Site (BBMS) I recently tackled some of the javascript that was scattered across the site.
The BBMS is a fairly ordinary website using traditional form posts and page refreshed to take actions and display data, it's certainly not a single page app and the javascript that's there has grown organically getting scattered across the site ending up in the most convenient place for whatever I was doing at the time.
While watching Laracasts a month or so ago a tool called browserify was mentioned, this allows you to use CommonJS module format and the require() statement in your code and then output a single compiled file. The require statement allows you to load in a library or another local file while keeping the scope locked down to what you're working on, it makes writing self contained pieces of javascript incredibly easy.
I didn't have the time to rewrite everything on the site so I decided to take a pragmatic approach and move the existing pieces of code within this new structure but leave them unchanged.
For example, on the site there is a basic feedback form available on every page, the code for this is a jquery form bind statement and a couple of handlers for a success and failure ajax response. Rather than being stuck in the footer or a random included file this code was placed in its own file in the CommonJS style.
FYI, this code is using the new ES6 class structure, it will end up getting compiled down for use across all browsers.
class FeedbackWidget {
constructor() {
var $ = require('jquery');
(function($){
//Support for simple ajax forms
$('.js-feedbackModalForm').on('submit', function(event) {
event.preventDefault();
$(this).find('input[type=submit]').attr('disabled', 'disabled');
//Clear the error messages
$(this).find('.js-errorMessages').text('');
$(this).find('.has-error').removeClass('has-error');
//Store the context for the callbacks
var $form = $(this);
$.ajax({
type: $(this).attr('method'),
url: $(this).attr('action'),
data: $(this).serialize(),
success: [function() {
$form.find('input[type=submit]').removeAttr('disabled');
}, feedbackFormSuccess],
error: [function() {
$form.find('input[type=submit]').removeAttr('disabled');
}, feedbackFormError],
dataType: 'json'
});
});
//Specific handlers for the feedback form
function feedbackFormSuccess(data) {
if (data.success) {
$('#feedbackWidgetModal').modal('hide');
$('.js-feedbackModalForm textarea').val('');
}
}
function feedbackFormError(xhr, status, error) {
var errors = eval("(" + xhr.responseText + ")");
for(var n in errors) {
var $field = $('.js-feedbackModalForm').find('.js-field-'+n+'');
$field.addClass('has-error');
$field.find('.js-errorMessages').text(errors[n]);
}
}
})($);
}
}
export default FeedbackWidget;
As you can see the code inside the FeedbackWidget class constructor is a very ordinary bit of code, it hasn't been refactored (yet), just dumped inside the constructor so the file can be loaded and used as a module.
jQuery has been required at the top and this is where some of the power of browserify comes from. I no longer need to be careful about the order in which I load these files, when everything gets compiled down browserify will make sure jquery which was loaded in via npm is available to the code.
In addition to these modules/files I have a app.js file which acts as the entry point, this file will include the above library file in the following way.
var FeedbackWidget = require('./FeedbackWidget');
new FeedbackWidget();
Again, this isn't doing anything clever. Its just loading the file and then instantiating it so the constructor gets run. At some point I will probably rewrite this code but the aim of the task was to tidy up the existing scattered code and bring it into one location which I successfully managed to do.
The finished js files.
The other benefit of this switch was getting rid of bower, it was a great way to download libraries but getting them included in the page was still a pain in the arse, one that involved nasty path includes. At one stage I had the following include lines in my gulp file.
'resources/assets/bower/jquery/dist/jquery.js',
'resources/assets/bower/bootstrap/dist/js/bootstrap.js',
'resources/assets/bower/bootstrap-datepicker/js/bootstrap-datepicker.js',
With the site JS updated and tidied into one place it allowed me to easily experiment with other things, that other thing being React. That was a fun experiment in replacing something that worked well with something far more complicated and doesn't do anything extra! Something for another post.