來談一個MVVM實務上超級常見的問題: 透過Binding宣告可以輕易將ViewModel屬性繫結到視覺元素上顯示,但數字、日期等內容常需依指定格式呈現才容易閱讀,因此在繫結時多半需要指定格式,例如: XAML在Binding時就可以透過StringFormat指定格式。但Knockout預設的繫結功能並不支援格式指定,所幸憑藉著KO優異的擴充性,這倒不算頭痛的問題。

我慣用的解法是借用Kendo UI程式庫的kendo.toString()工具函數,它能使用類似.NET string.Format()的語法指定日期數字的格式,省去自行發明格式語法及撰寫轉換邏輯的工夫,省時又方便。

線上展示

程式的寫法是利用範例12所介紹的自訂繫結技巧,自訂一個"formatText"繫結來取代"text"繫結。在其中使用kendo.toString()將屬性內容轉換成format(data-bind時可額外宣告)所指定的格式字串,再透過$(element).text()修改元素文字內容,只需要短短幾行就搞定囉~

完整程式範例如下: (因為只用到kendo.toString(),所需的Kendo UI程式庫直接取用CDN上kendo.core.min.js即可)

<!DOCTYPE html>
 
<html>
<head>
    <title>Lab 21 - 格式化顯示</title>
    <script src="../Scripts/jquery-1.7.2.js"></script>
    <script src="../Scripts/knockout-2.1.0.debug.js"></script>
    <script src="http://cdn.kendostatic.com/2012.2.913/js/kendo.core.min.js"></script>
    <script>
        ko.bindingHandlers["formatText"] = {
            update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
                $(element).text(kendo.toString(
                    ko.utils.unwrapObservable(valueAccessor()),
                    allBindingsAccessor().format));
            }
        };
    </script>
    <script>
        function MyViewModel() {
            var self = this;
            self.dateTime = ko.observable(new Date());
            self.date = ko.observable(new Date(2012, 11, 21));
            self.num = ko.observable(1234.567);
            self.perc = ko.observable(0.1234);
        }
        $(function () {
            ko.applyBindings(new MyViewModel());
        });
    </script>
    <style>
        .hi
        {
            color: blue;
            background-color: #f8c705;
            width: 320px;
        }
    </style>
</head>
<body>
    <dl>
        <dt>DateTime (raw)</dt>
        <dd data-bind="text: dateTime"></dd>
        <dt>DateTime HH:mm:ss</dt>
        <dd class="hi" 
            data-bind="formatText: dateTime, format: 'HH:mm:ss'"></dd>
        <dt>Date (raw)</dt>
        <dd data-bind="text: date"></dd>
        <dt>Date yyyy/MM/dd</dt>
        <dd class="hi"
            data-bind="formatText: date, format: 'yyyy/MM/dd'"></dd>
        <dt>Number (raw)</dt>
        <dd data-bind="text: num"></dd>
        <dt>Number N1</dt>
        <dd class="hi"
            data-bind="formatText: num, format: 'n1'"></dd>
        <dt>Percentage (raw)</dt>
        <dd data-bind="text: perc"></dd>
        <dt>Percentage p1</dt>
        <dd class="hi"
            data-bind="formatText: perc, format: 'p1'"></dd>
    </dl>
</body>
</html>

 

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

Comments

# by Mj

請教,為何不用extend做?

# by Jeffrey

to Mj, extend是指用jQuery.extend()在bindingHandlers加入formatText,還是其他方面的應用?

# by mj

透過ko.extenders加載的方式做,感覺比較直覺,也不會跟其他binding互斥。 ko.extenders.formatText = function(target, format){.....}

# by Jeffrey

to mj, extenders的應用主要是透過訂閱target變更事件去觸發特定邏輯,在這個案例中,我想到的做法是增加一個observable()用來儲存格式化後的文字內容,當作需要格式化文字的binding對象(或許還有其他更好做法,請指教)。 但如此設計將會增加額外屬性,在序列化時形成多餘資料。在我的應用中,格式化後的文字多用於靜態文字的呈現(如span, div...),日期、數字等型別需要輸入時會另外binding到Kendo UI的DatePicker等,較無binding相衝的疑慮。而如此規劃,算是依UI需求使用不同binding,ViewModel屬性維持單純的資料型態,因UI而變化的部分盡量放進binding邏輯中,感覺更傾向SoC(關注點分離)。 以上個人淺見。

# by mj

哈,感謝黑暗大大的建議,format確實是view所關心的事情,不應該用extenders去做(= =實做之後發現真個要弄有點複雜,要先把model的值隔離,僅在bind view的時候吐格式化的字串....這樣才不會影響到model取值.....)

Post a comment