<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Martini Lab Blog &#187; jQuery</title>
	<atom:link href="http://www.martinilab.com/blog/tag/jquery/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.martinilab.com/blog</link>
	<description>Web design, CSS, scripting, Adobe, tips and other scraps of things that come my way</description>
	<lastBuildDate>Thu, 09 Sep 2010 15:06:18 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>WebKit bug with Lazy Load plug-in</title>
		<link>http://www.martinilab.com/blog/243/webkit-bug-with-lazy-load-plug-in/</link>
		<comments>http://www.martinilab.com/blog/243/webkit-bug-with-lazy-load-plug-in/#comments</comments>
		<pubDate>Thu, 09 Sep 2010 15:06:18 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[bug]]></category>
		<category><![CDATA[iPad]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Mobile Safari]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=243</guid>
		<description><![CDATA[WebKit has a bug that affects Safari, Chrome, and Mobile Safari1. Images still load even when you manipulate remove the src attribute in JavaScript. First, let me state that Lazy Load is a great plug-in! I think it’s a very &#8230; <a href="http://www.martinilab.com/blog/243/webkit-bug-with-lazy-load-plug-in/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p class="firstLetter">WebKit has a bug that affects Safari, Chrome, and Mobile Safari<sup>1</sup>. Images still load even when you manipulate remove the <code>src</code> attribute in JavaScript.</p>
<p>First, let me state that <a href="http://www.appelsiini.net/projects/lazyload">Lazy Load</a> is a great plug-in! I think it’s a very well written, simple and clever plug-in.  It does what it is supposed to do perfectly. The bug isn’t with the plug-in, but rather with WebKit (and possibly jQuery).</p>
<h3>Some background info</h3>
<p>Zeus Comics’ checklist page lists a few hundred new items each week.  Each item has an image associated with it. So, rather than have the page load two to three megabytes of image data, I use the plug-in.  Works great, except for WebKit.  Safari and Chrome both load all the images because of this bug.</p>
<h3>It gets worse</h3>
<p>Either Mobile Safari or jQuery (on Mobile Safari) has an addition bug.<sup>2</sup> <code>.offset()</code> returns the wrong value.  So on the iPhone and iPad, the image’s <code>.offset()</code> value keeps increasing as you scroll down, thus it never is seen as being “above the fold” and doesn’t get triggered.  Subtracting <code>$(window).scrollTop()</code> will fix that, but you also need to subtract the height of the image of Lazy Load won’t trigger the image until it’s fully above the fold (or maybe not, and I simply screwed something up).</p>
<pre>
$.belowthefold = function(element, settings) {
        if (settings.container === undefined || settings.container === window) {
                var fold = $(window).height() + $(window).scrollTop();
        } else {
                var fold = $(settings.container).offset().top + $(settings.container).height();
        }
        <strong>return fold &lt;= ($(element).offset().top - $(element).height() - $(window).scrollTop()) - settings.threshold;</strong>
};
</pre>
<p>Unfortunately, this doesn’t solve for all of the images loading. The only way to prevent that from happening is to detect the browser on the server side and change the src to your place holder image.</p>
<pre>
&lt;?php
if( ($this-&gt;agent-&gt;browser() == 'Safari') || ($this-agent-&gt;browser() == 'Opera') )
{
        $image = $placeholder;
        $data_image =  $row-&gt;image;
}
else
{
        $image = $row-&gt;image;
        $data_image = $row-&gt;image;
}
?&gt;

&lt;img src="&lt;?php echo $image; ?&gt;" data-src="&lt;?php echo $data_image; ?&gt;" &gt;
</pre>
<p><em>*<code>data-src</code> replaces <code>original</code> in the script.  The only reason for that was, frankly, Discus uses this convention in their image scripts and I simply liked it better.  Also, if you are using CodeIgniter to detect browsers like I am, you will need to update your <a href="http://bitbucket.org/ellislab/codeigniter/src/tip/application/config/user_agents.php">user agent</a> file.</em></p>
<p>Since <code>src</code> is now a place holder, these code snippets can be removed.</p>
<pre>
if (undefined == $(self).attr("original")) {
        $(self).attr("original", $(self).attr("src"));
}
</pre>
<pre>
if (settings.placeholder) {
        $(self).attr("src", settings.placeholder);
} else {
        $(self).removeAttr("src");
}
</pre>
<p>Add the following to <code>self.loaded = true;</code></p>
<pre>
if ( $(self).attr('src') != $(self).attr('data-src') ) {
        $(self).attr('src', $(self).attr('data-src'));
}
</pre>
<p>In a perfect–bug free–world, we wouldn’t have to rely on servers to detect browsers and call specialized scripts.  It’s a bit like having to write separate a CSS file for Internet Explorer. <em>Yuck!</em> I did this because, the original script works fine except in two specific cases. While the WebKit bug still exists, the server has to set <code>src</code> to a placeholder image.</p>
<p>See it action <a href="http://www.zeuscomics.com/checklist">here</a>.</p>
<p><em>As with any code I publish here, I do apologize for my bad coding, especially in this case for butchering Mika Tuupola’s excellent work.</em></p>
<p><a href="https://bugs.webkit.org/show_bug.cgi?id=6656">1.</a> Bug 6656 — Image loading continues when IMG elements or Image JavaScript objects are removed<br />
<a href="http://forum.jquery.com/topic/offset-returns-incorrect-values-in-ipad">2.</a> offset() returns incorrect values in iPad</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/243/webkit-bug-with-lazy-load-plug-in/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mobile Safari needs a little jQuery .live() love</title>
		<link>http://www.martinilab.com/blog/209/mobile-safari-needs-a-little-jquery-live-love/</link>
		<comments>http://www.martinilab.com/blog/209/mobile-safari-needs-a-little-jquery-live-love/#comments</comments>
		<pubDate>Wed, 11 Aug 2010 22:53:08 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[hacks]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=209</guid>
		<description><![CDATA[file under: the importance of device testing Just a quick note about how Mobile Safari gets along with .live(). As a matter of coding style, I usually assign my actions to links and buttons. Sometimes I don’t. &#60;li&#62;Title of Something &#8230; <a href="http://www.martinilab.com/blog/209/mobile-safari-needs-a-little-jquery-live-love/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><em>file under: the importance of device testing</em></p>
<p>Just a quick note about how Mobile Safari gets along with <code>.live()</code>.  As a matter of coding style, I usually assign my actions to links and buttons.  Sometimes I don’t.</p>
<pre>
&lt;li&gt;Title of Something &lt;span class="more"&gt;&lt;/span&gt;&lt;/li&gt;
</pre>
<p>This code won’t get followed by a bot to show more info about the list item.</p>
<pre>
$('.more').click(function () {
    showMore();
});
</pre>
<p>Now I have an event assigned.  However, if this list item is added dynamically…</p>
<pre>
$(myListItem).appendTo('ul');

$('.more').live('click', function () {
    showMore();
});
</pre>
<p>Works great, except on Mobile Safari.  The workaround is <strong>really</strong> hacky though.  If this ‘more’ were a link (<code>&lt;a href="#" class="more"&gt;&lt;/a&gt;</code>), there wouldn’t be a problem, just like adding <code>onclick=""</code>.</p>
<pre>
&lt;li&gt;Title of Something &lt;span class="more" onclick=""&gt;&lt;/span&gt;&lt;/li&gt;
</pre>
<p>Problem Solved.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/209/mobile-safari-needs-a-little-jquery-live-love/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Introducing scribbr.com</title>
		<link>http://www.martinilab.com/blog/149/introducing-scribbrcom/</link>
		<comments>http://www.martinilab.com/blog/149/introducing-scribbrcom/#comments</comments>
		<pubDate>Sun, 07 Jun 2009 22:51:26 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[canvas]]></category>
		<category><![CDATA[CodeIgniter]]></category>
		<category><![CDATA[html]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[twitter]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=149</guid>
		<description><![CDATA[Scribbr is a tiny web app for drawing and posting to twitter. With jQuery, the user can control the color, size, opacity of the cursor to draw on the canvas element 320 x 320 in size. Last summer, I had &#8230; <a href="http://www.martinilab.com/blog/149/introducing-scribbrcom/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://scribbr.com">Scribbr</a> is a tiny web app for drawing and posting to twitter.   With jQuery, the user can control the color, size, opacity of the cursor to draw on the <code>canvas</code> element 320 x 320 in size.</p>
<p>Last summer, I had the opportunity to work with Eisner Award winning cartoonist Scott Kurtz of <a href="http://www.pvponline.com">PVP</a>. During one conversation we had, he came up with a punch-line to what we were talking about.  I don’t remember what the subject was, but the punch-line involved a mock editorial cartoon.  He grabbed a 2″ x 2″ sticky note pad and drew the cartoon.  We had a laugh.</p>
<p>This would never happen online.  At least, not in any convenient, simple, method that I was aware of.</p>
<p>Right now, it’s very crude.  The controls are, admittedly, non-intuitive. There is no ‘Undo’ other than clearing the canvas and starting over. It doesn’t support older browsers either.</p>
<p>I’m actually quite okay with that last part. This site was immensely fun to build. Getting it to work on IE6 would have taken away from that.  Right now, I know that it works on FireFox 3.0, Safari 4.0b (or WebKit nightly build), and Google Chrome 2.0.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/149/introducing-scribbrcom/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tackling Safari’s slow gif “feature” with jQuery</title>
		<link>http://www.martinilab.com/blog/75/tackling-safaris-slow-gif-feature-with-jquery/</link>
		<comments>http://www.martinilab.com/blog/75/tackling-safaris-slow-gif-feature-with-jquery/#comments</comments>
		<pubDate>Fri, 17 Apr 2009 23:19:36 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[adobe]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[hacks]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[Photoshop]]></category>
		<category><![CDATA[tips]]></category>
		<category><![CDATA[usability]]></category>
		<category><![CDATA[WebKit]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=75</guid>
		<description><![CDATA[Safari’s WebKit has a funny behavior of displaying animated gif files at a lower frame rate than Firefox. From what I can tell, WebKit (including Google Chrome) caps the animated gif frame rates at 10fps. While it may be faster &#8230; <a href="http://www.martinilab.com/blog/75/tackling-safaris-slow-gif-feature-with-jquery/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Safari’s WebKit has a funny behavior of displaying animated gif files at a lower frame rate than Firefox. From what I can tell, WebKit (including Google Chrome) caps the animated gif frame rates at 10fps. While it may be faster than some IE6 browsers, it’s not exactly helpful for those files with higher frame rates.</p>
<p>For example, the popular ajax loader file that hails from Apple’s own <a href="http://developer.apple.com/documentation/userexperience/Conceptual/AppleHIGuidelines/XHIGControls/XHIGControls.html">asynchronous progress indicator</a>, has 12 points and loops around once a second.  Well, for one thing, 12fps is really choppy, but it already plays slower on Apple’s nifty browser.</p>
<h2>jQuery to the rescue</h2>
<p>Did you know that jQuery can control css background positions? Are you thinking what I’m thinking?<br />
<span id="more-75"></span></p>
<h2>But first, back to drawing board</h2>
<p>So the first part was to break up the animated gif frames and spread them out across a very long canvas. Since Photoshop no longer opens animated gif files (unless you own ImageReady somewhere), Fireworks can open the file. Use Fireworks to export the frames to individual files and THEN Photoshop can import all the files as a stack.</p>
<p>File <code>&lt;Scripts&gt;</code> Load Files into Stack…</p>
<p>For our ajax loader, expand all the layers next to each other and then save it out to its own gif.  The file size won’t be that difference in fact.</p>
<h2>Now let’s get back to jQuery</h2>
<p>In our html, just set the div with a width and height to match your gif to prevent clipping and let the script do the rest.</p>
<pre lang="javascript" line="1">

$(document).ready(function(){
	var bgimage = 'url(images/ajax-loader-long.gif)';
	var frames = 24;
	var mpf = parseInt(1000/frames)  // how many miliseconds in each frame
	var currentFrame = 0;
	var offset = 0;

	$('.animate').css('background-image', bgimage);

	function animate() {
		offset = currentFrame * 64;
		$('.animate').css('background-position', '-' + offset + 'px 0px');
		currentFrame = (currentFrame == frames) ? 1 : currentFrame + 1;
	}

	setInterval(animate, mpf);
});
</pre>
<p>Now we have a script that will snap the position of the background over each “frame” at the correct frame rate. Actually, the frame rate is not divisible by 1000, but it’s close and it’s not like you could tell.</p>
<p>The only problem with this script is that it’s constantly running. If you look at the page in Firebug, your going to see the code whipping around with new values all the time. Annoying! Also, chances are your page isn’t going to need some animation playing constantly. Ajax loaders are intermittently called when a simple action is called as a… asynchronous progress indicator! Who knew.</p>
<p>So we’re going to put in a cancel command to this script: <strong>clearTimeout</strong>.</p>
<pre lang="javascript" line="1">

$(document).ready(function(){
	var bgimage = 'url(images/ajax-loader-long.gif)';
	var frames = 24;
	var mpf = parseInt(1000/frames)  // how many miliseconds in each frame
	var currentFrame = 0;
	var offset = 0;
	var runAnimate = 0;

	$('.animate').css('background-image', bgimage);

	function animate() {
		offset = currentFrame * 64;
		$('.animate').css('background-position', '-' + offset + 'px 0px');
		currentFrame = (currentFrame == frames) ? 1 : currentFrame + 1;
	}

	runAnimate = setInterval(animate, mpf);

	$('.animate').toggle(
		function () {
			clearTimeout(runAnimate);
		},
		function () {
			runAnimate = setInterval(animate, mpf);
		}
	);
});
</pre>
<p>Add in a toggle so show how to turn on and off the animation and were done.</p>
<p>It even works on ie6!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/75/tackling-safaris-slow-gif-feature-with-jquery/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>jQuery missing layerX</title>
		<link>http://www.martinilab.com/blog/68/jquery-missing-layerx/</link>
		<comments>http://www.martinilab.com/blog/68/jquery-missing-layerx/#comments</comments>
		<pubDate>Wed, 08 Apr 2009 18:41:46 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[event]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[tips]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=68</guid>
		<description><![CDATA[Getting the Mouse position in jQuery is very simple. However, getting the position from within an element take some extra steps. Normally, a javascript event such as onmousemove would use event.layerX and event.layerY to get the coordinates of our mouse &#8230; <a href="http://www.martinilab.com/blog/68/jquery-missing-layerx/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Getting the Mouse position in jQuery is very simple.  However, getting the position from within an element take some extra steps.</p>
<p>Normally, a javascript event such as <code>onmousemove</code> would use <code>event.layerX</code> and <code>event.layerY</code> to get the coordinates of our mouse when it moves.</p>
<p>For jQuery, <code>pageX</code> and <code>pageY</code> will return values from the top left of the page and not the layer.  Unless the element you’re working with is left-aligned, you need to calculate the offset of that element as well.  Not only that (unless there is a better way to do this) the offset values returned doesn’t include the offset values of the parent tag.</p>
<p>In this example, I have two divs.</p>
<pre lang="html4strict" line="1">
&lt;div id="frame"&gt;
	&lt;div id="content"&gt;&lt;/div&gt;
&lt;/div&gt;
</pre>
<p>And you can imagine that <code>#frame</code> is centered and has padding. Getting <code>#content</code>’s offset won’t include anything outside of the frame so <code>$(#content).offset().left;</code> would only get you the distance of itself and its parent.</p>
<h2>Get your layerX for jQuery</h2>
<p>For this example, we are using a known set of divs so we can just write out each div in this script.</p>
<pre lang="javascript" line="1">

$(document).ready(function(){
	$('#canvasForeground').mousemove(function(e){
		var myLayerX = $('#content').offset().left + $('#frame').offset().left;
		var myLayerY = $('#content').offset().top + $('#frame').offset().top;
		var x = 0, y = 0;
		x = e.pageX - myLayerX;
		y = e.pageY - myLayerY;
	});
});
</pre>
<p>The variables are declared inside the event function to handle window resizing.  Now were this a more complicated page and the element in question has deeply nested or even dynamically generated, writing our all the parent tags wouldn’t necessarily work (or be very efficient).* But the approach would remain the same.  That’s it!</p>
<p>*<em>standard apology for poor code writing skills</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/68/jquery-missing-layerx/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>jQuery Triptych Slideshow</title>
		<link>http://www.martinilab.com/blog/54/jquery-triptych-slide-show/</link>
		<comments>http://www.martinilab.com/blog/54/jquery-triptych-slide-show/#comments</comments>
		<pubDate>Wed, 01 Apr 2009 23:58:04 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=54</guid>
		<description><![CDATA[My previous how-to showed a new way to look at the slider function featured at the Apple.com/mac site. This one goes to Apple once again and their bucket ads shown in the iTunes Music Store. The buckets are what I’m &#8230; <a href="http://www.martinilab.com/blog/54/jquery-triptych-slide-show/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>My previous how-to showed a new way to look at the slider function featured at the Apple.com/mac site. This one goes to Apple once again and their bucket ads shown in the iTunes Music Store. The buckets are what I’m calling them. They are the three spots at the top of the music store which rotate graphics like the slideshow functions of many galleries. The difference is that these three take turns.</p>
<p>I was able to replicate this function using Scriptaculous on our beloved <a href="http://www.zeuscomics.com">comic shop</a> website. But after a few years of constantly adding duct tape to the site, it was time to move everything to a single framework. And that mean making this triptych banner script run on jQuery.  Hopefully without a lot of html recoding.<br />
<span id="more-54"></span></p>
<h2>Make your list</h2>
<p>For the html, set your divs with a ‘slide’ class and a position class.  I guess you can do this in an unordered list, but whatever.</p>
<pre lang="html4strict" line="1">
&lt;div class="slide pos1"&gt;&lt;a href="link1"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="slide pos2"&gt;&lt;a href="link2"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="slide pos3"&gt;&lt;a href="link3"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;

&lt;div class="slide pos1"&gt;&lt;a href="link4"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="slide pos2"&gt;&lt;a href="link5"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="slide pos3"&gt;&lt;a href="link6"&gt;&lt;img src=""&gt;&lt;/a&gt;&lt;/div&gt;
</pre>
<h2>Set your positions</h2>
<p>The easiest solution is to make the position classes absolute.</p>
<pre lang="css" line="1">

.pos1 {
    position: absolute;
    left: 0px;
    top: 0px;
}

.pos2 {
    position: absolute;
    left: 210px;
    top: 0px;
}

.pos3 {
    position: absolute;
    left: 420px;
    top: 0px;
}
</pre>
<h2>Good news, everyone! This script doesn’t need a plug-in.</h2>
<p>I always start my script codes with an apology, so why stop now.  I’m sorry.</p>
<p>The first part makes sure that the banners appear in the order that their written in html.  Because of the absolute positioning, however, they stack on top of each other and the last will be on top.</p>
<p>Also, I didn’t like how <code>i</code> in jQuery’s <code>each()</code> function started at 1 and not 0, but that’s just me.</p>
<pre lang="javascript" line="1">

$(document).ready(function(){
    var slideCount = $('.slide').size() - 1;
    $('.slide').each(function(i) {
        $(this).css('z-index', slideCount - i);
    });

    var currentSlide = slideCount;
    var zIndex = 0;

    function imgRotate() {
        $('.slide').each(function(i) {
            if($(this).css('z-index') == currentSlide)
            {
                $(this).fadeOut('slow', function() {
                    $(this).css('z-index', (0));
                    $(this).show();
                });
            }
            else
            {
                zIndex = $(this).css('z-index');
                zIndex = parseInt(zIndex) + parseInt(1);
                $(this).css('z-index', (zIndex));
            }
        });
    }

    setInterval(imgRotate, 4000);

});
</pre>
<p>This script should be able to make with any number of banners and any number of sets.  This example just used a set of three, but your can make it a set of two, or four, or ten!  Just make the list divisible by the set and fix your positions of course.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/54/jquery-triptych-slide-show/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A better jQuery slider</title>
		<link>http://www.martinilab.com/blog/25/a-better-jquery-slider/</link>
		<comments>http://www.martinilab.com/blog/25/a-better-jquery-slider/#comments</comments>
		<pubDate>Wed, 25 Mar 2009 22:09:51 +0000</pubDate>
		<dc:creator>Chris Williams</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[jQuery]]></category>

		<guid isPermaLink="false">http://www.martinilab.com/blog/?p=25</guid>
		<description><![CDATA[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 &#8230; <a href="http://www.martinilab.com/blog/25/a-better-jquery-slider/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>If you haven’t seen Apple’s product <a href="http://www.apple.com/mac/">slider</a> 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.</p>
<p>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.</p>
<p>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.<br />
<span id="more-25"></span></p>
<h2>The Problem</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>The Solution</h2>
<p>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.</p>
<p>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.</p>
<h2>HTML</h2>
<p>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.</p>
<pre lang="html4strict" line="1">
&lt;div id="mlSlideWidget" class="slideWidget"&gt;
	&lt;div id="sliderWindow" class="sliderGallery"&gt;
		&lt;ul class="sliderList"&gt;
			&lt;!-- Each category --&gt;
			&lt;li class="group"&gt;
				&lt;div class="groupContent"&gt;
					&lt;!-- Description info for category --&gt;
					&lt;p&gt;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.&lt;/p&gt;
					&lt;ul class="groupButtons"&gt;
						&lt;!-- Each product for this category --&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 1&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 2&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 3&lt;/a&gt;&lt;/li&gt;
					&lt;/ul&gt;
				&lt;/div&gt;
			&lt;/li&gt;
			&lt;li class="group"&gt;
				&lt;div class="groupContent"&gt;
					&lt;p&gt;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.&lt;/p&gt;
					&lt;ul class="groupButtons"&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 4&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 5&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 6&lt;/a&gt;&lt;/li&gt;
					&lt;/ul&gt;
				&lt;/div&gt;
			&lt;/li&gt;
			&lt;li class="group"&gt;
				&lt;div class="groupContent"&gt;
					&lt;p&gt;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.&lt;/p&gt;
					&lt;ul class="groupButtons"&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 7&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 8&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 9&lt;/a&gt;&lt;/li&gt;
					&lt;/ul&gt;
				&lt;/div&gt;
			&lt;/li&gt;
			&lt;li class="group"&gt;
				&lt;div class="groupContent"&gt;
					&lt;p&gt;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.&lt;/p&gt;
					&lt;ul class="groupButtons"&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 10&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 11&lt;/a&gt;&lt;/li&gt;
						&lt;li class="singleButton"&gt;&lt;a href="#"&gt;Product 12&lt;/a&gt;&lt;/li&gt;
					&lt;/ul&gt;
				&lt;/div&gt;
			&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/div&gt;

	&lt;div id="scroll" class="slider"&gt;
		&lt;!-- the handler to action the slide --&gt;
		&lt;div class="handle"&gt;&lt;/div&gt;
		&lt;!-- labels appear against the slider, as pointers to the user --&gt;
		&lt;ul&gt;
			&lt;!-- These are categories --&gt;
			&lt;li class="marker"&gt;Category 1&lt;/li&gt;
			&lt;li class="marker"&gt;Category 2&lt;/li&gt;
			&lt;li class="marker"&gt;Category 3&lt;/li&gt;
			&lt;li class="marker"&gt;Category 4&lt;/li&gt;
		&lt;/ul&gt;
		&lt;div class="fakeHandle"&gt;
			&lt;table width="100%" cellpadding="0" cellspacing="0" border="0"&gt;
				&lt;tr&gt;
					&lt;td width="15"&gt;&lt;img src="images/handle-left.png" width="15" height="115" alt="&lt;" /&gt;&lt;/td&gt;
					&lt;td background="images/handle-middle.png"&gt;&nbsp;&lt;/td&gt;
					&lt;td width="15"&gt;&lt;img src="images/handle-right.png" width="15" height="115" alt="&gt;" /&gt;&lt;/td&gt;
				&lt;/tr&gt;
			&lt;/table&gt;
		&lt;/div&gt;
	&lt;/div&gt;

&lt;/div&gt;
</pre>
<h2>CSS</h2>
<p>I threw in a dash of <a href="http://developer.yahoo.com/yui/">YUI</a>’s css reset. Most of the id and class names are generic.  I would recommend renaming these to something more specific to avoid conflict.</p>
<pre lang="css" line="1">
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;
}
</pre>
<p>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.</p>
<p>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.</p>
<h2>Javascript</h2>
<p>Try not to laugh at my n00b code.  I’m sure there are much better ways to write this.</p>
<pre lang="javascript" line="1">

//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');
	}

});
</pre>
<p>Notice the versions of jQuery and UI.  1.7.1 has issues with this solution.  As of yet, I haven’t found a fix.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.martinilab.com/blog/25/a-better-jquery-slider/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
