[Lift WebFramework] 橋接 jQuery UI 的 Drag & Drop 事件

緣起

話說一直找不到覺得順手的 GTD Style 的 TODO 管理程式,所以就自己用 Scala / Lift 刻了一個簡單的網站,叫做 BeDone,畢竟雖然知道 Lift 這個有趣的 Web Framework 很久了,但沒真沒拿他來做過什麼認真的東西,就當做個練習吧。

前一陣子把 BeDone 的一些主要功能刻出來之後,在用的時候覺得有些動作還是用拖拉的 UI 操作方式比較直覺,所以就想要來實做托拉的功能,研究了一下後發現沒有想像中的來的複雜,就在這邊記錄一下怎麼樣橋接 jQuery UI 的 Drag / Drop 到 Lift Ajax 的做法。

目標

當使用者把網頁上的某一個 <li> 標籤拉到特定的 <div> 區塊之後,會呼叫 Server 端的 Callback,並且進行一些東西(例如查詢資料庫),然後再傳回 JavaScript 給瀏覽器做某些事。

HTML 的部份

HTML 的部份相當簡單,我們需要一個可以被拉動的 <li> 標籤,並且透過 HTML5 的 data 屬性,給 <li> 標籤一個 ID,這個 ID 會對應到後端資庫表格當中的 ID 欄位:

<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>

此外,我們還要有一個 <div> 標籤,讓使用者可以把上述的 <li> 標籤拉到這個地方:

<div id="trashcan">拉到這裡來把我刪掉</div>

jQuery UI 的部份

jQuery UI 提供了 Drag & Drop 的元件,使用上都相當簡單,我們只要告訴 jQuery UI 要把哪些 HTML 區塊當成可以拉動的元件,哪些 HTML 區塊當成是可以被丟東西進去的就好了。

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

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

當套用了上面的 JavaScript 程式後,當 <li> 當中的東西被拉到 dropZone 區塊的時候,就會在瀏覽器的 JavaScrip console 中顯示出被拉進去的 <li> 區塊的 data-id 屬性的值。

我們需要做的事

接下來我們要做的,就是想辦法讓上面的 JavaScript,可以把 todoID 傳給 Server 端的 Lift 中的某個 callback 函式,做一些事情,最後再把某些 JavaScript 傳回給瀏覽器執行。

在這個例子裡,我們需要做下面四件事:

  1. 設計一個叫做 removeTodoFromDB 的 Scala Callback 函式,傳入的是 todoID,他會把資料庫中相對應的 todo 刪除,並返回刪除 <li> 的 JavaScript 給瀏覽器執行。
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. 設計一個叫做 removeTodo 的 JavaScript 函式,他接授一個 todoID 的參數,並且會透過 Ajax 執行 Server 端的 removeTodoFromDB 函式。
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. 把 removeTodo 這個 JavaScript 程式塞到網頁上
def render = {
    "#removeTodoJS *" #> removeTodo
}
  1. 在 jQuery UI 的 Drop 事件處理的部份中,加入呼叫 removeTodo 的部份。
$("#trashcan").droppable({
    accept: ".listRow",
    tolerance: "pointer",
    drop: function(event, ui) {
        var todoID = ui.draggable.data("id");
        console.log("todoID:" + todoID);
        removeTodo(todoID)
    }
})

全部合在一起

把上述的東西全部合在一起之後,就會有一個 HTML 的模版:

<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">拉到這裡來把我刪掉</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>

以及一個 Scala 的 Lift snippet 類別:

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
    }
}

就這樣,我們就成功地把 jQuery 裡 Droppable 物件的事件,橋接到 Server 端了。

回響