jQuery – vmodel.js 討論列表範例教學(1) 基本方法
前言
vmodel 優點:
- HTML、CSS、JS 盡可能去做分離的動作。設計版面這件事情,在設計與支解成模組的過程,可以非常快速的修改。微調樣式,設計師基本上不需要透過工程師,因為使用 HTML + CSS 而已。
- 直接使用 jQuery 的編撰習慣。vmodel() 非常容易上手,他只是提供一個框架結構的思維而已。如果有使用寫過物件導向的經驗,一定很容易上手。
- 適合大型架構。多個工程師負責不同的模型,彼此再拼接起來。
- 善用根結點綁定概念,讓你避免重複渲染。
- 用很簡單的結構,解決複雜的介面呼叫與綁定事件。
至於 MVC 嗎?沒有喔,這裡沒有MVC的概念。這裡只有 模組 (model)、結構 (html)、樣式 (css)。
假設我們要做一個這樣子的討論模組。
左邊是發佈表單與留言串;右邊是提示訊息,用來接收最新的提示訊息。
其中,誰說什麼話,這個樣式與功能是重複的。所以不僅式樣式不要去重複寫,就連程式也是。
下面會示範如何讓每個小模組去交互運用。
切割
一般我們取得設計師做好的靜態版面,會長這樣
<!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() 來建構喔。
所以下面會分割成這樣子
<!-- 四種模組的編碼 --> <!-- 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 是分離的。
我們的結構會是這樣
.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 裡面。
$(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); })
解釋動作
我們使用
$.vmodel.get("md/box", true);
會去觸發 md/box autoload。
這裡只建立表單與空列表,也就是把一開始我們打散的結構,放到 .box。「放過去」這件事情,會分別呼叫模組 md/form 與 md/list 並指定第二個參數 true,讓他們觸發 autoload。
$(".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
// 表單模組 $(".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
// 列表模組 $(".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 的這一段編碼
// 負責放到 .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()
//討論模組 $(".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); } });
其中這一段
// 使用者說了什麼 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” 底下。這樣就完成使用者的討論串了。
接著我們看看底下的這一段
$.vmodel.get("md/message", true);
他呼叫了 md/message 並觸發 autoload ,
//訊息模組。這個模組是主動式的。也就是當使用者送出資料以後,並不見得會馬上啟用訊息模組。 $(".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()
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 免安裝版,所以放到底下
Sublime Text\Data\Packages\你自己取一個名稱吧
就可以囉!
完整教學範例
- GitHub 下載
- jQuery – vmodel 模組化 jQuery 的編寫結構。
- jQuery – vmodel.js 討論列表範例教學(1) 基本方法
- jQuery – vmodel.js 討論列表範例教學(2) 添加回覆功能
- jQuery – vmodel.js 討論列表範例教學(3) 如何改版?
Comments