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

前言

vmodel 優點:

  1. HTML、CSS、JS 盡可能去做分離的動作。設計版面這件事情,在設計與支解成模組的過程,可以非常快速的修改。微調樣式,設計師基本上不需要透過工程師,因為使用 HTML + CSS 而已。
  2. 直接使用 jQuery 的編撰習慣。vmodel() 非常容易上手,他只是提供一個框架結構的思維而已。如果有使用寫過物件導向的經驗,一定很容易上手。
  3. 適合大型架構。多個工程師負責不同的模型,彼此再拼接起來。
  4. 善用根結點綁定概念,讓你避免重複渲染。
  5. 用很簡單的結構,解決複雜的介面呼叫與綁定事件。

至於 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

 

開始寫程式

我們設計的流程是:

  1. 先定義好每個模組,但沒有任何的執行。我們會在 vmodel() 第二個參數設定為 false,代表加載完不會被自動觸發。
  2. 最後透過 $.vmodel.get(model, true) 去觸發一連串的模組。

 

記得

  1. vmodel() 有兩個內建的屬性可以使用喔!分別是 this.root 與 this.selector 可參考說明書
  2. 我自己使用縮寫 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\你自己取一個名稱吧

就可以囉!

 

完整教學範例

 

Comments

發表迴響