After Responsive Design I and Responsive Design II, unfortunately time did not permit me to complete the work I was doing for the third and final post on responsive design. However, I still have some goodies for you in the form of a jQuery plugin I am still working on.
First of all, as always it is essential to know the basics before attempting to do anything of your own. A jQuery plugin works on the JavaScript prototype mechanism, which is used to create a class based approach on JavaScript objects. In JavaScript, it is likely that you have encountered the following scenarios:
- Functions as objects
var myFunc = function() {
// todo your code here
}
- Functions as methods
function myFunc() {
// todo your code here
}
JavaScript prototypes builds on the first scenario, where we have functions as objects. In fact, it is possible to also do the following:
function myObj(id) {
this.id = id;
this.printID = function() {
alert("ID: " + id);
}
}
In the above case, it would be possible to create two instances of myObj, each one with its own ID property, and calling that object's printID method will result in an alert popup with the respective ID values. This is the very basic of JavaScript prototype and allows us to create our own classes, each with it's own properties and methods, similarly to any other object oriented language.
Using prototyping, it's possible to do the same thing in a more organized and neatly-constructed manner. More importantly, using prototyping, it is possible to add functions to prebuilt JavaScript objects, such as strings or int. Check out this super useful example I include in almost every project I am working on:
// checks whether a string ends with the given character
if (typeof String.prototype.endsWith != 'function') {
String.prototype.endsWith = function (str) {
return this.slice(-str.length) == str;
};
}
// checks whether a string starts with the given character
if (typeof String.prototype.startsWith != 'function') {
String.prototype.startsWith = function (str) {
return this.slice(0, str.length) == str;
};
}
You may now call this function as if you were calling a function which is already existent on the String object:
return "incredible web".endsWith("web") // will return true
As you should definitely know, jQuery is a framework built upon plain JavaScript, making them Turing complete, i.e. anything which could be done in jQuery could also be done in JavaScript (albeit with more work and swearing). Now when doing it in jQuery, there are a few basic design patterns which should be followed to make sure your plugin is as scalable, maintainable and as flexible as possible (three properties you want for probably every module of code you are working on).
The most basic way of creating a jQuery plugin is using the following code:
$.fn.myPlugin = function() {
// todo your code here
};
The above code adds a new function to jQuery's $.fn object, which is an alias to the prototype property of that object, in fact jQuery.fn === jQuery.prototype. Therefore if you look deep into the jQuery architecture, you will find something of the sort:
foo.fn = foo.prototype = {
init:function() {
/*...*/
}
//...
};
We could improve on this by wrapping our plugin in an anonymous function to ensure it won't be affected by the remainder of your JavaScript code. We could then further improve this by using the $.extend, which allows you to define multiple functions one after the other. Therefore, working on our previous example where we checked whether a string starts/ends with a given substring:
(function ($) {
$.extend($.fn, {
endsWith: function(){
return this.slice(-str.length) == str;
},
startsWith: function(){
return this.slice(0, str.length) == str;
}
});
})(jQuery);
That's the very basics of jQuery plugins. There's still a lot to learn and I have included some suggested readings at the bottom of this article, however this is sufficient for the scope of this blog.
So what are incredible buttons? The idea started after seeing these gorgeous buttons by @hakimel a few weeks back. Admittedly, they are really great and give a really smooth user experience and I had begun using them, until I realized that they are not exactly what I was looking for, so I obviously proceeded to creating my own. This goes against my policy (and hopefully everyone elses) of not re-inventing the wheel, but I thought it would prove a great learning experience anyway.
So, we want to replicate a similar button, i.e. we want our button follow progressive enhancement design principles, be unobtrusive, be scalable and flexible; and most importantly to provide a great user experience. For starters, we created our normal button using CSS and JavaScript as we would do ordinarily (note that the CSS is extended for links which should appear as buttons).
a.buttonLink, input[type='submit'] {
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
-ms-border-radius: 10px;
-o-border-radius: 10px;
border-radius: 10px;
display: inline-block;
text-transform: uppercase;
text-decoration: none;
color: white;
background: #00acde;
padding: 1em 1.25em;
border: 6px solid white; }
a.buttonLink:hover, input[type='submit']:hover {
color: white;
background: #f58220; }
a.buttonLink:disabled, input[type='submit']:disabled {
background: #999999; }
<input type="submit" value="Submit" />
Now we proceed to build our unobtrusive jQuery plugin. First of all, we want that the user could simply change a normal button into an incredible button by attaching it the required class, hence our submit button becomes:
<input type="submit" value="Submit" class="incredible-button" />
We will then run the required function to search the DOM for instances of ".incredible-button" and update the HTML accordingly. In fact, we need to append a <span> after the button which will contain the spinner GIF.
if ($(".incredible-button").length) {
$.grep($(".incredible-button"), function (that, n) {
$(that).parent().css("position", "relative");
var spinner = "<span class='loading-spinner' style='width:" + $(that).innerWidth() + "px; margin-left: -" + ($(that).outerWidth() - (($(that).outerWidth() - $(that).innerWidth()) / 2)) + "px'></span>";
$(that).after($(spinner));
});
}
As you may notice in the above code, we are assigning the width and margin of the <span> dynamically, based on the width and margin of the button. This allows our span to be used on buttons of varying widths and formats. The end result is that the span is directly above the button (although set to display: none;).
When clicking the button, we will then assign the button the "loading" class, which will set the background to a gray colour and the text to transparent; and the "loading" class to the spinner, which will make it visible and set its display property to "inline-block". This is the simplest form of using this button and animation, however we want to also assign it different transition options. We would do this by creating a custom attribute on the submit button and checking the value of this attribute.
<input type="submit" value="Submit" class="incredible-button" transition="slide-right" />
$(".incredible-button").bind('click', function () {
// keep the button object
var that = $(this);
// set it to loading mode
$(that).addClass("loading");
// retrieve the loading spinner
var spinner = $(that).next("span.loading-spinner");
if (spinner) {
// show the spinner
$(spinner).addClass("loading");
switch (that.attr("transition")) {
case "slide-right":
var x = (spinner.innerWidth() / 2) - (32 / 2);
$(spinner).animate({
'background-position-x': x + 'px'
}, {
duration: 300,
easing: $.easie(0.175, 0.885, 0.320, 1.275)
});
break;
default:
break;
}
}
});
The end result is a smooth sliding animation once the button is clicked. Half the work is done, however we now need to make sure that the buttons returns to normal once the AJAX request is completed. For this we have created the notify function, which will be called in the callback function of your AJAX request:
(function ($) {
$.extend($.fn, {
notify: function () {
// check if the item is of the correct type
if ($(this).is("input[type=submit], button")) {
var that = $(this);
// retrieve the loading spinner
var spinner = $(that).next("span.loading-spinner");
if (spinner) {
switch (that.attr("transition")) {
case "slide-right":
$(spinner).delay(300).animate({
'background-position-x': '-32px'
}, {
duration: 300,
easing: $.easie(0.175, 0.885, 0.320, 1.275),
complete: function () {
// hide the spinner and reset it's background position
$(spinner).removeClass("loading").css({ 'background-position-x': '0'});
// remove the loading class
$(that).removeClass("loading");
}
});
break;
default:
break;
}
}
// return the button for jquery chaining
return that;
}
}
});
})(jQuery);
That's all, you have a nice jQuery button which works across all browsers and is progressively enhanced and unobtrusive. Great stuff! In the future I intend to continue building on this plugin, improving it's speed and also increasing the different transition options and hopefully sharing this plugin with everyone.
And as always, a jsfiddle to play with: http://jsfiddle.net/KevinFarrugia/Sk8vC/2/
Until then, thanks for reading and have a nice weekend,
Kevin
Recommended readings: