Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

Adding to Our Leopard Desktop with jQuery

by
Gift

Want a free year on Tuts+ (worth $180)? Start an InMotion Hosting plan for $3.49/mo.

Last Week I got you all to create a neat looking Dashboard/Desktop. You guys are gonna totally FLIP when you hear what's in this jam packed tutorial! More focus on the Dashboard (I swear it's cooler than it sounds, and requires a lot of code), and I'll even go into how to create a stack (seperate from the dock, sorry jqDock doesn't like nestled <ul>s), and some extra little bits to make it all click.

Preface

As with the previous tutorial, I must note this disclaimer! I don't own any of the images used in this tutorial, nor do you. They are copyright to their vendors, whether it be Apple, inc, Adobe, etc. Using icons is a bit of an integrity issue, so don't abuse it!

Secondly, a new jQuery.UI file will replace the draggable js file. This is basically all the interaction packs. Download jQuery.UI code. You'll also need the final product of last week's tutorial! Make sure you expand that into it's own directory! We'll be adding onto that. A whole lot if images are needed too. New Images. Make sure you expand that zip into the 'images' directory, so that any new folders are merged with their counter parts from last week. I apologise for the confusion with this. Stupid file structure, my bad. So. Files that need adding:

Just the same, the jQuery.ui links need editing.

<script src="js/jquery.ui.interaction.min.js" type="text/javascript"></script>

Plan of Attack

Though it might not look like it, a whole lot of code is needed for these few things:

  1. Stacks
  2. Dashboard
    1. Opening/Closing the Adding Widgets Panel
    2. Dragging Widgets onto the Dashboard list
    3. Closing Widgets
  3. Some extra bits (improving dock, adding desktop items)

Changes

Just before we start, I really do apologise, but there were a few things that needed changing from last week. the #dock css should read:

#dock{
	position: fixed;
	margin: 0 auto;
	bottom: 38px;
	left: 40%;
	z-index: 0;
	list-style: none;
}

The #closeZone's zIndex in dashboard.js on line 24 should be 99 not 9999

Step 1 - Stacks

So lets dive right into it, and start with Stacks. Or more a Stack. Unfortunately due to the way that jqDock works, it's impossible to nestle stacks within the jqDock without editing the core js, which is much further than this tutorial intends. So instead, we'll be creating a stack to the bottom right of the page. The harder parts of coding stacks is a) the incrementing height for each item, and the curve. Luckily, a loop combined with maths can do this hard work for us.

Step 1:1 - HTML

Lets begin with adding the HTML structure of the stack. Now due to the nature of the stack, if you wish to use it on a different website, you can! Basically anything inline within the <li>s work. The positioning just needs tweaking. Regardless, we use spans (and you could always wrap the images in <a>s!

<div class="stack">
	<img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/stack.png" alt="stack"/>
	<ul id="stack1">
		<li><span>Acrobat</span><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/adobeAcrobat.png" alt="" /></li>
		<li><span>Aperture</span><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/aperture.png" alt="" /></li>
		<li><span>Photoshop</span><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/photoshop.png" alt="" /></li>
		<li><span>Safari</span><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/safari.png" alt="" /></li>
		<li><span>Finder</span><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/finder.png" alt="" /></li>
	</ul>
</div>

The first image is the folder placeholder. This is what activates the dock, so it's necessary. (When we use the jQuery selectors, however, I'm sure you could use :first to get the first dock item if you /really/ don't want a containing basket).

Step 1:2 - CSS

Contrary to the first tutorial, I'm going to include the CSS and jQuery for each step, just so the design doesn't muddle up completely. Open up style.css from last week and add to the bottom:

.stack{
	position: absolute;
	bottom: 0;
	right: 100px;
}

.stack ul{
	list-style: none;
	position: absolute;
	top: -30px;
	z-index: -9;
}

.stack ul li{
	position: absolute;
}

.stack ul li span{
	display: none;
}

/*I'm for the jquery*/
.stack .openStack li span{
	display:block;
	position:absolute;
	top: 17px;
	right:60px;
	height: 14px;
	line-height: 14px;
	border: 0;
	background-color:#000;
	color: #fcfcfc;
	text-align: center;
	opacity: .85;
	padding: 3px 10px;
	border-radius: 10px;
	-webkit-border-radius: 10px;
	-moz-border-radius: 10px;
	opera-border-radius: 10px;
	text-shadow: #000 1px 1px 1px;
}

Your stack will now look like a closed stack, but you can't open it. This just stacks (hah, no pun intended) all the icons on top of each other, so they're compressed into a small square. The last selector is for the jQuery. When the stack is opened, the class 'openStack is added to the ul. I apologise to those CSS3 haters, It's the fastest most efficient way to get it right in most modern browsers.

Step 1:3 - jQuery

In plain english, the stack needs to open when the img is clicked, pushing each (hint...) li up in incrememnts, and to the right a little, whilst resizing to a smaller size. Then when clicked again, everything returns to normal.

$('.stack>img').toggle(function(){
	//this function, for each element increases the top position to by 50px,
	//and across using, the equation: value = (value+1)*2. Both variables
	//start at 0.
	
}, function(){
	//this one just reverses the above.
	
});

The second function is easy, but the first is a pain.

var vertical = 0;
var horizontal = 0;
$('~ul>li'this).each(function(){
	$(this).animate({top: '-' +vertical + 'px', left: horizontal + 'px'}, 300);
	vertical = vertical + 50;
	horizontal = (horizontal+1)*2;
});
$('~ul', this).animate({top: '-50px', left: '10px'}, 300).addClass('openStack');
$('~ul>li>img', this).animate({width: '50px', marginLeft: '9px'}, 300);

Woo, jampacked with string interrupting, variables and math. Interesting selectors, huh? The ~ is 'siblings of' Erg. Math. Let me explain. The first 2 variables are for the vertical position and the horizontal position (curve).

Top incrementing is the same each time, where as unless you want a horizontal straight line, each horizontal position has to be slightly more than the rest. In this case, it increases by the previous number plus one, times 2. so it'll go 2, 6, 14, 30, 62, 126, etc. I know they're weird numbers, but it works. Use any equation you like!

The 'each' function is similar to, say a WordPress loop. This function happens every time the next element is used. The equation 'value = (value+1)*2', means 'new value equals old value plus one, then this times two.

The first animate line adds the variables (within the plus) every time it's looped via string splitting. The last two lines are just the size. The other half of the toggle function, just resets everything back to normal:

$('~ul', this).removeClass('openStack').children('li').animate({top: '20px', left: '-10px'}, 300);
$('~ul>li>img', this).animate({width: '79px', marginLeft: '0'}, 300);

Simple! Now your jQuery stacks will successfully animate, even curving! Unfortunately rotation is a tad more difficult. Though when HTML5 comes out in 2022 (-_-) the canvas tag might have full support for that.

Step 2 - Dashboard

So we're gonna add to the Dashboard a bit. Firstly, an Add Widgets Panel (Wont do the actual adding til later). After that, closing of the widgets will be possible when this panel is open. Finally, being able to add your own widgets from this panel. Uses some very different selecting methods. Adding the widgets also covers Droppables extensively, as the drop function is rather large.

Step 2:1 - Add Widget Panel

Firstly, the HTML. Add this just before the closing #dashboardWrapper div.

<div id="addWidgets">
<span id="openAddWidgets">Add/remove widgets</span>
<div id="dashPanel">
    <ul>
    	<li><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/widgets/thumbs/sticky.png" alt="" id="sticky" class="widgetThumb" /><span>Sticky</span></li>
    	<li><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/widgets/thumbs/clock.png" alt="" id="clock" class="widgetThumb" /><span>Clock</span></li>
    	<li><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/widgets/thumbs/weather.png" alt="" id="weather" class="widgetThumb" /><span>Weather</span></li>
    </ul>
</div>
</div>

The 'openAddWidgets' is the little cross/plus that opens and closes the Panel. The list items are the available widgets (create as many as you'd like!). The images you downloaded are the little thumbs. These will become draggables, and you will be able to drop them onto the #closeZone and eventually, widgets appended to the #widgets list.

At the moment, this looks like a bit of a mess;

But with some CSS we'll fix that right up.

#addWidgets{
	position: absolute;
	z-index: 9999;
	bottom: 0;
	left: 0;
	width: 96%;
	height: 164px;
	background: url(images/dashpanel.png) bottom repeat-x;
	padding: 0 2%;
}

#openAddWidgets{
	display: block;
	width: 36px;
	height: 36px;
	background: url(images/opendashpanel.png) center;
	position: relative;
	z-index: 9999;
	text-indent: -9999em;
}

#dashPanel ul{
	list-style: none;
	margin-top: 27px;
}

#dashPanel ul li{
	float: left;
	padding-right: 30px;
}

#dashPanel ul li img{
	display: block;
}

#dashPanel ul li span{
	width: 74px;
	display: block;
	text-align: center;
	font-weight: bold;
	text-shadow: #fff 1px 0 1px;
	color: #17243e;
	padding-top: 10px;
}

Heavy on with positioning, z-indexing and floating, this should gather an effect like this (Panel's there, not hidden):

Finally, the jQuery to hide and show it. Add the following under the comment '//draggables definition' (for the sake of organisation):

$('#addWidgets ul li img').draggable({helper: 'clone'});

And add this under the '//initial hiding of dashboard + addition of 'closeZone'' block:

//initial hiding of #dashPanel and addable widgets
$('#addWidgets').css({bottom: '-118px'});

Now for the toggle code. In english, when the 'open' button is clicked, slide the panel up. When it's clicked again, slide the panel down. Let's start with the toggle.

//open/closing of the dashpanel
$('#openAddWidgets').toggle(function(){
	//this opens the dashboard, animation and all
	
}, function(){
	//opposite to above
	
});

Thus the opening function will be in the first gap, whilst the closing in the second. The first:

$(this).css({background: 'url(images/closedashpanel.png)'});
$('#addWidgets').animate({bottom: '0px'}, 500);

And the second, reversing above:

$(this).css({background: 'url(images/opendashpanel.png)'});
$('#addWidgets').animate({bottom: '-118px'}, 500);

Finally, like Leopard, it should close when the user returns to the Desktop, right? Add this to //#closeZone's job: closing the Dashboard' function (within it!):

$('#openAddWidgets').css({background: 'url(images/opendashpanel.png)'});
$('#addWidgets').animate({bottom: '-118px'}, 500);

Now if you click the little plus in the bottom left when the Dashboard is open, it should animate! Awesome!

Step 2:2 - Adding Widgets to the Dashboard List

This proved a beast and a half. Lots of code for this... Yay! Luckily, It's only jQuery! Let's start by defining the Droppable; #closeZone. Place this under the Draggables definitions:

//droppable definition
$('#closeZone').droppable({
	accept: '.widgetThumb',
	drop: function(ev, ui){
			
	}
});

Basically, #closeZone can now accept the thumbs in the Panel as droppables, and we're about to delve into what happens on the drop.

In understandable language, this is how it goes. Variables for the mouse position need to be found so the position of the drop can be where we want it. Another variable for the type of widget to append is needed. On drop, the Widget needs to be appended, a different image depending on the widgetType variable. Now to be different, the stickies will be editable (No way!). A textarea will be appended, to allow writing. Because all the draggable definitions happen on the load of the document, they'll need to be re-defined every time a widget is appended to the DOM, so that it's applied to the newest one of such.

We'll start with the variables.

var x = ev.clientX - 100;
var y = ev.clientY - 50;
var widgetType = $(ui.draggable).attr('id');

Sadly we can't get the width and height of the image that's about to be appended too easily (to center the drop). So instead we need to guess, by offsetting the position of the mouse by 100 and 50, so it's not in the top left. The JavaScript variables 'cleintX' and 'clientY' are basically the mouse position. And that interesting selector; ui.draggable, is the element that has just been dragged! Thanks jQuery.ui! Now for appendage:

$('#widgets').append('<li class="widget '+widgetType+'Widget" style="left: '+ x +'px; top: '+ y +'px;"><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/widgets/'+widgetType+'.png" alt="" /></li>');
$('.stickyWidget').append('<textarea></textarea>');//needed to add textarea to newest DOM member
$('.widget').draggable(); //needed to 'draggable' the newest DOM member

Allow me to explain how the variables work in the appending. To give a class to the new widget for customisation, adding "...'+widgetType+'Widget'..." will return a class similar to 'stickyWidget' or 'weatherWidget'. The inline style (so sorry it's inline! Don't shoot me!) determines the absolute position of the widget by the variables, which are of course the mouse coordinates. As I mentioned, the newest DOM members need any jQuery modifications or appendages [made on the document load] re-applied, as jQuery doesn't recognise new DOM members. For the last two lines, jQuery needs to append the textarea (so you can edit the text) and the new Widget needs to become a draggable.

In order for all this to work, some CSS is needed. Replace, in style.css, the '.widget' selector and attributes with:

.widget{
	position: absolute;
	z-index: 9999;
	float: left;
	margin: 1em;
	list-style: none;
}

.stickyWidget{
	padding: 15px 20px;
	width: 185px;
	height: 155px;
	background: url(images/widgets/sticky.png) no-repeat center;
}

.stickyWidget>img{
	display: none;
}

.stickyWidget textarea{
	height: 100%;
	width: 100%;
	background: 0;
	border: 0;
	outline: 0;
	font-size: 16px;
	font-family: 'Marker Felt';
	overflow: hidden;
}

This makes the sticky all looking like a sticky. The you either will or wont have the Marker Felt font, that's what the actually sticky widget uses. For the original widget to remain pretty, wrap the text, rather than in <p>s but with:

<textarea rows="10" cols="10">
...
<textarea>

And give the li an extra class of 'stickyWidget' to match the css (The li'll now have 2 classes).

All goes according to plan, you should now be able to a) edit stickies, and b) add new widgets to the Dashboard.

Step 2:3 - Closing widgets

Why give this part a whole section to itself? Because the workings of this are weaved into all of the previous functions, clicks, and appendages. So instead of being confused adding this throughout all the parts, why not keep it in one?

Right. So basically a small span'll be applied to widgets when the panel opens, and when a new widget is added to the Dashboard. When this is clicked, the parent widget will disappear! Awesome, huh? When the Add Widgets Panel closes, the crosses will disappear into the realms of .hide().

Working down the document for integrating the close button, we begin with the #closeZone's function. Underneath the #addWidget's disappearing act (code), add:

$('.closeWidget').hide();

Next up, within the droppable definition. This snippet of code will apply a close widget button and it's function to all widgets when a new one is dragged on. Underneath the newest draggable definition for the newly created widget (within the drop function), add:

$('.widget').append('<span class="closeWidget"><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/closebox.png" alt=""/></span>');
//click function of newest DOM element.
$('.closeWidget').click(function(){
	$(this).parent().animate({opacity: '0'}, 300).animate({left: '-9999em'},1);
});

Finally, the open/close panel function is where it really matters, as this will append the thing to all Widgets on open of the Panel (like Leopard). Below both animates, add respectively:

$('.widget').append('<span class="closeWidget"><img src="http://nettuts.s3.amazonaws.com/082_leopard2/images/closebox.png" alt=""/></span>');
//click function of newest DOM element.
$('.closeWidget').click(function(){
	$(this).parent().animate({opacity: '0'}, 300).animate({left: '-9999em'},1);
});

and

$('.closeWidget').hide();

Now, when the Panel is opened, a little clickable cross goes to the bottom right of the Widget, and when you drag a new Widget on, it'll seemingly duplicate. To fix all this, add this CSS:

.closeWidget{
	position: absolute;
	z-index: 99999;
	top: -5px;
	left: -5px;
}

And TADA! You now have widgets that close, and can reform when you want them too! Awesome stuff!

Step 3 - Some Extra Bits

This is really just for visuals sake, but we'll be adding a Desktop Item that you can create your own function when double clicked, and making the Dock a bit speedier.

Step 3:1 - Desktop Item

Add some HTML, make this the first thing after the opening #wrapper div:

<ul id="desktopItems">
	<li id="macintoschHD"><span>Macintosch HD</span></li>
</ul>

Give it some CSS to look snazzy:

#desktopItems{
	list-style: none;
	width: 100%;
	height: 100%;
}

#macintoschHD{
	background: url(images/macHD.png) no-repeat center top;
	padding-top: 128px;
	width: 138px;
	margin: 20px;
	text-align: center;
	position: absolute;
	right: 0;
	color: white;
	font-weight: bold;
	text-shadow: #000 1px 1px 2px;
}

And finally some jQuery to execute your double-click function (honestly, change the alert to whatever you please):

//Open finder from desktop item
$('#macintoschHD').dblclick(function(){
	alert("Hey... Gimme a break, I've worked hard at this!");
});

Step 3:2 - Improve the Dock

So last week some of you complained about the clunky Dock, and I maintain there isn't a lot I can do about it. However, to trick your eyes to think it's smooth, you can increase the speed. Simply change the jqDock declaration in dashboard.js to:

var jqDockOpts = {duration: 200};
$('#dock').jqDock(jqDockOpts);

And now you should have a faster dock!

Wrap Up

What a mighty tutorial to write... That was tough. But hey! We did it! I'll just use this space to note a few things from last week that came up in comments.

IE. That Bastard. Shame on jQuery too, for not being cross browser like it's meant to. I get the feeling from some of you that complain think my code is shoddy in the regard that it doesn't work in YOUR browsers that YOU code for. I wrote an article about it on my website discussing browser specific coders. Obviously I know that you should be adept at all browsers... But nobody's perfect.

Practability. Obviously, this is just meant to be fun. Stacks might be an option for a site, but ultimately it's meant to be fun. Let me quote a comment from last week (Not shameless promotion I promise!).

Thanks for taking the time to write this tutorial, jQuery is great and its nice to take some time as developers and have some fun with all the code libraries floating around. Lighten up people and have some fun with it, it isnt meant to be practical, just fun and inspiring. Great tut.

Regards,

Drew

I think that's it. Hope y'all (No I'm not TEXAN!) enjoyed this tutorial, wasn't too hard to keep up with, and are gonna get straight back into practical thinking right now!

  • Subscribe to the NETTUTS RSS Feed for more daily web development tuts and articles.


Advertisement