Thursday 31 March 2011

Draggable items in Accordion

In the next months I'll be helping out part-time on a new project at work using Asp.Net Mvc 3 with a rich, jQuery based, UI. We'll be migrating-evolving an existing Flex application, so the User Interface we'll need to be rather smooth, and allow things like dragging items on some pageable-editable-sortable Ajax based Grid.
So, I've been playing around a bit in the last weeks to see how different jQuery based controls, plugins play together.

This is how I found out, to my horror, that the jQuery UI Accordion and the draggable functionality don't mix well together.
If we have an Accordion where each "button" (the h3 element) has a list of items (li's inside the sibling div), doing those items draggable does not work as expected, and dragging them results in an horizontal scroll being added to the div, not being able to drag the items outside.

Some searches on the net brought up some unanswered questions on some forums, which led me to think that it could be a fun and good idea to do my own accordion. The code for a basic accordion is rather simple (I'm using a closure to save as state the current expanded item):


function accordionize(containerId) {
//the eventhandling function is a closure that will trap the "expanded" variable, that is the only state we need to keep
var expanded;

var $headers = $("#" + containerId + " h3");
//notice that h3 and div elements are siblings here, that's why given a h3 element I use next() to refer to its corresponding div.
//use toggle for showing/hiding, instead of show-hide, the animation effect is better
$headers.each(function (index) {
$(this).click(function () {
if (index != expanded) {
var $cur = $(this);
//the callback function is only called once, not one time for each item being animated
$cur.siblings("h3").next().toggle("slow", function () {
printf("hidden");
$cur.next().toggle("slow");
expanded = index;
});
}
});
});

//initial display, hide all excepting the first one
$headers.not(":first").next().toggle();
expanded = 0;
}


The behavior is OK, and I guess that if I exerted myself for a while with CSS I would manage to give it a decent look. At first the dragging was working fine, but when I added a overflow-y: auto (you know, making it scrollable), to the div containing the list, the same issue I had with the jQuery accordion sprung up... Checking the jQuery UI accordion I noticed that the styles applied to it by the CSS framework were also adding the overflow-y:auto, so that's where the issue seems to lie. That looked almost like a dead end to me, cause it's rather likely that I'll need the scrolling functionality.

Hopefully, after some more and more internet reading, I've finally found the 2 draggable options that make it play (apparently) fine with the accordion:

appendTo: "body",
helper: "clone"


You can view it here

It seemed promising, but after having this entry almost ready I've realized it has a huge problem. In my case, if the item is not dropped, it gets replaced in the original position by a cloned element that is not draggable (examining it with some DOM explorer shows that it lacks the "ui-draggable" class that items acquire when done draggable. I've tried several things and none seems to work... but I guess the solution will be using the helper:function(){} option.

Update, 2011/04/14:
I was right in my assumption above, if we change the helper option to something like this:

helper: function(event){
return $("").html(event.target.innerHTML).css("border", "1px solid black");
}

(we return there the DOM element that we want to see dragging around the screen)

it works great!!!
Well, all of this has been a rather unfortunate and very time consuming issue...

Departing a bit from the main topic of this post, adding the fails-works radio buttons on top came useful to refresh a bit my memory on how these things work:

  • You group them by giving them the same value to their name attribute.

  • To handle them with jQuery you'll use the change() function-event, and the .attr("checked") function.

  • Furthermore, selecting an item based on it's value can be pretty useful:

    $("#accordionSelection").find("input[value='correct']").attr("checked", true);



  • As for the "changing" message that I show/hide when the radio button changes, it's just some very simple jQuery magic:


    var $changedItem = $(this);
    $changedItem.after($("").text("changing")
    .addClass("messageTag"));
    setTimeout(function(){
    $changedItem.next().remove();
    }, 2000)

No comments:

Post a Comment