緣起
話說一直找不到覺得順手的 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 傳回給瀏覽器執行。
在這個例子裡,我們需要做下面四件事:
- 設計一個叫做 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))
}
- 設計一個叫做 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 _)
)
- 把 removeTodo 這個 JavaScript 程式塞到網頁上
def render = {
"#removeTodoJS *" #> removeTodo
}
- 在 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 端了。
回響