[創意料理] 介紹一個不要臉的 jQuery 擴充函式 - jquery-model

這個不要臉的 jQuery 擴充函式 - jquery-model 是在下的拙作,原本是我個人用 jQuery 在開發前端程式時所使用的,同事也拿去用了之後受到好評,應該也可以推薦給大家,它不是一個什麼高大上的東西,只是讓我在將 UI 上的內容兜成 JSON 物件時可以少寫一些程式碼。

緣起

會搞這個東西有兩個原因,第一個原因是「有時候前端的 MVVM 框架太沉重了」,使用 MVVM 的前端框架來開發 SPA 程式真的非常合適,只要專注在 Model 上,以及事先宣告好狀態與 UI 的關係,後續就交給框架來處理,相當輕鬆,但是當我的頁面是無狀態的時候,引用 MVVM 前端框架就顯得有點殺雞用牛刀的感覺。

第二個原因是「團隊以 jQuery 為主要的前端工具」,我們公司的 UI 設計師強項在 HTML 跟 CSS,JavaScript 就弱了一些,所以經常會去找一些現成的 jQuery Plugin 回來自己調樣式,以實現頁面上一些特殊的互動功能,那麼 MVVM 前端框架大都無法跟 jQuery Plugin 直接無縫接軌,運氣好一點是可以找到有大大縫合好的版本,沒有的話就要自己縫了。

基於這兩個原因,我就在想能不能有一個簡便的方法,直接將 HTML 元件的內容轉成一個 JSON 物件,以及將 JSON 物件更新到 HTML 元件上? 因此 jquery-model 就誕生了。

jquery-model

受到 MVVM 框架的啟發,它採用宣告式語法,只有三個:c-modelc-model-numberc-model-html(這個稍後會介紹到),如果 property 是字串型態就用 c-model,是數值型態就用 c-model-number,attribute 的值為 property 的名稱。

<div id="formDiv">
    <h1>formDiv</h1>
    <div><input type="text" value="abc" c-model="abcText"></div>
    <div><input type="text" value="123" c-model-number="abcNumber"></div>
    <div>
        <select value="opt2" c-model="myOpt">
            <option value="opt1">opt1</option>
            <option value="opt2">opt2</option>
            <option value="opt3">opt3</option>
        </select>
    </div>
</div>

如果要輸出 JSON 物件,就使用 model() 方法:

$("#formDiv").model();

// result: { abcText: "abc", abcNumber: 123, myOpt: "opt2" }

如果要將 JSON 物件更新到 HTML 元件上,就用 model({...})model("property", value) 方法:

$("#formDiv").model({ abcText: "cba", abcNumber: 456 });

// --or--

$("#formDiv").model("abcText", "cba");
$("#formDiv").model("abcNumber", 456);

radio

radio 是用 name attribute 組成一個群組的,所以 radio 元件必須指定 name attribute 及其群組名稱,另外 radio 群組中只需要其中一個元件宣告 c-model 或 c-model-number 即可。

<div id="formDiv">
    <h1>formDiv</h1>
    <div>
        <input type="radio" name="myRadio" value="4a" c-model="myOption" />4a
        <input type="radio" name="myRadio" value="44b" />44b
        <input type="radio" name="myRadio" value="444c" />444c
    </div>
</div>

checkbox

checkbox 如果有指定 value attribute,有勾選就會輸出 property,沒有勾選就不會;如果沒有指定 value attribute 就視為是 boolean 型別。

<div id="formDiv">
    <h1>formDiv</h1>
    <div><input type="checkbox" c-model="myChecked1" />some text</div>
    <div><input type="checkbox" c-model-number="myChecked2" />some text</div>
    <div><input type="checkbox" value="happy" c-model="myStatus" />some text</div>
</div>

<script>
    $("#formDiv").model();
    
    // result if all checked: { myChecked1: true, myChecked2: 1, myStatus: "happy" }
    // result if all unchecked: { myChecked1: false, myChecked2: 0 }
</script>

非輸入類型的 HTML 元件

如果想要顯示資料在 <p>、<div>、<span>、... 這種非輸入類型的 HTML 元件上,也是可以的,直接宣告 c-model 就好了,顯示的資料是 HTML 內容的話,則宣告 c-model-html。

<div id="formDiv">
    <h1>formDiv</h1>
    <div c-model="abcText"></div>
    <div c-model="defText"></div>
    <div c-model-html="ghiHtml"></div>
</div>

<script>
    $("#formDiv").model({ abcText: "abc", defText: "def", ghiHtml: "<h1>ghi</h1>" });
    
    // result: <div c-model="abcText">abc</div>
    //               <div c-model="defText">def</div>
    //               <div c-model-html="ghiHtml"><h1>ghi</h1></div>
</script>

由於非輸入類型的 HTML 元件互動性較低,所以在輸出成 JSON 物件時,會自動略過這些非輸入類型的 HTML 元件。

<div id="formDiv">
    <h1>formDiv</h1>
    <div><input type="text" value="aaa" c-model="aaaText"></div>
    <div c-model="abcText"></div>
    <div c-model="defText"></div>
</div>

<script>
    $("#formDiv").model();
    
    // result: { aaaText: "aaa" }
</script>

除此之外,非輸入類型的 HTML 元件在更新資料時,是可以接受 function 的。

<div id="formDiv">
    <h1>formDiv</h1>
    <div c-model="formattedAbcText"></div>
</div>

<script>
    $("#formDiv").model({ formattedAbcText: function () {
        return "formatted abc.";
    }});

    // result: <div c-model="formattedAbcText">formatted abc.</div>
</script>

集合

jquery-model 是有支援集合的,如果確定 jQuery Select 出來的 HTML 元件是一集合,可以呼叫 models() 方法,輸出一個 JSON 物件的集合。

<ul id="list">
    <li>
        <div><input type="text" value="11" c-model-number="id"></div>
        <div><input type="text" value="22" c-model="name"></div>
        <div><input type="text" value="33" c-model-number="age"></div>
    </li>
    <li>
        <input type="hidden" value="44" c-model-number="id" />
        <div c-model="id">44</div>
        <div><input type="text" value="55" c-model="name"></div>
        <div><input type="text" value="66" c-model-number="age"></div>
    </li>
    <li>
        <div><input type="text" value="77" c-model-number="id"></div>
        <div><input type="text" value="88" c-model="name"></div>
        <div><input type="text" value="99" c-model-number="age"></div>
    </li>
</ul>

<script>
    $("#list > li").models();

    // result: [{ id: 11, name: "22", age: 33 }, { id: 44, name: "55", age: 66 }, { id: 77, name: "88", age: 99 }]
</script>

如果只需要取得集合中的一個,則需帶入識別用的 property 名稱及值。

$("#list > li").models("id", 11);

// result: { id: 11, name: "22", age: 33 }

再來是將集合的值更新到 HTML 元件上,使用的是 models([{...}, ...], "keyName") 方法,第一個參數是指定更新的集合,第二個參數是指定識別用的 property 名稱。

var collection = [
    { id: 11, name: "二二二", age: 33 },
    { id: 44, name: "五五五", age: 66 },
    { id: 77, name: "八八八", age: 99 }
];

$("#list > li").models(collection, "id");

如果只需要更新集合中的其中一個項目,可以使用 models({...}, "keyName") 方法。

$("#list > li").models({ id: 11, name: "二二二", age: 33 }, "id");
最後提醒三件事:
  1. 儘量將 c-model 或 c-model-number 宣告在最後面
  2. 當應用在集合的時候,識別用的 HTML 元件必須要是輸入類型的,如果識別值不需要顯示出來,則可以考慮使用 <input type="hidden" />,然後儘量將識別用的元件放置在第一個。
  3. 在想要輸出 JSON 物件的 HTML 元件範圍內,輸入類型元件上不要重覆 property 名稱。