Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery Sortable - allow some items to be dropped only into certain lists

I'm using jQuery UI Sortable to implement a categorized selection, linked to a set of radio buttons. This part has been working well for a couple of years. (See jsFiddle version.) Now, the client wants to modify it: some items should now only be allowed to go into certain categories, and I'm totally stuck.

On the back end of things, it's easy enough to disable certain radio buttons:

<ul id="L" class="L ui-sortable">
    Available (A):
    <li id='L_1'>
    <input type='radio' name='L_1' id='L_1_A' value='A'>
      <label for='L_1_A'>A</label>
    <input type='radio' name='L_1' id='L_1_B' disabled='disabled' value='B'>
      <label for='L_1_B'>B</label>
    <input type='radio' name='L_1' id='L_1_C' value='C'>
      <label for='L_1_C'>C</label>
    Item 1 can't go in B
    </li>
    <li id='L_2'>
    <input type='radio' name='L_2' id='L_2_A' value='A'>
      <label for='L_2_A'>A</label>
    <input type='radio' name='L_2' id='L_2_B' value='B'>
      <label for='L_2_B'>B</label>
    <input type='radio' name='L_2' id='L_2_C' disabled='disabled' value='C'>
      <label for='L_2_C'>C</label>
    Item 2 can't go in C
    </li>
    <!-- etc. -->
</ul>
<ul id='L_B' class='L ui-sortable cat'>Selected - B</ul>
<ul id='L_C' class='L ui-sortable cat'>Selected - C</ul>

(The radio buttons are hidden via a combination of CSS and javascript.) This has the side effect of making it impossible to save improper categorizations: if you try to put something into a category it can't go into, your selection is totally ignored (because a disabled radio button doesn't return a value). However, unless you go back and look at your saved record, you'll never know what happened: to you, it looks like you put the item in the box, as per usual.

What I would like is for the destination box that's linked to a disabled radio button to "go gray" when an item is grabbed, and if they still try to put it in that box, the item should jump back to its starting point. Basically, it should behave like what happens if you set $("ul.notallowed").sortable("option","disabled",true);, but only while the user is "holding" a disallowed item.

I know that there is an .is(":disabled") command to figure out if a radio button is disabled, but I have no idea where to put it.

Here's the javascript for the existing implementation, i.e. where all categories are allowed for all items. (I'm sure there are better/more efficient ways to code this, but I'm just happy that it works.)

$(function() {
    $("#L").sortable({
        connectWith: '.L',
        containment: '#ListContainer',
        receive: function(event, ui) {
            $(ui.item).find('input:radio')[0].checked = true;
        }
    }).disableSelection();
    $("#L_B").sortable({
        connectWith: '.L',
        containment: '#ListContainer',
        receive: function(event, ui) {
            $(ui.item).find('input:radio')[1].checked = true;
        }
    }).disableSelection();
    $("#L_C").sortable({
        connectWith: '.L',
        containment: '#ListContainer',
        receive: function(event, ui) {
            $(ui.item).find('input:radio')[2].checked = true;
        }
    }).disableSelection();
});
$(document).ready(function() {
    $('.B').closest('li').appendTo("#L_B");
    $('.C').closest('li').appendTo("#L_C");
    $('ul.ui-sortable').width("10em");
    $('ul.ui-sortable li').css({cursor:'pointer'});
});

I should note that I totally suck at programming languages that involve curly braces. I wrote the above code, but I couldn't tell you what most of it does, let alone why it works. So if you could frame your answers in the simplest possible terms, I'd greatly appreciate it.

like image 859
Martha Avatar asked Jan 14 '23 05:01

Martha


1 Answers

So, have I mentioned that I hate JavaScript? But I got it working (sans graying out, but I long since stopped caring about that) with the graying-out and everything.

Part 1, forget about detecting disabled radio buttons in jQuery. ((":disabled") works as a selector, but it returns a radio button, not its parent list item.) Instead, just add a class (or classes) to the list items.

<li class="noB">
    <label><input type="radio" name="L1" value="A" />A</label>
    <label><input type="radio" name="L1" value="B" disabled="disabled" />B</label>
    <label><input type="radio" name="L1" value="C" />C</label>
    Item 1 can't go in B
</li>

Part 2, in the "receive" functions of the destination lists, conditionally cancel the sortable for the sender jQuery object. For example,

$("#L_B").sortable({
    connectWith: '.L',
    containment: '#ListContainer',
    receive: function (event, ui) {
        if ($(ui.item).hasClass("noB")) {
            $(ui.sender).sortable("cancel");
        }
        else {
            $(ui.item).find('input:radio')[1].checked = true;
        }
    },
}).disableSelection();

The .sender part is crucial: I don't know how many hairs I've permanently lost while trying various things with ui.item or li.noB or whatever. (It makes little sense to me: what I want to prevent is putting the item in the wrong place, so why do I need to cancel the removing of it from the parent list? But anyway.)

Part 3 in my case was to move the list labels out of the lists, because besides being not-quite-valid, it made the cancel do straaaange things.

Part 4, the grayness: use .addClass in the "start" function and .removeClass in the "stop" function of each sortable box. I put these after the receive: function(...) {...}, but order shouldn't matter. (There is doubtless some way to write this function once and call it each time, but have I mentioned how JavaScript and I don't get along?)

    start: function (event, ui) {
        if ($(ui.item).hasClass("noB")) {
            $("#L_B").addClass("disabled");
        }
        if ($(ui.item).hasClass("noC")) {
            $("#L_C").addClass("disabled");
        }
    },
    stop: function (event, ui) {
        if ($(ui.item).hasClass("noB")) {
            $("#L_B").removeClass("disabled");
        }
        if ($(ui.item).hasClass("noC")) {
            $("#L_C").removeClass("disabled");
        }
    }

Don't forget to define the "disabled" class in your CSS, otherwise all these gymnastics will do exactly... nothing.

Working jsFiddle: http://jsfiddle.net/NY7Ms/4/

like image 138
Martha Avatar answered Jan 31 '23 07:01

Martha