jQuery – vmodel.js 討論列表範例教學(1) 基本方法

前言
vmodel 優點:
- HTML、CSS、JS 盡可能去做分離的動作。設計版面這件事情,在設計與支解成模組的過程,可以非常快速的修改。微調樣式,設計師基本上不需要透過工程師,因為使用 HTML + CSS 而已。
- 直接使用 jQuery 的編撰習慣。vmodel() 非常容易上手,他只是提供一個框架結構的思維而已。如果有使用寫過物件導向的經驗,一定很容易上手。
- 適合大型架構。多個工程師負責不同的模型,彼此再拼接起來。
- 善用根結點綁定概念,讓你避免重複渲染。
- 用很簡單的結構,解決複雜的介面呼叫與綁定事件。
至於 MVC 嗎?沒有喔,這裡沒有MVC的概念。這裡只有 模組 (model)、結構 (html)、樣式 (css)。
假設我們要做一個這樣子的討論模組。
左邊是發佈表單與留言串;右邊是提示訊息,用來接收最新的提示訊息。
其中,誰說什麼話,這個樣式與功能是重複的。所以不僅式樣式不要去重複寫,就連程式也是。
下面會示範如何讓每個小模組去交互運用。
切割
一般我們取得設計師做好的靜態版面,會長這樣
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 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vmodel</title> <link rel="stylesheet" href="index.css"><!-- 範例樣式 --> </head> <body> <div class="box"> <!-- form --> <div class="form"> <form class="userdata" action="" data-user-name="小明"> <input type="text" class="text"> <button class="submit">送出</button> </form> </div> <!-- list --> <div class="list"> <!-- comment --> <div class="comment"> <span class="name">Jason</span> <span class="say">範例文字</span> - <span class="current">下午3:14:50</span> </div> <div class="comment"> <span class="name">Maple</span> <span class="say">範例文字範例文字範例文字</span> - <span class="current">下午3:14:50</span> </div> <div class="comment"> <span class="name">Ghost</span> <span class="say">範例文字範例文字</span> - <span class="current">下午3:14:50</span> </div> </div> </div> <!-- 提示訊息顯示 --> <div class="message"> <div class="title">Message</div> <div class="liwrap"> <div class="comment"> <span class="name">小華</span> <span class="say">哈囉!</span> - <span class="current">下午3:15:39</span> </div> <div class="comment"> <span class="name">小華</span> <span class="say">哈囉!</span> - <span class="current">下午3:15:39</span> </div> <div class="comment"> <span class="name">小華</span> <span class="say">哈囉!</span> - <span class="current">下午3:15:39</span> </div> </div> </div> </body> </html> |
看到呈現會是這樣子
沒錯,設計師做好的版面,會有標籤的從屬結構。這也是我們現階段普遍設計的觀念。
不過,當我們要開始做動態程式的時候,我們需要透過 vmodel() 達到模組化。在寫 jQuery 之前,先分割這些 HTML,將他們把誰是誰這件事,做個簡單的行為分離 (不是標籤全部分開喔),當成一個一個小模組,像是有功能的零件一樣。這樣的能讓小元件可以被重複利用,還有讀程式碼比較輕鬆。缺點當然就是開發時間慢了一些些 (參考頁底有 sublimetext 片段快速使用)。但如果你的結構是中小型規模以上又使用 jQuery,建議都使用 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 |
<!-- 四種模組的編碼 --> <!-- 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 工程師請添加 hidden 預先隱藏起來 --> <div class="comment" hidden> <span class="name"></span> <span class="say"></span> - <span class="current"></span> </div> <!-- 提示訊息顯示 --> <div class="message"> <div class="title">Message</div> <div class="liwrap"></div> </div> |
看到了嗎?
我們把父元素、子元素打掉了--也就是誰在誰底下的繼承關係,我們透過 vmodel() 規範。HTML 標籤語言,我們就寫在 HTML ,盡可能不要寫在 jQuery 裡面,不然會讓你的程式碼看起來骯髒喔。記得, JS、HTML、CSS 是分離的。
我們的結構會是這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.box //整個討論串+表單 .form //輸入的表單 .list //討論串列表 .comment //每個討論方塊 .name //誰發佈的 .say //說了什麼話 .current //發佈時間 ~ .message //定期接收的提示的訊息 .title //只是顯示標題 .liwrap // 包圍 .comment .comment // 同上 .comment .name .say .current |
開始寫程式
我們設計的流程是:
- 先定義好每個模組,但沒有任何的執行。我們會在 vmodel() 第二個參數設定為 false,代表加載完不會被自動觸發。
- 最後透過 $.vmodel.get(model, true) 去觸發一連串的模組。
記得
- vmodel() 有兩個內建的屬性可以使用喔!分別是 this.root 與 this.selector 可參考說明書。
- 我自己使用縮寫 vs = this 來取代。因為 this 這樣的字詞會出現很多。 使用 vs (vmodel self) 來取代會比較好分辨。
下面會列出所有的程式碼。總共有 4 種模組,我喜歡幫模組以前贅字源命名。例如我這裡的 “md/form” 是使用 “md/” ,或是你也可以使用如 “model_form” 。如果使用 $(“.form”).vmodel(“form”, false, function) ,那當你要替換字詞 form 為 newform 的時候會比較麻煩一些,尤其有時候你趕時間,會很偷懶的把 js 寫在 html 裡面。
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 |
$(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 (){ //發送後可以做一些事情... }); return false; }) } // 清空 this.clean = function (){ vs.root.find(".text").val(null); } }); // 列表模組 $(".list").vmodel("md/list", false, function (){ var vs = this; this.autoload = ['init_position']; // 這裡只負責推送到 .box ,為了範例簡單,我們這裡不推到 message。 this.init_position = function (){ vs.root.appendTo(".box"); } }); // 整體框的主要模組 $(".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.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); } }); //訊息模組。這個模組是主動式的。也就是當使用者送出資料以後,並不見得會馬上啟用訊息模組。 $(".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"); }); }, 200); } }); // 全部都定義好了,我們去觸發 box 模組與 message 模組吧 $.vmodel.get("md/box", true); $.vmodel.get("md/message", true); }) |
解釋動作
我們使用
1 2 3 |
$.vmodel.get("md/box", true); |
會去觸發 md/box autoload。
這裡只建立表單與空列表,也就是把一開始我們打散的結構,放到 .box。「放過去」這件事情,會分別呼叫模組 md/form 與 md/list 並指定第二個參數 true,讓他們觸發 autoload。
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 |
$(".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); } }); |
我們看 md/form
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 |
// 表單模組 $(".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 (){ //發送後可以做一些事情... }); return false; }) } // 清空 this.clean = function (){ vs.root.find(".text").val(null); } }); |
我們會觸發 init_position 與 submit。也就是把 HTML 放到正確的位置,還有綁定表單送出功能。
我們再看 md/list
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 列表模組 $(".list").vmodel("md/list", false, function (){ var vs = this; this.autoload = ['init_position']; // 這裡只負責推送到 .box ,為了範例簡單,我們這裡不推到 message。 this.init_position = function (){ vs.root.appendTo(".box"); } }); |
他這個模組,只是把 list 放到正確位置而已。
這樣目前大家就定位了,我們等候使用者輸入文字,並送出表單吧!所以來看 md/form 的這一段編碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 負責放到 .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 (){ //發送後可以做一些事情... }); return false; }) } |
當送出表單的時候,我們會呼叫 md/comment 底下的 say()
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 |
//討論模組 $(".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.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); } }); |
其中這一段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// 使用者說了什麼 this.say = function (name, say){ vs.set(name, say) .post_to(".box .list"); // 記得清空 $.vmodel.get("md/form").clean(); // 也可以把模板清空 vs.clean(); return vs; } |
就是把使用者是誰、說了什麼,透過 set() 放到了模版,再把模版複製起來,貼到 “.box .list” 底下。這樣就完成使用者的討論串了。
接著我們看看底下的這一段
1 2 3 |
$.vmodel.get("md/message", true); |
他呼叫了 md/message 並觸發 autoload ,
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 |
//訊息模組。這個模組是主動式的。也就是當使用者送出資料以後,並不見得會馬上啟用訊息模組。 $(".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"); }); }, 200); } }); |
主要看 update() 的部分,假設我們利用 AJAX 取得遠端數據 data,我們使用each 批次送到 md/comment 底下的 set() 與 post_to()
1 2 3 4 5 6 7 8 9 |
var comment = $.vmodel.get("md/comment"); $.each(data, function(index, ele) { comment .set(ele.name, ele.say, ele.current) .post_to(vs.selector + " .liwrap"); }); |
讓訊息可以出現再 Message 的地方。
到這邊就結束這段流程了,如果需要複雜一點的實務,我們就參考下一篇,教你如何利用這個範例,擴增回覆功能。
透過這些流程,你會發現我們讓每個小模組都做自己的事情,整篇程式碼看起來,你會發現很優美。若有使用 Sublimetext ,要看 function + 註解你也會很清晰。
如果你的 JS 裡面有很多模組名稱,那麼你可以搜尋 vmodel(“md/box” 編輯器就能帶你到指定位置囉。(不過…..這部分我覺得應該之後會再改良,還不夠方便。)
編輯器 Sublimetext 快速片段
快速產生框架,有幾個片段已經做好了,去 Github 下載。
- vmodel 快速產生架構
- vsfind 快速產生 vs.root.find()
- vson 快速產生 vs.root.on()
這幾個是非常非常之常寫的指令, find 跟 on 完全是 jQuery 的 api 喔!這裡只是讓你少打幾個字而已。
安裝的話,我是下載 Sublimetext 3 免安裝版,所以放到底下
1 2 3 |
Sublime Text\Data\Packages\你自己取一個名稱吧 |
就可以囉!
完整教學範例
- GitHub 下載
- jQuery – vmodel 模組化 jQuery 的編寫結構。
- jQuery – vmodel.js 討論列表範例教學(1) 基本方法
- jQuery – vmodel.js 討論列表範例教學(2) 添加回覆功能
- jQuery – vmodel.js 討論列表範例教學(3) 如何改版?
Comments