If you haven’t seen Apple’s product slider in action, you must check it out. It’s a horizontal gallery of their products with horizontal slider controlled with javascript. I believe they are using the scriptaculous motion for this, but there should be a way to build this in jQuery.
Truth be told, I was a die-hard fanatic for scriptaculous/prototype, but after ExpressionEngine announce their adoption of jQuery for EE 2.0, it was time to grow beyond the one framework pony.
Luckily, someone already set out to build the slider in jQuery. And major hat’s off to Remy Sharp for a great approach to this. If you looking for a way to build your own slider, start there first.
The Problem
Actually, there was no problem until I had a client to didn’t fit into the strict mold set out for this widget. As with most inventive solutions, they are begat from necessity.
The slider is built on an unordered list, but doesn’t support nested lists. For my needs, the slider had to be able to not only show multiple categories of products, but also visually differentiate.
Now, this can be resolved by manually setting css values. This approach would “hard code” the positions of the categories. That isn’t very code may be puled from a database.
Also, the slider needs to work without javascript. I know. Who doesn’t use javascript? The answer is, I don’t know, but someone always points out if something doesn’t degrade properly.
The Solution
I built this out backwards, but it works. So don’t question it. This demo shows the slider with wireframes to show off the block level div’s and li’s.
You can see by this code example that the lists are simple and even has a space for the category description if your site requires it.
HTML
This is a simple template shoing how to format your product groups and your categories for them. There can be as many needed. This example should be able to display them all successfully.
<div id="mlSlideWidget" class="slideWidget">
<div id="sliderWindow" class="sliderGallery">
<ul class="sliderList">
<!-- Each category -->
<li class="group">
<div class="groupContent">
<!-- Description info for category -->
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
<ul class="groupButtons">
<!-- Each product for this category -->
<li class="singleButton"><a href="#">Product 1</a></li>
<li class="singleButton"><a href="#">Product 2</a></li>
<li class="singleButton"><a href="#">Product 3</a></li>
</ul>
</div>
</li>
<li class="group">
<div class="groupContent">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
<ul class="groupButtons">
<li class="singleButton"><a href="#">Product 4</a></li>
<li class="singleButton"><a href="#">Product 5</a></li>
<li class="singleButton"><a href="#">Product 6</a></li>
</ul>
</div>
</li>
<li class="group">
<div class="groupContent">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
<ul class="groupButtons">
<li class="singleButton"><a href="#">Product 7</a></li>
<li class="singleButton"><a href="#">Product 8</a></li>
<li class="singleButton"><a href="#">Product 9</a></li>
</ul>
</div>
</li>
<li class="group">
<div class="groupContent">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vestibulum vitae diam vitae leo hendrerit aliquet. Donec dolor. Integer placerat rhoncus metus. Proin sagittis. Nullam sed lacus accumsan orci convallis interdum. Aenean aliquet, nisi et sollicitudin posuere, orci tortor fermentum.</p>
<ul class="groupButtons">
<li class="singleButton"><a href="#">Product 10</a></li>
<li class="singleButton"><a href="#">Product 11</a></li>
<li class="singleButton"><a href="#">Product 12</a></li>
</ul>
</div>
</li>
</ul>
</div>
<div id="scroll" class="slider">
<!-- the handler to action the slide -->
<div class="handle"></div>
<!-- labels appear against the slider, as pointers to the user -->
<ul>
<!-- These are categories -->
<li class="marker">Category 1</li>
<li class="marker">Category 2</li>
<li class="marker">Category 3</li>
<li class="marker">Category 4</li>
</ul>
<div class="fakeHandle">
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="15"><img src="images/handle-left.png" width="15" height="115" alt="<" /></td>
<td background="images/handle-middle.png"> </td>
<td width="15"><img src="images/handle-right.png" width="15" height="115" alt=">" /></td>
</tr>
</table>
</div>
</div>
</div>
CSS
I threw in a dash of YUI’s css reset. Most of the id and class names are generic. I would recommend renaming these to something more specific to avoid conflict.
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, img {border-width: 0px;margin:0;padding:0;}
#mlSlideWidget {
font:11px arial,helvetica,clean,sans-serif;
*font-size:small;
*font:x-small;
position: relative;
height: 361px;
width: 659px;
background: url(images/slider-background.jpg) left top no-repeat;
}
#sliderWindow {
margin: 1px;
height: 365px;
position: relative;
width: 655px;
}
.sliderGallery {
overflow: auto;
}
.sliderGallery ul.sliderList {
top: 0px;
left: 0px;
margin-top: 135px;
padding-bottom: 0;
position: absolute;
list-style: none;
height: 210px;
overflow: auto;
width: 2000px;
white-space: nowrap;
}
#mlSlideWidget .sliderGallery ul li.group {
margin-left: 7px;
margin-right: 7px;
display: inline;
float: left;
}
#mlSlideWidget .sliderGallery ul li.group .groupContent {
margin-right: 10px;
padding-left: 10px;
padding-top: 10px;
padding-bottom: 10px;
}
ul.groupButtons {
height: 120px;
}
li.group {
background: url(images/group-backgroundcap.png) no-repeat right top;
}
li.group .groupContent{
background: url(images/group-background.png) no-repeat left top;
}
li.group p {
width: 430px;
height: 70px;
font-size: 10px;
line-height: 12px;
white-space: normal;
}
li.group ul.groupButtons {
list-style: none;
white-space: nowrap;
}
li.group ul.groupButtons li.singleButton {
width: 140px;
float: left;
height: 118px;
text-align: center;
}
#scroll {
position: absolute;
top: 0px;
left: 0px;
z-index: 100;
width: 639px;
margin-left: 10px;
margin-right: 8px;
}
#scroll ul {
height: 123px;
list-style: none;
}
#scroll li.marker {
margin-top: 4px;
padding-top: 60px;
text-align: center;
z-index: 90;
font-size: 10px;
height: 30px;
width: 55px;
padding-left: 20px;
padding-right: 20px;
margin-left: 10px;
margin-right: 10px;
float: left;
}
.handle {
position: absolute;
top: 8px;
left: 0;
z-index: 110;
height: 115px;
}
.fakeHandle {
position: absolute;
top: 8px;
left: 0;
z-index: 80;
height: 115px;
}
jQuery UI will need to be built with UI Core, Draggable, Slider, Effects Core, and Slide. Some of the values of this rules in the example are built to my specs and can be modified to whatever specs fit your site. Note the versions of jQuery at the time of this article.
This example doesn’t know how many objects exist in the slider. If you use a set amount, this example can be greatly reduced. However, if your categories are managed via a database or cms tool, the number of objects used can change.
Javascript
Try not to laugh at my n00b code. I’m sure there are much better ways to write this.
//basic rules for controling the divs
$(document).ready(function(){
//fix css rules
//above css written for pages loaded without javascript
$('.sliderGallery').css({'overflow-x': 'hidden', 'overflow-y': 'visible'});
$('li.marker').css({'float': 'none', 'position': 'absolute', 'top': 0, 'left': 0});
//set up some public variables and arrays
var scrollBar = 639;
var sliderListOffsetLeft = $('.sliderList').offset();
var productWidth = 0;
var groupWidth = 0;
var sliderWidth = new Array();
var sliderLeft = new Array();
var groupRangeLeft = new Array();
var marker = new Array();
//measure the width of all the sub-categories
//and the sub-sub-categories while we're at it
$('li.group').each(function (i) {
sliderLeft[i] = productWidth;
$('li.singleButton', this).each(function (i) {
groupWidth = groupWidth + $(this).width()
});
//hard coded minimum width for groups
if (groupWidth < 450)
groupWidth = 450;
else
groupWidth += 20;
productWidth = productWidth + groupWidth + 16;
//set width for each sub-caterogy
sliderWidth[i] = groupWidth + 14;
$(this).css({width: groupWidth});
//reset the variable
groupWidth = 0;
//used in another function not used in demo
groupOffsetleft = $(this).offset();
groupRangeLeft[i] = groupOffsetleft.left - sliderListOffsetLeft.left - ((i > 0) ? 14 : 7 );
});
//Fix css to fit with slider content
$('ul.sliderList').css({width: productWidth + 14});
//Variables to make math easier
var handle = (scrollBar / productWidth) * scrollBar;
var markerLeft = 0;
var scrollBarVisible = scrollBar - handle;
$('li.marker').each(function (i) {
markerLeft = (sliderLeft[i] / productWidth * scrollBar);
markerWidth = (sliderWidth[i] / productWidth * scrollBar);
$(this).css({left: markerLeft});
marker[i] = (markerLeft / scrollBarVisible) * 100;
});
$('div.handle').css({width: handle});
$('div.fakeHandle').css({width: handle});
if (productWidth > scrollBar)
{
$('div.slideWidget').each(function () {
var ul = $('ul.sliderList', this);
var fake = $('.fakeHandle', this);
var slider = $('.slider', this).slider({
handle: '.handle',
minValue: 0,
maxValue: productWidth,
slide: function (ev, ui) {
ul.css('left', '-' + Math.round(ui.value / 100 * (productWidth - 657)) + 'px');
fake.css('left', Math.round(ui.value / 100 * (scrollBar - handle)) + 'px');
},
stop: function (ev, ui) {
move = ui.value / 100 * (productWidth - 657);
ul.animate({ 'left' : '-' + Math.round(move) + 'px' }, 500);
fake.animate({'left' : Math.round(ui.value / 100 * (scrollBar - handle)) + 'px'}, 500);
}
});
})
}
else {
$('.fakeHandle').css({width: 0});
$('.handle').css({width: 0});
$('.fakeHandle').css('visibility', 'hidden');
$('.handle').css('visibility', 'hidden');
}
});
Notice the versions of jQuery and UI. 1.7.1 has issues with this solution. As of yet, I haven’t found a fix.
that is pretty badass.