[Lift WebFramework] Bridge jQuery UI Droppable to Lift AJAX handler

Introduction

I'm working on a GTD-style todo list management web application called BeDone for my personal use, which is implemented by Scala / Lift Web Framework.

During I'm working on this project, I would like to use jQuery drag & droppable widget to handle some user actions, and binding these actions to Lift ajax handler that will do some server side work.

I was not very sure how to do this at first, but it turns out that it's quite easy, since Lift provides some really useful API to do exactly these kind of work.

Objective

User when drag and drop a <li> tag on the page to a <div> block, it will execute some callback on the server side, doing something (e.g. querying database). Finally, the server side will return some JavaScript to browser to execute.

The HTML Part

The HTML we need is very simple, just a bunch of <li> tags with HTML5's data attribute, which contains the ID of that entry in database, like the following code:

<li class="todo" data-id="1">TODO 1</li>
<li class="todo" data-id="2">TODO 2</li>
<li class="todo" data-id="3">TODO 3</li>

Beside these, we also have a <div> tag, where user could drag <li> tags to it.

<div id="trashcan">Drag here to delete</div>

The jQuery UI Part

jQuery UI provides Drag & Drop widgets which is quite easy to use. We just tell jQuery UI which HTML tags in the page is draggable or droppable, and add some simple JavaScript callback when user drag an <li> tag to the <div> block.

$('.todo').draggable({
    revert: true
});

$("#trashcan").droppable({
    accept: ".listRow",
    tolerance: "pointer",
    drop: function(event, ui) {
        var todoID = ui.draggable.data("id");
        console.log("todoID:" + todoID);
    }
})

When we applied above JavaScript to our HTML page, it will print out the value of data-id attribute of accepted <li> tag to the browsers JavaScript console.

What We Need to Do

What we need to do next is modifiy the above JavaScript, to make it pass the todoID variable to some server side Scala callback function, and make that callback function return some JavaScript to the browser.

In this example, we need to do the following works:

  1. Writing a Scala callback function called removeTodoFromDB, which has an argument called todoID. This function will delete that entry from database, returning a JavaScript to browser.
import net.liftweb.http.js.JsCmds
import net.liftweb.http.js.JE.JsRaw

def removeTodoFromDB(todoID: String): JsCmd = {
    println("Delete todo %d from DB..." format(todoID))

    JsRaw("""$('li[data-id="%d]"').remove()""" format(todoID))
}
  1. Writing a JavaScript function called removeTodo by Lift JavaScript abstract layer. This JavaScript function has an argument called todoID, and this function will call to removeTodoFromDB() by AJAAJAXX.
import net.liftweb.http.js.JsCmds.Function
import net.liftweb.http.SHtml

val removeTodo = Function(
    name = "removeTodo",
    params = List("todoID"),
    body = SHtml.ajaxCall(JsRaw("todoID"), removeTodoFromDB _)
)
  1. Binding removeTodo JavaScript function to the page.
def render = {
    "#removeTodoJS *" #> removeTodo
}
  1. In jQuery UI droppable event handler, execute removeTodo with the todoID that drop event has received.
$("#trashcan").droppable({
    accept: ".listRow",
    tolerance: "pointer",
    drop: function(event, ui) {
        var todoID = ui.draggable.data("id");
        console.log("todoID:" + todoID);
        removeTodo(todoID)
    }
})

Glue It Together

After we gule everything together, we will have an HTML template like the following:

<html>
  <body data-lift="DragAndDrop">
  <ul>
    <li class="todo" data-id="1">TODO 1</li>
    <li class="todo" data-id="2">TODO 2</li>
    <li class="todo" data-id="3">TODO 3</li>
  </ul>
  <div id="trashcan">Drag here to delete</div>
  <script type="text/javascript">
    $('.todo').draggable({
        revert: true
    });

    $("#trashcan").droppable({
        accept: ".listRow",
        tolerance: "pointer",
        drop: function(event, ui) {
            var todoID = ui.draggable.data("id");
            console.log("todoID:" + todoID);
            removeTodo(todoID)
        }
    })
  </script>
  <script type="text/javascript" id="removeTodoJS"></script>
  </body>
</html>

And a Lift snippet written in Scala:

import net.liftweb.http.js.JE.JsRaw
import net.liftweb.http.js.JsCmds
import net.liftweb.http.js.JsCmds.Function
import net.liftweb.http.SHtml

class DragAndDrop
{
    def removeTodoFromDB(todoID: String): JsCmd = {
        println("Delete todo %d from DB..." format(todoID))

        JsRaw("""$('li[data-id="%d]"').remove()""" format(todoID))
    }

    val removeTodo = Function(
        name = "removeTodo",
        params = List("todoID"),
        body = SHtml.ajaxCall(JsRaw("todoID"), removeTodoFromDB _)
    )

    def render = {
        "#removeTodoJS *" #> removeTodo
    }
}

And that's all we need to do to binding a jQuery drag & drop widget to Lift AJAX handler.

回響