jQuery – vmodel.js 討論列表範例教學(3) 如何改版?

這次的例子簡單多了,旨在簡化改版這件事情的介紹。我們模擬當版面設計僅需要小幅度修改時,如何應對。
大家知道在 jQuery 的推廣概念中,將 JavsScript、HTML、CSS 支解分離是能夠有效的降低彼此的依附關聯,對於編碼來說會非常乾淨,也容易理解、容易修改。針對之前的例子,我們試試看如何透過 vmodel 做修改。
假設需求是要添加刪除按鈕。
舊款
新款
當滑鼠移入時,會出現刪除按鈕,這個動作不是太過花俏,所以可以從 CSS 直接修改。而 HTML 的部分,也只需要修改 .comment 的編碼,加入新的元素即可。目前為止不會動到任何的 jQ。
修改結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<!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 是新添加的元素,就是代表那顆刪除的叉叉。
1 2 3 4 5 6 |
<!-- 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 的部分。完整程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
$(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()
1 2 3 4 5 6 7 8 9 |
// 刪除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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//刪除該筆討論 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();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$.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 喔!
1 2 3 4 5 6 7 8 |
//刪除誰底下的 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) 如何改版?