Collapsible Drag & Drop Panels Using jQuery

Posted in jQuery

Drag n Drop panels are great to let the user control how he/she wants to see the information as he can arrange various information blocks according to his preference. This type of functionality is often provided by web portals or personal homepage services like iGoogle.
iGoogle
Similarly, WordPress dashboard also lets user control how the various boxes are displayed.
WordPress Dashboard

Today, i am going to show you how to create collapsible, drag and drop panels easily using jQuery and jQuery UI libraries. Here’s what the final result will look like.

Collapsible Drag Drop Panels

Let’s begin with it.

Include The Libraries

We’ll be using the all powerful jQuery library and the jQuery UI libraries, so grab the latest version of both and add it to your page header.

The HTML Structure

We’ll be using two vertical columns defined by div with class="column" in this example that will hold our panels. You can increase the number of panels by adjusting the width in the stylesheet for .column. Each panel is a div with class="dragbox". And the content of each panel resides within div with class="dragbox-content".

Here’s the complete HTML structure for our example, i will be using 5 panels within two columns.

	<div class="column" id="column1">
		<div class="dragbox" id="item1" >
			<h2>Handle 1</h2>
			<div class="dragbox-content" >
				<!-- Panel Content Here --> 
			</div>
		</div>
		<div class="dragbox" id="item2" >
			<h2><span class="configure" ><a href="#" >Configure</a></span>Handle 2</h2>
			<div class="dragbox-content" >
				<!-- Panel Content Here -->
			</div>
		</div>
		<div class="dragbox" id="item3" >
			<h2>Handle 3</h2>
			<div class="dragbox-content" >
				<!-- Panel Content Here -->
			</div>
		</div>
	</div>
	<div class="column" id="column2" >
		<div class="dragbox" id="item4" >
			<h2>Handle 4</h2>
			<div class="dragbox-content" >
				<!-- Panel Content Here-->
			</div>
		</div>
		<div class="dragbox" id="item5" >
			<h2>Handle 5</h2>
			<div class="dragbox-content" >
				<!--Panel Content Here--> 
			</div>
		</div>
	</div>

You can add a link to configuration of a panel within the <h2> tag using <span class="configure">. This will show up when mouse hovers over the panel header.

Update
Each column now has a unique id and every panel also has a unique id, this will help in saving the order of panels if the user changes them.

The CSS Styles

Here are the CSS styles, you can modify it as you wish but remember to adjust the width of .column in case you increase the number of columns.

.column{
	width:49%;
	margin-right:.5%;
	min-height:300px;
	background:#fff;
	float:left;
}
.column .dragbox{
	margin:5px 2px  20px;
	background:#fff;
	position:relative;
	border:1px solid #ddd;
	-moz-border-radius:5px;
	-webkit-border-radius:5px;
}
.column .dragbox h2{
	margin:0;
	font-size:12px;
	padding:5px;
	background:#f0f0f0;
	color:#000;
	border-bottom:1px solid #eee;
	font-family:Verdana;
	cursor:move;
}
.dragbox-content{
	background:#fff;
	min-height:100px; margin:5px;
	font-family:'Lucida Grande', Verdana; font-size:0.8em; line-height:1.5em;
}
.column  .placeholder{
	background: #f0f0f0;
	border:1px dashed #ddd;
}
.dragbox h2.collapse{
	background:#f0f0f0 url('collapse.png') no-repeat top right;
}
.dragbox h2 .configure{
	font-size:11px; font-weight:normal;
	margin-right:30px; float:right;
}

The JavaScript Code

To make the boxes draggable and droppable, we’ll be using the Sortables function of jQuery UI library. Here’ the code you need to use, add this to $(document).ready() function:

$('.column').sortable({
	connectWith: '.column',
	handle: 'h2',
	cursor: 'move',
	placeholder: 'placeholder',
	forcePlaceholderSize: true,
	opacity: 0.4,
})
.disableSelection();

Explanation: I used some options with sortable function . the first one connectWith lets you move panels across different columns. The handle defines the tag which is used to drag the panel. The placeholder parameter is the CSS class to use for the panel placeholder which is displayed when you drag a panel to show the possible position of the dragged panel. forcePlaceholderSize makes sure that placeholder size is equal to the size of the dragged panel and at last opacity sets the opacity of the panel while dragging.

Collapsing The Panels
To collapse the content of a panel when you click on the panel header and show the configure link on panel header on hover, use the below code in jQuery document ready function.

$('.dragbox').each(function(){
	$(this).hover(function(){
		$(this).find('h2').addClass('collapse');
	}, function(){
		$(this).find('h2').removeClass('collapse');
	})
	.find('h2').hover(function(){
		$(this).find('.configure').css('visibility', 'visible');
	}, function(){
		$(this).find('.configure').css('visibility', 'hidden');
	})
	.click(function(){
		$(this).siblings('.dragbox-content').toggle();
	})
	.end()
	.find('.configure').css('visibility', 'hidden');
});

One thing you’ll notice after adding this code is that when you drag the panel, the content of the panel toggles as when you drag the click event of panel header also fires that toggles the state of content. To fix this, you need to add another parameter to sortable so that when drag is finished, state of content does not toggle.

$('.column').sortable({
	connectWith: '.column',
	handle: 'h2',
	cursor: 'move',
	placeholder: 'placeholder',
	forcePlaceholderSize: true,
	opacity: 0.4,
	stop: function(event, ui){
		$(ui.item).find('h2').click();
	}
})
.disableSelection();

Here i added another parameter stop that defines the function to use when drag is complete. Once the drag is complete, click event of panel header is fired so that the original state of content is restored.

Saving State

Update (Sep 16, 2009)
Many of the commentors below asked about how to save the state of panels. I have written a follow up post on how to save state using database. Be sure to check it if you want to know.

There you are with nice multi-column collapsible drag drop panels.

See the Working Demo or Download source code & try.

Tags:

Share


Subscribe to Full RSS Feed

RSS FeedIf you found this article useful, then consider subscribing to our RSS Feed or e-mail updates to stay updated with latest Web Design/ Development articles. You can also follow @webdevplus on twitter for latest updates.

50 Responses (Add Your Comment)

  1. Ben says:

    How would you save the positions of the panels?

  2. Balaji says:

    Great article! Thanks for taking the time to explain the Drag and drop panels through Jquery. I’ve been thinking about similar topics lately, and it’s good to see that I’m not alone. What do you think about paging with Jquery?

  3. CoryMathews says:

    It does not seem to work in opera 10. It will move and pop up that it has been changed but move it to a weird location. WordPress used to have this same problem a version or two ago but seemed to have fixed it sense then. Works great in the other browsers!

  4. Jeff says:

    typo in the title!

  5. Steffy says:

    Hi i wanna now how u can have such a nice designed comment-Box. I want it too! :/ can u tell me how you did this?

  6. Sandeep says:

    Hi, its great article but it is not working in IE 7 but its working fine with firefox.
    I think javascript is not working in the IE 7. I need it very badly, can you please tell me how to fix it

  7. Ben says:

    Thanks so much for the great write-up! I already had something similar to this working using the interface library, but it was having IE issues so I figured I would try this one, worked immediately… Almost. IE8 seems to only recognize the area directly over the h2 text as a handle, _until_ you drag/drop it, at which time the entire gray box becomes draggable. Weird. I’ll post again if I figure it out.

  8. sinner_pj says:

    Nice one, thanks. I’ve done a similar trick with jQuery, jQuery UI and ist-ui-panel (googled recently).

  9. Mark H says:

    Thanks for this but I also have the same problem where it does become active until you drag it once… also its seem to drag easier from top-bottom than from bottom-top.

    Also how do you propose to reload the saved order (from wherever I was thinking writing that array to a cookie) on each postback?

    thanks in advance
    M.H.

  10. mark says:

    Hi

    I have been trying in vain for hours to get the data saved to a database. Any chance you can give some pointers on this area of the script please?

    cheers
    Mark

    • Refer to the Saving State section in the article above, you can pass the sortorder variable to the server side script either using GET or POST, e.g. $.get('serversidescript', sortorder, function(){ //do something });
      and on the server side, you’ll have $_GET["column1"], $_GET["column2"] etc. variables with values corresponding to panels which you can save to database.

      • mark says:

        Hi
        Thank you for your help. I now have a partially working script to update my database. If I could beg your help once more I would be ever grateful.

        In my database I have the following rows.
        id, item_id, column_id, item_order and name.

        In my update.php script I have this
        $col1 = $_GET['column1'];
        $col2 = $_GET['column2'];
        $col3 = $_GET['column3'];
        $col4 = $_GET['column4'];

        $query = "UPDATE sg_desktop_widgets SET column_id = " . $_GET['column1']; . " WHERE item_id = 3";
        mysql_query($query) or die('Error, insert query failed');

        Obviously my query is completely wrong. Do you have any pointers I could follow to make this work properly

        TIA
        Mark

      • sg says:

        Oh big programming god,
        Many many thanks for this script. It’s great !!!
        I’m sorry to ask, but I’m so excited to get this working :-) could you inform a bit better what to do to have it saved?
        Should I create a file with chmod755 on the server?
        And what code do I need to add specific.
        Greets from a designer, not a coder :-)

  11. RAN says:

    Hi. THanks for this wonderful effect. I need to store this positions . Can you give me the sample update coding…(with explanation)… Once again thanks for this beautiful one…………….

  12. AltaSeO says:

    All greetings!
    My browser is Opera, it blocks the movement after he takes not a correct position.
    What could be the problem, the script itself or customize css?

  13. Russ says:

    Hi,
    I am in an unfortunate position in that I have to cater for IE6. I have noticed that in this browser the handle only works on the text of the header and also the expand / collapse arrows don’t work. Are these things that can be changed with css selectors or would they need a bigger fix?

  14. george says:

    Excellent tutorial and many thanks for sharing.
    Is it possible to show us how you save the state in cookies and then retrieve the state from those cookies on postback or refersh.
    Thanks again.

  15. mark says:

    Hi All

    I now have a working version of the database update script. I changed it from GET to POST as well. I am not a fan of GET, no idea why really :)

    You will also need to update the JS script to send the data to the php file. see further below for the full modified JS

    $val){
    $ind = preg_replace ('/[^\d\s]/', '',$ind); //strip non-numeric characters
    $ind = (int)$ind;//make it an integer
    $insertions = prep_data($ind,$val);
    mysql_query("$insertions") or die('Error, insert query failed');
    }
    echo '> Widget position updated';
    ?>

    Obviously you will need to change the database table and row details but all should be good. works absolutely fine for me. If you need a little more help drop me a mail and I will walk you through it.

    here is the JS

    // MAIN STAGE DRAGGABLE BOXES
    $(function(){
    $('.dragbox')
    .each(function(){
    $(this).hover(function(){
    $(this).find('h2').addClass('collapse');
    }, function(){
    $(this).find('h2').removeClass('collapse');
    })
    .find('h2').hover(function(){
    $(this).find('.configure').css('visibility', 'visible');
    }, function(){
    $(this).find('.configure').css('visibility', 'hidden');
    })
    .click(function(){
    $(this).siblings('.dragbox-content').toggle();
    })
    .end()
    .find('.configure').css('visibility', 'hidden');
    });

    $(document).ready(function(){
    function stageResponse(){
    setTimeout(function(){
    $("#response").removeClass("processing");
    }, 2000);}

    $('.column').sortable({
    connectWith: '.column',
    handle: 'h2',
    cursor: 'move',
    placeholder: 'placeholder',
    forcePlaceholderSize: true,
    opacity: 0.4,
    stop: function(event, ui){
    $(ui.item).find('h2').click();
    ui.item.css({'top':'0','left':'0'});
    var sortorder='';
    $('.column').each(function(){
    var item_order=$(this).sortable('toArray');
    var columnId=$(this).attr('id');
    sortorder+=columnId+'='+item_order.toString()+'&';
    });
    /*Pass sortorder variable to server using ajax to save state*/
    $.post('updateDesktop.php', sortorder, function(updateDesktopResponse){
    $("#response").addClass('processing');
    $("#console").fadeIn("5000").append(''+updateDesktopResponse+'');
    stageResponse();
    });

    }
    })
    .disableSelection();
    });
    });

    Thanks should go to “kratsg” from phpfreaks for his help debugging and fixing the script that saves the data.

  16. mark says:

    I forgot to add, that script will only update which column the widget is in, it DOES NOT currently update the order in the column or remember the open/closed state

    I am working on the last few things to make sure it works in every way possible.

    Anybody out there have any idea how to get the widget order working?

  17. mark says:

    email has been sent. thank you

  18. James Tombs says:

    Working on integrating this on my works development site, but have a problem where the original div’s will only sort in the column it is originally in.

    The site uses Blueprint CSS as the framework for the layout and has 3 columns. The 2 sidebars (left and right of the content) need to be sortable and be able to move panels from 1 side to the other.

    Are there any known issues with Blueprint CSS or is it another factor?

    • James Tombs says:

      After a little bit more research multiple column sorting doesn’t work until you upgrade to jQuery 1.3.2

      Unfortunately I am limited to jQuery 1.2.6, you may want to add a caveat to let people know.

  19. hey everyone, many of you were asking about how to save the state of panels, i have gone ahead and coded the complete solution to that problem. You can check it out here.

  20. unfilter says:

    great work!

    i have a question about cross browser compatibility between firefox and ie7. in the stop function, there’s a $(ui.item).find(‘h2′).click(), which seems to be forcing the panel to open back up after dragging, i imagine because it’s being dragging is erroneously also registering a click triggering a toggle. the problem is that with that line in, it works great in firefox, but not in ie7. take the line out, and the reverse is true. is there a fix to make it work for both?

    also, i’m noticing that in ie7, the first time you try to drag a panel, the handle only seems to apply to the text in the header. after the first move, then you can use the entire header as a handle. is this a known jquery issue?

    thanks in advance

  21. Mohd Arif Khan says:

    Great Work !
    This work in Firefox but not in IE6,IE7,IE8 can u please help me its urgent.

  22. NickAce says:

    Hi this is great tutorial, just what Iam looking for.
    I’m tweaking with your code and jquery for hours and Now I just have one problem how can I lock the first and or second cell in the left side side column I want it static just like in the customization layout of the blogspot.com.

    any hint or help how can this be done.

    many thanks
    Nick Ace

  23. Sunil says:

    1. How do I insert dynamically new widget windows… with different div Id’s??
    2. Also how do i remove a particular widget if I follow your approach. ??

Comments are closed for this post.