KendoUI: Sortable Lists


Few days ago a colleague of mine was complaining about KendoUI arguing that it does not provide sortable list as jQueryUI does (check this if you want to see it). I told him that it could easily be implemented using KendoUI Tree Views that might not look exactly the same but the functionality exists.

He challenged me to do it in a few lines of code. Wow! I love challenges…

Here is the result:

KendoUI Sortable Lists

First of all let’s define a list that I will display as a flat tree (one level) and I want to customize how to display each element so, I’ll be using KendoUI templates.

The HTML, the minimum HTML, just some place where to insert the list.

<div id="sortable"></div>

The JavaScript where I have my list:

var dataSource = [
    { id:1, text:"Element 1" },
    { id:2, text:"Element 2" },
    { id:3, text:"Element 3" },
    { id:4, text:"Element 4" },
    { id:5, text:"Element 5" },
    { id:6, text:"Element 6" }
];

And now the initialization of the list:

$("#sortable").kendoTreeView({
    dataSource :dataSource,
    template   :"<div class='ob-item'> #= item.text # </div>",
    dragAndDrop:true
});

I’m using template in order to provide a little styling to each item in the list (border, background and text color):

.ob-item {
    background-color: #e9e9e9;
    border: 1px solid #a99f9a;
    color: #2e2e2e;
    padding: 5px;
    border-radius: 4px;
}

With this, we already hava a list that is sortable BUT -and here is where my colleague was complaining about- is that we can drop one element into another and then having a multilevel tree (not flat).

Forcing flat tree

I need to do some little checking in order to guarantee that one item it is not dropped over an element. The easiest way (and I think that is not a bad solution) is inserting a node before (or after) the node where you drop it.

So, I write a drop event handler that will manage where to insert instead of delegating it in kendoTreeView.

drop       :function (e) {
    $(e.sourceNode).insertBefore(e.destinationNode);
    e.setValid(false);
}

What I do is insert sourceNode before destination node and invoke setValid with false in order to prevent default handler of doing the insertion.

Final tuning

But this is not perfect:

  • It is possible to drop a node on itself that would cause invoking insertBefore (and the node would get lost)
  • The visual feedback about where the element is going to be inserted is wrong.
drag       :function (e) {
    var dst = $($(e.dropTarget).closest(".k-item")[0]).data("uid");
    var src = $(e.sourceNode).data("uid");
    if ($(e.dropTarget).hasClass("ob-item") && dst != src) {
        e.setStatusClass("k-insert-top");
    } else {
        e.setStatusClass("k-denied");
    }
},
drop       :function (e) {
    if ($(e.sourceNode).data("uid") !== $(e.destinationNode).data("uid")) {
        $(e.sourceNode).insertBefore(e.destinationNode);
    }
    e.setValid(false);
}

For the visual feedback what I do is:

  1. Check that I’m dropping the element in top of another element ($(e.dropTarget).hasClass(“ob-item”))
  2. Check that source (node being moved) and destination (the node on which I’m dropping the source) are different (have different uid).

If both are true then I set status class (visual feedback) to k-insert-top that displays that it is going to be inserted before. Otherwise, I set the status class to k-denied to show that it is not a valid movement.

The complete final code is:

$("#sortable").kendoTreeView({
    dataSource :dataSource,
    template   :"<div class='ob-item'> #= item.text # </div>",
    dragAndDrop:true,
    drag       :function (e) {
        var dst = $($(e.dropTarget).closest(".k-item")[0]).data("uid");
        var src = $(e.sourceNode).data("uid");
        if ($(e.dropTarget).hasClass("ob-item") && dst != src) {
            e.setStatusClass("k-insert-top");
        } else {
            e.setStatusClass("k-denied");
        }
    },
    drop       :function (e) {
        if ($(e.sourceNode).data("uid") !== $(e.destinationNode).data("uid")) {
            $(e.sourceNode).insertBefore(e.destinationNode);
        }
        e.setValid(false);
    }
});

Conclusion

You can do it, it is not that hard. The functionality is the same but you might find jQueryUI Sortable more appealing.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s