jQuery – vmodel.js 討論列表範例教學(3) 如何改版?
這次的例子簡單多了,旨在簡化改版這件事情的介紹。我們模擬當版面設計僅需要小幅度修改時,如何應對。
大家知道在 jQuery 的推廣概念中,將 JavsScript、HTML、CSS 支解分離是能夠有效的降低彼此的依附關聯,對於編碼來說會非常乾淨,也容易理解、容易修改。針對之前的例子,我們試試看如何透過 vmodel 做修改。
假設需求是要添加刪除按鈕。
舊款
新款
當滑鼠移入時,會出現刪除按鈕,這個動作不是太過花俏,所以可以從 CSS 直接修改。而 HTML 的部分,也只需要修改 .comment 的編碼,加入新的元素即可。目前為止不會動到任何的 jQ。
修改結構
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vmodel</title> <style></style> <script src="//code.jquery.com/jquery-1.11.3.min.js"></script><!-- jQuery 核心 --> <script src="../src/jquery.vmodel.min.js"></script><!-- vmodel 核心 --> <script src="index3.js"></script><!-- 範例編碼 --> <link rel="stylesheet" href="index3.css"><!-- 範例樣式 --> </head> <body> <!-- box --> <div class="box"></div> <!-- form --> <div class="form"> <form class="userdata" action="" data-user-name="小明"> <input type="text" class="text" autofocus> <button class="submit">送出</button> </form> </div> <!-- list --> <div class="list"></div> <!-- comment 預先隱藏起來 --> <div class="comment" hidden> <span class="name"></span> <span class="say"></span> - <span class="current"></span><span class="del">X</span> </div> <!-- 提示訊息顯示 --> <div class="message"> <div class="title">Message</div> <div class="liwrap"></div> </div> <!-- reply --> <div class="box_reply" hidden> </div> <!-- list --> <div class="list_reply"> </div> <!-- form --> <form class="form_reply" data-user-name="Lee" hidden> <textarea class="text" placeholder="回覆訊息..."></textarea> <button class="submit_reply">送出</button> </form> </body> </html>
其中這段後面的 .del 是新添加的元素,就是代表那顆刪除的叉叉。
<!-- comment 預先隱藏起來 --> <div class="comment" hidden> <span class="name"></span> <span class="say"></span> - <span class="current"></span><span class="del">X</span> </div>
開始修改邏輯部分
當我們把HTML編碼調整到你要的定位後,設定新的 CSS ,接著才來動作 vmodel 的部分。完整程式碼如下
$(function (){ // 表單模組 $(".form").vmodel("md/form", false, function (){ var vs = this; this.autoload = ['init_position', 'submit']; // 初始化會被搬移到 .box 底下 this.init_position = function (){ vs.root.appendTo(".box"); } // 取得輸入的文字 this.user_say = function (){ var val = vs.root.find(".text").val(); return $.trim(val); } // 取得使用者是誰 this.user_name = function (){ return $.trim(vs.root.find(".userdata").attr("data-user-name")); } // 負責放到 .comment this.put = function (user, say, callback){ $.vmodel.get("md/comment").say(user, say); if (callback) callback(); } //送出時... this.submit = function (){ vs.root.on("submit", ".userdata", function (){ var user = vs.user_name(); var say = vs.user_say(); vs.put(user, say, function (){ //發送後可以做一些事情... //將回覆框放進來 var where = $(".list .comment").first(); $.vmodel.get("md/box_reply").post_to(where) }); return false; }) } // 清空 this.clean = function (){ vs.root.find(".text").val(null); } }); // 列表模組 $(".list").vmodel("md/list", false, function (){ var vs = this; this.autoload = ['init_position', 'when_reply', 'when_send_reply', 'delete']; // 這裡只負責推送到 .box ,為了範例簡單,我們這裡不推到 message。 this.init_position = function (){ vs.root.appendTo(".box"); } // 當使用者想要回覆該則留言 this.when_reply = function() { vs.root.on("click", ".comment .say", function (){ //顯示該則的回覆表單 var who = $(this).parents(".comment"); $.vmodel.get("md/form_reply").show(who); }); } //當送出回覆表單。要綁定在這裡,而不是綁定在 md/comment ,是因為 .list 是不會變動的。 this.when_send_reply = function (){ vs.root.on("submit", ".form_reply", function (){ $.vmodel.get("md/form_reply").send(this); return false; }); } // 刪除comment this.delete = function (){ vs.root.on("click", ".del", function (){ // 呼叫模組 md/comment 刪除元素 $.vmodel.get("md/comment").remove_element(this); }); } }); // 整體框的主要模組 $(".box").vmodel("md/box", false, function (){ var vs = this; this.autoload = ['init']; this.init = function (){ //只放置表單、與列表框到指定的位置。 //目前 .list 應該不會有任何資料,一直到使用者送出表單。 vs.create_form() .create_list(); } //初始化使用者表單 this.create_form = function (){ $.vmodel.get("md/form", true); return vs; } //初始化列表 this.create_list = function (){ $.vmodel.get("md/list", true); } }); //討論模組 $(".comment").vmodel("md/comment", false, function (){ var vs = this; // 使用者說了什麼 this.say = function (name, say){ vs.set(name, say) .post_to(".box .list"); // 記得清空 $.vmodel.get("md/form").clean(); // 也可以把模板清空 vs.clean(); return vs; } // 使用者回覆了什麼 this.reply = function (name, say, post_to){ vs.set(name, say) .post_to(post_to); } // 將數據放入模板 this.set = function (name, say){ vs.root.find(".name").html(name); vs.root.find(".say").html(say); // 我們加入時間 var NowDate = new Date(); vs.root.find(".current").html(NowDate.toLocaleTimeString()); return vs; } // 放到列表中 this.post_to = function(selector){ //需要先拔除原本的 hidden 屬性才能顯示。 var obj = vs.root.clone() obj.removeAttr('hidden').prependTo(selector); return vs; } // 清空 this.clean = function (){ vs.root.find(".name").html(null); vs.root.find(".say").html(null); vs.root.find(".current").html(null); } //刪除該筆討論 this.remove_element = function (selector){ //透過CSS動畫模擬刪除動作 var $this = $(selector); $this.parent(".comment").css({ transition: "all 0.4s", transform: "translateX(180px)", opacity: 0 }); //簡易的當CSS動畫結束時,刪除元素 setTimeout(function (){ $this.parent(".comment").remove(); }, 400); } //刪除誰底下的 comment 刪除鈕 this.remove_delete_button = function (selector_find){ var button = vs.root.find(".del"); if (button.length == 0) return true; $(selector_find).find(vs.selector + " .del").remove(); } }); //訊息模組。這個模組是主動式的。也就是當使用者送出資料以後,並不見得會馬上啟用訊息模組。 $(".message").vmodel("md/message", false, function (){ var vs = this; this.autoload = ['interval']; // 定時更新 this.interval = function (){ //這裡使用 setInterval 作範例,實際上可透過其他效能較好的方式 setInterval(function (){ vs.update(); }, 2000); } // 更新訊息 this.update = function (){ // 應該是由 AJAX 向遠端更新訊息。因為遠端的關係,通常會比較慢才取回資料。 // 我們這邊只假設是本地數據。並延遲觸發來模擬遠端的感覺。 setTimeout(function (){ var NowDate = new Date(); var data = [{ name: "小華", say: "哈囉!", current: NowDate.toLocaleTimeString() }]; var comment = $.vmodel.get("md/comment"); $.each(data, function(index, ele) { comment .set(ele.name, ele.say, ele.current) .post_to(vs.selector + " .liwrap"); // 添加刪除討論的刪除元素, 因為不需要出現。 var button = vs.root.find(".del"); if (button.length == 0) return true; // 呼叫模組的刪除按鈕指令 $.vmodel.get("md/comment").remove_delete_button(vs.selector); }); }, 200); } }); // 全部都定義好了,我們去觸發 box 模組與 message 模組吧 $.vmodel.get("md/box", true); $.vmodel.get("md/message", true); /*******/ // 回覆框模組 $(".box_reply").vmodel("md/box_reply", false, function (){ var vs = this; this.autoload = ['init']; //初始化 this.init = function (){ //將回覆列表與表單,放到 .box_reply 裡面 vs.create_list() .create_form(); } //建立表單 this.create_form = function (){ //讓 "md/form_reply" 初始化 $.vmodel.get("md/form_reply", true); return this; } //建立列表 this.create_list = function (){ //讓 "md/list_reply" 初始化 $.vmodel.get("md/list_reply", true); return this; } // 複製模版,並放到指定的地方 this.post_to = function (selector){ var newobj = vs.root.clone(); //這時候先不要移除 .form_reply 的 hidden,因為我們要等到使用者需要回覆時,才會顯示。 newobj.removeAttr("hidden").appendTo(selector); } }); //回覆表單模組 $(".form_reply").vmodel("md/form_reply", false, function (){ var vs = this; this.autoload = ['init']; //初始化 this.init = function (){ //放到位置 vs.root.appendTo('.box_reply'); } //顯示表單 this.show = function (selector){ $(selector).find(".form_reply").removeAttr('hidden'); } //送出使用者回覆訊息 this.send = function (selector){ var name = $(selector).attr("data-user-name"); var text = $(selector).find(".text").val(); // 等候重整的回覆列表是誰 var who = $(selector).parents(".box_reply").find(".list_reply"); //通常我們可以使用 AJAX 送出,但我們這裡使用本地的模擬延遲 setTimeout(function() { //重新讀取該則回覆列表 $.vmodel.get("md/list_reply").reload(who, function (){ //也許可以做一些事情.... }); // 清空表單 vs.clean(selector); }, 100); } //清空哪個回覆表單 this.clean = function (selector){ $(selector).find(".text").val(null); } }); //回覆列表模組 $(".list_reply").vmodel("md/list_reply", false, function (){ var vs = this; this.autoload = ['init']; // 初始化 this.init = function (){ //放到位置 vs.root.appendTo('.box_reply'); } //哪個回覆列表,需要重新讀取 this.reload = function (selector, callback){ //假設我們模擬透過 AJAX 取得遠端的數據 var data = [{ name: "新人", text: "早安 (其實我是本地產生的數據)" }, { name: "新人2", text: "午安 (其實我也是本地產生的數據)" }]; $.each(data, function(index, info) { //我們要呼叫一開始的模組,因為避免重新設計,討論 .comment 都是使用一款模組。 $.vmodel.get("md/comment").reply(info.name, info.text, selector); }); if (callback) callback(); } }); // 支解後HTML,透過 vmodel 先拼裝起來。 $.vmodel.get("md/box_reply", true); // "md/list" 添加函式 delete(), 綁定點擊刪除鈕的動作 // 因為 .message 底下的 .comment 使用同一個模組,但是不應該出現刪除按鈕。 // 所以修改 "md/message" 的 update(); })
首先,我們要做 「點擊後觸發刪除該則討論」,所以 “md/list” 添加函式 delete()
// 刪除comment this.delete = function (){ vs.root.on("click", ".del", function (){ // 呼叫模組 md/comment 刪除元素 $.vmodel.get("md/comment").remove_element(this); }); }
一樣記得刪除這件事情,是由 “md/list” 綁定喔!至於為什麼就參考 上一篇 的說明。當事件綁定後,會去呼叫 “md/comment” 的 remove_element()
//刪除該筆討論 this.remove_element = function (selector){ //透過CSS動畫模擬刪除動作 var $this = $(selector); $this.parent(".comment").css({ transition: "all 0.4s", transform: "translateX(180px)", opacity: 0 }); //簡易的當CSS動畫結束時,刪除元素 setTimeout(function (){ $this.parent(".comment").remove(); }, 400); }
這邊要記得使用 parent() 而不是 parents() ,不然會刪除到最頂端的選擇器。透過這支函式,我們就做到刪除的動作了。
那麼接下來,因為 .message 也使用了相同的 comment 模組,只是 .message 屬於提示訊息,我們並不希望他有刪除的按鈕,這該怎麼辦呢?沒關係,我們來看看接著要修改 “md/message” 的 update();
$.each(data, function(index, ele) { comment .set(ele.name, ele.say, ele.current) .post_to(vs.selector + " .liwrap"); // 添加刪除討論的刪除元素, 因為不需要出現。 var button = vs.root.find(".del"); if (button.length == 0) return true; // 呼叫模組的刪除按鈕指令 $.vmodel.get("md/comment").remove_delete_button(vs.selector); });
這邊我們只要再添加一些判斷,並透過模組 “md/comment” 來呼叫 remove_delete_button() ,如此一來就能夠移除 .message 底下 .comment 的 .del 喔!
//刪除誰底下的 comment 刪除鈕 this.remove_delete_button = function (selector_find){ var button = vs.root.find(".del"); if (button.length == 0) return true; $(selector_find).find(vs.selector + " .del").remove(); }
結語
在製作使用者介面的時候,基本上還是把 HTML 的標籤,放置在 HTML 的位置,CSS 的部分寫在 CSS 的位置,而不是放在任何的 JavaScript 或 jQuery 的部分。jQuery.vmodel() 主要沿用這樣的概念設計,當我們需要換版面或是添加小零件時,才不會發生「我這句 HTML 應該要去 HTML 的位置找呢,還是要去看 jQuery 的部分?」統一集中處理,是非常必要的習慣。像我們這邊因為要添加刪除按鈕,很自然的,會先去處理 HTML + CSS ,之後才會去處理 jQuery.vmodel() 的邏輯。希望透過這些範例教學,可以讓你感受到使用 vmodel() 設計的複雜介面時的優點,歡迎大家下載來玩玩。
完整教學範例
- GitHub 下載
- jQuery – vmodel 模組化 jQuery 的編寫結構。
- jQuery – vmodel.js 討論列表範例教學(1) 基本方法
- jQuery – vmodel.js 討論列表範例教學(2) 添加回覆功能
- jQuery – vmodel.js 討論列表範例教學(3) 如何改版?