One of the topics that was of great interest at #jqcon was jQueryUI and it’s impressive widget library. What didn’t receive as much attention was how exactly to go about creating widgets that are compatible with jQueryUI. In this first of a series of articles in jQueryUI development, we’ll cover the basics of creating a simple jQueryUI widget. In future articles, we’ll look at how to pair widgets together using the Publisher / Subscriber (pub/sub) pattern.
This carousel is probably not quite robust enough to be deployed to production and I urge you to avoid copying and pasting this code into your live site. This basic carousel aims to teach you how to build one yourself as well as how to build a jQueryUI widget.
Create the Basic HTML Markup
To begin, we need a basic page and some photos thumbnails to work with. The thumbnails, as you’ll see later, can be any size you choose and our carousel will expand to fit them. Note especially that we’re only including some basic shell markup. There are a few good reasons for this decision: First, it keeps our basic document clean and free of extraneous markup. Second, it is far less work for people implementing our widget—and fewer potential points of error in their process.
<div id="slide">
<div>
<img src="img/carousel/0_10.jpg" width="78" height="29" alt="" />
</div>
<div>
<img src="img/carousel/0_11.jpg" width="78" height="58" alt="" />
</div>
<div>
<img src="img/carousel/1_6.jpg" width="78" height="58" alt="" />
</div>
<div>
<img src="img/carousel/1_4.jpg" width="78" height="58" alt="" />
</div>
<div>
<img src="img/carousel/0_9.jpg" width="78" height="58" alt="" />
</div>
</div>
For demonstration purposes, I’ve chosen the Cupertino theme available for download from the jQueryUI ThemeRoller application. You can choose any theme you desire—or create your own. Also, you can see I’ve created a reference to a carousel.js file. We’ll create this file as we continue.
Create the basic framework of a jQueryUI plugin
Now that we have our basic structure in place for the carousel, we can begin to create the javascript that will control the animation and interaction. jQueryUI provides a great construct called $.widget() that will encapsulate all of the functionality of the widget within a single namespace and construct. Let’s start by creating a basic widget:
(function($) {
$.widget("ui.carousel", {
_init: function() {
}
});
})(jQuery);
For those unfamiliar with the syntax above, we’ve created a self-executing function that takes the jQuery object as its only parameter. If you’re interested, you can read further about self-executing Javascript functions (and you really should; jQuery makes use of this capability extensively). Additionally, we’ve called $.widget and told it to create a ui.carousel component and passed an object that defines the component. Note the _init() function is a special, required function called a constructor that will run automatically when our carousel is created.
Provide a means for taking options
One of the great features jQueryUI’s $.widget() construct provides is the ability to automatically define a set of defaults that can be easily overridden by passing options to the function. Let’s go ahead and set up some of those defaults now. Add this second block beneath the block defined above:
$.extend($.ui.carousel, {
version: 0.1,
defaults: {
perPage: 3,
startPage: 1,
thumbWidth: 78,
thumbHeight: 58,
marginVert: 10,
marginHoriz: 10,
thumbBg: "#000",
borderColor: "#000",
borderWidth: 1
}
});
Here we’ve set defaults such as how many thumbnails should appear per page, the size of the thumbnails, and any bordering and spacing we want to apply. We’ll see later how we can easily override these defaults to suit our widget.
Set up the Structure
Recall that earlier in the tutorial we created only a basic HTML structure for a carousel. We now need to enhance that structure with the markup, css, and classes needed to turn our basic markup into a carousel. To do this, we will create methods that build the structure and call these methods from our _init() function.
First, we need some basic style rules to be implemented that the carousel will need in order to function. Ideally, we’d include these rules in a CSS file that we could dynamically load into the page, but for simplicity’s sake, we’ll simply include a string of CSS:
_addStyles: function() {
var c = "body { background: #fff; }\
.carousel {\
overflow: hidden;\
float: left;\
}\
.carousel .inner-carousel {\
width: 100000px;\
position: relative;\
}\
.carousel .inner-carousel div {\
float: left;\
}\
.carousel-icon {\
float: left;\
margin: 10px;\
}";
var style = document.createElement("style").innerHTML = c
document.body.appendChild(style);
}
Next, we’ll add the markup we need around the whole of the carousel itself:
_setupContainers: function(){
var self = this;
this.element.addClass("inner-carousel")
.wrap($(document.createElement("div")).addClass("carousel"));
var carousel = this.element.parent();
var cWidth = (this.options.thumbWidth*this.options.perPage)+
(this.options.marginHoriz*this.options.perPage)+
(this.options.borderWidth*2*this.options.perPage);
carousel.css({
'height': (this.options.thumbHeight+(this.options.marginVert*2)+
(this.options.borderWidth*2))+"px",
'width': cWidth+"px"
});
carousel.wrap($(document.createElement("div"))
.addClass("ui-widget ui-widget-content ui-corner-all")
.css({"height": (this.options.thumbHeight+(this.options.marginVert*2))+"px",
"width": (cWidth)+"px"}));
},
There are a few really cool things happening here. First, our widget is defining its own structure and required styles. Second, note the use of this.options. This object hash contains the values we defined in our widget defaults in a previous step. Third, notice the use of classes prefixed with “ui-” These classes are provided as part of jQueryUI’s CSS API to make our carousel compatible with ThemeRoller themes.
We also need to set up a couple of methods that handle adding the extra markup around each thumbnail, as well as set up the “letterbox” effect for images that are smaller than our standard thumbnail size.
_setupThumbContainers: function(){
var self = this;
var thumbContainers = $("div", this.element);
thumbContainers.each(function(i, thumb){
$(thumb).css({"margin-top": self.options.marginVert+"px",
"margin-left": (self.options.marginHoriz/2)+"px",
"margin-right": (self.options.marginHoriz/2)+"px",
"margin-botom": self.options.marginVert+"px"});
});
},
_setupLetterbox: function(){
var self = this;
var thumbContainers = $("div", this.element);
thumbContainers.each(function(i, thumb){
thumbContainers.css({"background-color": self.options.thumbBg,
"height": self.options.thumbHeight+"px",
"width": self.options.thumbWidth+"px",
"border": self.options.borderWidth+"px solid " + self.options.borderColor});
var img = $("img", thumb);
if(img.height() < self.options.thumbHeight){
img.css({'margin-top': Math.floor((self.options.thumbHeight-img.height())/2)+"px"});
}
if(img.width() < self.options.thumbWidth){
img.css({'margin-top': Math.floor((self.options.thumbWidth-img.width())/2)+"px"});
}
});
},
Now, we simply add calls to these methods to our _init() function and they’ll run as soon as the widget is created.
_init: function(){
this._addStyles();
this._setupContainers();
this._setupThumbContainers();
this._setupLetterbox();
}
Add to our _init()
Now that we have the structure methods setup, we need to add some pieces to our _init() method. In addition to actually calling these structure methods, we need to know a few things such as if an animation is currently running and the width of a single row of images. We also need to know how many pages there are.
this.numPages = Math.ceil(($("div > img", this.element).length/this.options.perPage));
this.currentPage = 1;
this.pageWidth = this.element.parent().width();
this.isAnimating = false;
Add Our Animation
Before we can add our buttons and their event handlers, we need to create our slide() method. This method is where the “magic” happens for the carousel. It handles providing the sliding effect of the images.
slide: function(distance){
if(!this.isAnimating){
distance = (distance) ? distance : this.pageWidth;
var self = this,
curLeft = (this.element.css('left') == 'auto') ? 0 : parseInt(this.element.css('left'));
this.isAnimating = true;
distance = curLeft+distance;
this.element.animate({left: distance+"px"}, 500, "swing",
function(){ self.isAnimating = false; });
}
},
First, we make sure that we’re not already running another animation. Then we calculate the new value of our CSS “left:” rule to define the distance to move. We pass in a negative distance to move the slider to the left thus advancing to the next page of the carousel. A positive distance will move the slider to the right thus moving to the previous page.
Add Our Buttons
Now that we have an animation we can use, we can buttons to control that animation using jQuery’s DOM manipulation abilities.
addButtons: function(){
var nextButton = $(document.createElement("div"))
.addClass("ui-state-default ui-corner-all carousel-icon")
.append($(document.createElement("div"))
.addClass("ui-icon ui-icon-carat-1-e"));
var prevButton = $(document.createElement("div"))
.addClass("ui-state-default ui-corner-all carousel-icon")
.append($(document.createElement("div"))
.addClass("ui-icon ui-icon-carat-1-w"));
this.element.parent()
.before(prevButton)
.after(nextButton);
},
There are two important concepts to notice here: First, that we’re again using the jQueryUI classes to give style to our buttons. Second, we are able to insert before and after the parent node of our carousel because we actually generated that parent in an earlier step. By creating our own sandbox, we can make certain assumptions about the DOM within that sandbox which makes life in this step much easier.
Add Event Handlers
Now we need to make these buttons actually do something. The logic is fairly simple. We need to:
- Ensure that we are not at the first page if we’re trying to move to previous
- Ensure that we are not at the last page if we’re trying to move to next
- Record the new page that we’re on after the page change
addHandlers: function(){
var self = this;
this.element.parent().prev().click(function(){
if(self.currentPage > 1){
self.currentPage--;
self.slide(self.pageWidth);
}
}).bind("mouseenter", function(){
$(this).addClass("ui-state-hover");
}).bind("mouseleave", function(){
$(this).removeClass("ui-state-hover");
});
this.element.parent().next().click(function(){
if(self.currentPage < self.numPages){
self.currentPage++;
self.slide(-self.pageWidth);
}
}).bind("mouseenter", function(){
$(this).addClass("ui-state-hover");
}).bind("mouseleave", function(){
$(this).removeClass("ui-state-hover");
});
},
As you can see, jQueryUI’s Theme API also provides some hover states for our buttons, so we’ve added those as well. Be sure also to add calls for adding the buttons and event handlers to the _init() method.
Putting it all together
Now we have the full carousel. In my example, my thumbnail size is different the default that we set earlier, so we must make one quick modification to how our widget is instantiated:
$(document).ready(function(){
$('#slide').carousel({thumbWidth: 78, thumbHeight: 58});
});
Our new values will automatically overwrite the defaults for the widget we defined earlier. You can see the fully functional demo page with the Cupertino theme applied. I’ve also included a second example page with the ui-darkness theme applied to show how quickly and easily we can change the look of our jQueryUI widget. Additionally, note that there are carousels of 3 different lengths on each page. By simply changing the perPage setting, the carousel sizes itself appropriately to fit your content.
I hope you’ve found this an interesting tutorial and that you’re thinking about how you can extend your carousel to do more than the basic work I’ve shown here. I also hope you’ll start using jQueryUI to build these types of components—as you can see it’s a very robust system that gives you a great platform for building UI widgets.
In future articles, we’ll do the following:
- Dynamically load our base CSS using Ajax
- Create an image viewer
- Pair a carousel and viewer together using pub/sub.