June 29, 2013

KnockoutJS - 使用Knockout Validation實作前端資料驗證

在設計表單頁面時,使用者送出表單資料前,我們一定會為使用者所填入的資料做第一階段的驗證,以避免資料送到後端時不符合預期格式或是不正確的數值。Knockout ValidationKnockout最有名的extender之一,即是為了前端資料驗證使用。關於extender的實作,可參考Knockout官方文件Using extenders to augment observables,在此不詳述。

Knockout Validation內建提供了十幾種驗證規則,可驗證資料是否為必填、是否為Email格式或是特定字串格式等。除此之外開發人員也可自訂驗證規則或是直接套用熱心網友提供的驗證規則

本篇文章將以一個簡單的表單範例來說明如何使用Knockout Validation實作前端資料驗證。表單頁面如下


從上圖可以看到使用者需要輸入的欄位有4個,Email、Password、Confirm Your Password及Name。輸入完畢後按下Register按鈕送出資料,Knockout Validation則是在按下Register按鈕後進行資料驗證。針對表單資料的驗證需要完成以下需求:
  1. Email欄位為必填,且格式需符合正確的Email格式,如pete.chen@outlook.com
  2. Password欄位為必填,長度為8到16個字元,字元至少需包含1個大寫英文字母、1個小寫英文字母及1個數字,如Passw0rd
  3. Confirm Your Password欄位為必填,且需與Password欄位資料相同
  4. Name欄位為必填,長度為2到30個字元。

建立ViewModel
function ViewModel() {

    var self = this;

    self.Email = ko.observable().extend({
        required: true,
        email: true
    });

    self.Password = ko.observable().extend({
        required: true,
        pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$/
    });

    self.Password2 = ko.observable().extend({
        required: true,
        equal: self.Password
    });

    self.Name = ko.observable().extend({
        required: true,
        minLength: 2,
        maxLength: 30
    });

    self.Register = function () {

        self.errors = ko.validation.group(self);

        if (self.isValid()) {
            alert('data sent');
        } else {
            self.errors.showAllMessages();
        }
    };
}

ko.validation.configure();
ko.applyBindings(new ViewModel());


ViewModel內實作了4個observable屬性(Email、Password、Password2和Name)及1個方法(Register)。以下針對ViewModel的實作做說明:
  1. 5~8行:設定Email屬性的驗證規則,使用required定義屬性必需要有資料,email定義資料格式為Email格式
  2. 10~13行:設定Password屬性的驗證規則,使用required定義屬性必需要有資料,pattern定義資料格式需符合正則表示式^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$。這裡要注意的是pattern的設定方式有兩種,一種是將正則表示式以斜線(/)包起來,如程式碼中的/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$/,另一種則是以字串的方式將正則表示式放在單雙引號中,但若引號中含有反斜線(\),則需將其取代為兩個反斜線,如'^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$'
  3. 15~18行:設定Password2屬性的驗證規則,使用required定義屬性必需要有資料,equal定義資料需與Password屬性相同
  4. 20~24行:設定Name屬性的驗證規則,使用required定義屬性必需要有資料,minLength定義資料長度最小2個字元,maxLength定義資料長度最多30個字元
  5. 26~36行:為一函式,其中28行執行資料驗證,並將驗證結果指派給errors屬性。這裡要注意的是,Knockout Validation在驗證ViewModel時,預設只會驗證第一層的屬性。也就是說如果某個屬性本身是個複雜物件(complex object),則其底下的屬性將不會被驗證。若想將完整地驗證ViewModel,可設定deep參數,如ko.validation.group(self, { deep: true });。30行使用isValid()來判斷ViewModel的驗證是否成功,若失敗則在33行執行self.errors.showAllMessages();將驗證錯誤訊息顯示在頁面上,預設訊息會顯示在驗證失敗的欄位右側,如

  6. 38行:完成Knockout Validation設定,例如設定錯誤訊息或欄位的顯示樣式等。在此使用預設設定,詳細說明可參考Knockout Validation Wiki上的Configuration Options

設定View的data bindings
<div>Registration Form</div>
<div>Email:
    <input type="text" data-bind="value: Email" />
</div>
<div>Password:
    <input type="text" data-bind="value: Password" />
</div>
<div>Confirm Your Password:
    <input type="text" data-bind="value: Password2" />
</div>
<div>Name:
    <input type="text" data-bind="value: Name" />
</div>
<div>
    <input type="button" value="Register" data-bind="click: Register" />
</div>

View的設定相對ViewModel簡單多了,在這裡僅僅使用到了valueclick binding

執行結果



自訂錯誤訊息

在未做任何設定下,Knockout Validation將會顯示預設的錯誤訊息,若想顯示自訂的錯誤訊息,可在ViewModel屬性中加入message參數,如
self.Email = ko.observable().extend({
    required: {
        params: true,
        message: "Please enter your email"
    },
    email: {
        params: true,
        message: "The format is not correct"
    }
});
上面程式碼中的message參數即為自訂訊息,要注意的是驗證規則的設定值改為指派給params參數。如需更進階的自訂錯誤訊息方式,可參考Knockout Validation Wiki上的Validation Bindings

June 23, 2013

KnockoutJS - drop-down list應用(2)

KnockoutJS - drop-down list應用(1)範例中,繫結至drop-down list的資料皆是單純的字串陣列,如
self.currencies = ko.observableArray(["USD", "GBP", "EUR", "JPY"]);
以上述方式在drop-down list不管是選項文字或是選項值都是相同的,可由Chrome的開發人員工具得證。


自訂drop-down list選項文字及選項值
在實務上,很常遇到的情況是選項文字與選項值是不同的,例如選項文字是讓使用者能理解的資料,而選項值則是實際送至後端儲存至資料庫的資料,這時繫結至drop-down list就不再是單純的字串陣列,而是Javascript物件陣列了。如
<select data-bind="options: CreditCards, optionsText: 'CardNumber',
    optionsValue: 'CardId', value: SelectedCard, optionsCaption: '請選擇'"></select>
<span data-bind="text: SelectedCard"></span> selected.
function ViewModel() {
    var self = this;
    self.CreditCards = ko.observableArray([{
        "CardId": 1,
            "CardNumber": "1234-5678-1234-0001",
            "CardHolder": "Pete"
    }, {
        "CardId": 2,
            "CardNumber": "1234-5678-1234-0002",
            "CardHolder": "Claire"
    }, {
        "CardId": 3,
            "CardNumber": "1234-5678-1234-0003",
            "CardHolder": "Pudding"
    }]);

    self.SelectedCard = ko.observable();
}

ko.applyBindings(new ViewModel());

上面的範例中,CreditCards是一個observableArray,裡面有3張信用卡資料,分別有CardId、CardNumber及CardHolder 3個屬性。在View中,options binding的optionsText參數指定了CardNumber,表示以CardNumber做為drop-down list的選項文字,而optionsValue參數指定了CardId,表示以CardId做為drop-down list的選項值,要注意的是CardId及CardNumer需有單引號包起來。另外,SelectedCard之值即為選項值(CardId之值)。執行以上範例可以看到選項文字及選項值被正確地設定。



利用選項值取得選項文字
對後端來說,有意義的資料大多是選項值,也就是範例中的CardId,但如果要顯示給使用者知道哪個選項被選取了,當然是要顯示對使用者有意義的選項文字,如範例中的卡號。要知道被選取的選項文字,可以透過jQuery的選取器(selector)來取得。如果不使用jQuery,則需透過Knockout的utils函式庫來搜尋CreditCards陣列來取得選項文字。
<select data-bind="options: CreditCards, optionsText: 'CardNumber',
    optionsValue: 'CardId', value: SelectedCard, optionsCaption: '請選擇'"></select>
<span data-bind="text: SelectedCardText"></span> selected.

self.SelectedCardText = ko.computed(function () {

    var search = self.SelectedCard();

    if (!search) {

        return null;

    } else {

        var matchedItem = ko.utils.arrayFirst(self.CreditCards(), function (item) {
            return item.CardId == search;
        });

        return matchedItem.CardNumber;
    }
});


在原本的ViewModel中,我們新增了一個computed屬性SelectedCardText來顯示卡號。執行結果如下



事實上,還有另一個方式可以直接取得選項文字。在前面的範例中我們指定了optionsValue為CardId,如果不指定optionsValue,取得的SelectedCard不會是純量值(CardId)而會是一個含有CardId、CardNumber及CardHolder的Javascript物件。如此便可透過這個Javascript物件取得CardNumber,也就是選項文字。
<select data-bind="options: CreditCards, optionsText: 'CardNumber',
    value: SelectedCard, optionsCaption: '請選擇'"></select>
<span data-bind="text: SelectedCard() ? SelectedCard().CardNumber : 'none'"></span> selected.

function ViewModel() {
    var self = this;
    self.CreditCards = ko.observableArray([{
        "CardId": 1,
            "CardNumber": "1234-5678-1234-0001",
            "CardHolder": "Pete"
    }, {
        "CardId": 2,
            "CardNumber": "1234-5678-1234-0002",
            "CardHolder": "Claire"
    }, {
        "CardId": 3,
            "CardNumber": "1234-5678-1234-0003",
            "CardHolder": "Pudding"
    }]);

    self.SelectedCard = ko.observable();
}

ko.applyBindings(new ViewModel());

不過這個方式有個潛在的問題會導致jQuery Validation驗証失敗

自訂選項文字
在選項文字的顯示上,除了可以直接指定Javascript物件中的某個屬性,也可以在data binding時,動態地組合出顯示文字,例如顯示卡號再加上持卡人名稱。
<select data-bind="options: CreditCards,
    optionsText: function(item){return item.CardNumber + '(' + item.CardHolder + ')'},
    value: SelectedCard, optionsCaption: '請選擇'"></select>
<span data-bind="text: SelectedCard() ? SelectedCard().CardNumber : 'none'"></span> selected.

June 20, 2013

KnockoutJS - drop-down list應用(1)

將陣列資料繫結至drop-down list
在HTML語法中,drop-down list主要是以select標籤來實作。在KnockoutJS裡,我們可以透過設定observableArray搭配options binding,將陣列資料繄結到drop-down list裡,此陣列可以是由資料庫或其它資料來源取得的資料,如透過AJAX呼叫某支web service取得。
<select data-bind="options: currencies"></select>
function ViewModel() {
    var self = this;
    self.currencies = ko.observableArray(["USD", "GBP", "EUR", "JPY"]);
}

ko.applyBindings(new ViewModel());
在上面的程式碼中,將currencies指定給options binding,KnockoutJS就自動幫我們把選項(option標籤)列出來。



取得被選取的選項值
如果想知道是哪個選項被選取,需先設定value binding
<select data-bind="options: currencies, value: selectedCurrency"></select>
<span data-bind="text: selectedCurrency"></span> selected!

function ViewModel() {
    var self = this;
    self.currencies = ko.observableArray(["USD", "GBP", "EUR", "JPY"]);
    self.selectedCurrency = ko.observable("USD");
}

ko.applyBindings(new ViewModel());

上述程式中加入了selectedCurrency屬性並指定給value binding,當使用者選擇了drop-down list中的任何一個選項時,selectedCurrency就會被寫入選項值。selectedCurrency的預設值為USD,也可以是currencies陣列中其它的選項。



為drop-down list加入非陣列資料中的預設選項
在上述的範例中,drop-down list預設在頁面載入時就選取USD。然而在某些情況下(如drop-down list為非必選欄位),我們會希望drop-down list有個選項也許叫"請選擇",而不直接預設任何一個currencies屬性裡的值。要達成這個需求,一個方法是直接在currencies中加入"請選擇",如此KnockoutJS便會將請選擇選項加到select標籤下,如
self.currencies = ko.observableArray(["請選擇", "USD", "GBP", "EUR", "JPY"]);

然而這方式雖然可行但不是個好作法,因為請選擇這個選項與currencies屬性的命名在意義上是不同的。

在KnockoutJS中其實已經為options binding內建了一個名為optionsCaption的參數用來實現上述的需求,只要在optionsCaption內指定需要顯示的文字即可。
<select data-bind="options: currencies, value: selectedCurrency, optionsCaption: '請選擇'">
</select>
<span data-bind="text: selectedCurrency"></span> selected!
function ViewModel() {
    var self = this;
    self.currencies = ko.observableArray(["USD", "GBP", "EUR", "JPY"]);
    self.selectedCurrency = ko.observable();
}

ko.applyBindings(new ViewModel());

在上面的程式碼中,selectedCurrency預設無設定任何資料,drop-down list預設的選項便是請選擇。



延伸閱讀
KnockoutJS - drop-down list應用(2)

June 7, 2013

KnockoutJS - radio button應用(2)

使用foreach binding顯示多個radio button 
有時radio button是動態產生的,如根據資料庫取得的資料來顯示對應的radio button,這時就需要搭配foreach binding來產生radio button。
<div data-bind="foreach: techniques">
    <label>
        <input type="radio" data-bind="value: value, checked: $parent.selectedTechnique" />
        <span data-bind="text: text"></span>
    </label>
</div>

<div data-bind="text: selectedTechnique"></div>
function ViewModel() {
    var self = this;
    self.techniques = [{
        text: "ASP.NET Web Forms",
        value: "webforms"
    }, {
        text: "ASP.NET MVC",
        value: "mvc"
    }, {
        text: "ASP.NET Web API",
        value: "webapi"
    }];

    self.selectedTechnique = ko.observable("");
}

ko.applyBindings(new ViewModel());

techniques為陣列,可以是資料庫取出的資料,selectedTechnique則為使用者選取的radio button之值。在View中,selectedTechnique需透過$parent$root取得,因為在foreach binding中的binding context與selectedTechnique的binding context不同。執行結果可參考



透過陣列取得選項文字
由上面執行結果可看出selectedTechnique顯示的是radio button之,如果想要顯示radio button的選項文字,可參考KnockoutJS - radio button應用(1)一文使用jQuery搭配KnockoutJS的subscribe功能取得選項文字。還有另一種方式不需使用到jQuery操作DOM來取得選項文字,而是透過KnockoutJS的utils函式庫搜尋techniques陣列,找出radio button值相對應的選項文字。
<div data-bind="foreach: techniques">
    <label>
        <input type="radio" data-bind="value: value, checked: $parent.selectedTechnique" />
        <span data-bind="text: text"></span>
    </label>
</div>

<div data-bind="text: selectedTechniqueText"></div>
function ViewModel() {
    var self = this;
    self.techniques = [{
        text: "ASP.NET Web Forms",
        value: "webforms"
    }, {
        text: "ASP.NET MVC",
        value: "mvc"
    }, {
        text: "ASP.NET Web API",
        value: "webapi"
    }];

    self.selectedTechnique = ko.observable("");

    self.selectedTechniqueText = ko.computed(function () {

        var search = self.selectedTechnique().toLowerCase();

        if (!search) {

            return null;

        } else {

            var matchedItem = ko.utils.arrayFirst(self.techniques, function (item) {
                return ko.utils.stringStartsWith(item.value.toLowerCase(), search);
            });

            return matchedItem.text;
        }
    });
}

ko.applyBindings(new ViewModel());

上面程式碼片段是由Utility Functions in KnockoutJS一文中的Searching for an item in the array章節修改而來,因為KnockoutJS的官網並未放上utils函式庫的參考文件。有一點要特別注意的是,stringStartsWith函式在KnockoutJS release的版本中並未被放入,但在debug的版本有,所以在使用時需手動複製加入

執行結果



使用Virtual Element控制radio button選項文字
在前個例子中,radio button的選項文字是透過span標籤加上text binding達成,我們也可以透過KnockoutJS的Virtual Element以註解方式將radio button的選項文字加入。
<div data-bind="foreach: techniques">
    <label>
        <input type="radio" data-bind="value: value, checked: $parent.selectedTechnique" />
         <!--ko text: text--><!--/ko-->
    </label>
</div>
<div data-bind="text: selectedTechnique"></div>


使用template binding顯示多個radio button
除了使用foreach binding外,也可以再額外搭配template binding讓radio button可以重複使用。template內為要重複顯示的資料,div中template binding的name屬性值需與script中的id屬性值相同。如同前面的例子所述,selectedTechnique需透過$parent$root取得,因為在foreach binding中的binding context與selectedTechniques的binding context不同。
<div data-bind="template: { name: 'multipleradibuttons', foreach: techniques }"></div>
<script type="text/html" id="multipleradibuttons">
    <label>
        <input type="radio" data-bind="value: value, checked: $parent.selectedTechnique" />
        <!--ko text: text--><!--/ko-->
    </label>
</script>
<div data-bind="text: selectedTechnique"></div>

其它應用
KnockoutJS - radio button與textbox混搭應用

June 6, 2013

KnockoutJS - radio button應用(1)

多個radio button
radio button常見的應用之一就是在多個選項中讓使用者單選出其中一個項目。與checkbox相同,radio button也是使用checked binding來讀取或寫入當前使用者所選擇的項目。對radio button來說,因為所選擇的項目只有一個,所以ViewModel用於data binding的屬性是observable而不是observableArray。
<label>
    <input name="aspnet" type="radio" value="webforms" data-bind="checked: technique" />ASP.NET Web Forms
</label>
<label>
    <input name="aspnet" type="radio" value="mvc" data-bind="checked: technique" />ASP.NET MVC
</label>
<label>
    <input name="aspnet" type="radio" value="webapi" data-bind="checked: technique" />ASP.NET Web API
</label>
<div data-bind="text: technique"></div>
function ViewModel() {
    var self = this;
    self.technique = ko.observable("");
}

ko.applyBindings(new ViewModel());

執行結果



顯示radio button選項文字
從上面的執行結果可以知道,當使用者選擇ASP.NET Web Forms,下方顯示的是webforms,也就是radio button之。這值通常是我們取得後要更新回後端用的,對使用者而言未必是有意義的資料。在某些情況下,使用者想看到並不是radio button的值,而是選項的名稱,如ASP.NET Web Forms,這時我們就必須在使用者按下選項後,即時抓取radio button後面的選項文字來顯示給使用者知道。
<label>
    <input name="aspnet" type="radio" value="webforms" data-bind="checked: technique" />ASP.NET Web Forms
</label>
<label>
    <input name="aspnet" type="radio" value="mvc" data-bind="checked: technique" />ASP.NET MVC
</label>
<label>
    <input name="aspnet" type="radio" value="webapi" data-bind="checked: technique" />ASP.NET Web API
</label>
<div data-bind="text: techniqueText"></div>
function ViewModel() {
    var self = this;
    self.technique = ko.observable("");
    self.technique.subscribe(function (newValue) {
        self.techniqueText($("input[name=aspnet]:checked").parent().contents().filter(function () {
            return this.nodeType == 3;
        }).text());
    });

    self.techniqueText = ko.observable("");
}

ko.applyBindings(new ViewModel());

與之前的例子不同的是,我們新增了一個techniqueText屬性值用來顯示使用者所選擇的選項文字。除此之外,我們利用subscribe函式訂閱technique屬性值的變動,jQuery會取得被選取的radio button後方的選項文字。

執行結果


上述方法雖然可行,但如果在選項文字後方又有其它不必要顯示出來的文字的話(如括號註解說明)也會一併顯示出來。

另一個可行的方法則是將選項文字加上標籤如span,再搭配jQuery的next函式取得radio button的下一個element(span標籤),即可取得選項文字。
<label>
    <input name="aspnet" type="radio" value="webforms" data-bind="checked: technique" /><span>ASP.NET Web Form</span></label>
<label>
    <input name="aspnet" type="radio" value="mvc" data-bind="checked: technique" /><span>ASP.NET MVC</span>
</label>
<label>
    <input name="aspnet" type="radio" value="webapi" data-bind="checked: technique" /><span>ASP.NET Web API</span>
</label>
<div data-bind="text: techniqueText"></div>
function ViewModel() {
    var self = this;
    self.technique = ko.observable("");
    self.technique.subscribe(function (newValue) {
        self.techniqueText($("input[name=aspnet]:checked").next().text());
    });

    self.techniqueText = ko.observable("");
}

ko.applyBindings(new ViewModel());

執行結果



延伸閱讀
KnockoutJS - radio button應用(2)

June 5, 2013

KnockoutJS - checkbox應用(2)

使用foreach binding顯示多個checkbox
有時checkbox是動態產生的,如根據資料庫取得的資料來顯示對應的checkbox,這時就需要搭配foreach binding來產生checkbox。
<div data-bind="foreach: techniques">
    <label>
        <input type="checkbox" data-bind="value: value, checked: $parent.selectedTechniques" />
        <span data-bind="text: text"></span>
    </label>
</div>

<div data-bind="text: selectedTechniques().join(', ')"></div>
function ViewModel() {
    var self = this;
    self.techniques = [{
        text: "ASP.NET Web Forms",
        value: "webforms"
    }, {
        text: "ASP.NET MVC",
        value: "mvc"
    }, {
        text: "ASP.NET Web API",
        value: "webapi"
    }];

    self.selectedTechniques = ko.observableArray();
}

ko.applyBindings(new ViewModel());
techniques為陣列,可以是資料庫取出的資料,selectedTechniques則為使用者勾選的checkbox之值。在View中,selectedTechniques需透過$parent$root取得,因為在foreach binding中的binding context與selectedTechniques的binding context不同。執行結果可參考


使用Virtual Element控制checkbox顯示文字
在前個例子中,checkbox的顯示文字是透過span標籤加上text binding達成,我們也可以透過KnockoutJS的Virtual Element以註解方式將checkobx的顯示文字加入。


使用template binding顯示多個checkbox
除了使用foreach binding外,也可以再額外搭配template binding讓checkbox可以重複使用。template內為要重複顯示的資料,div中template binding的name屬性值需與script中的id屬性值相同。如同前面的例子所述,selectedTechniques需透過$parent$root取得,因為在foreach binding中的binding context與selectedTechniques的binding context不同。



KnockoutJS - checkbox應用(1)

單一checkbox
在使用單一checkbox時,checked binding所儲存的值為true或false
<label>
    <input type="checkbox" data-bind="checked: hasChecked" />Tick me!
</label>
<div>
    hasChecked: <span data-bind="text: hasChecked"></span>
</div>
function ViewModel() {
    var self = this;
    self.hasChecked = ko.observable(false);
}

ko.applyBindings(new ViewModel());

勾選checkbox時,hasChecked屬性會被指派為true,反之為false。執行結果可參考



單一checkbox搭配顯示開關
某些情況下我們會希望當checkbox被勾選時才把資料顯示出來,例如顯示一段警告訊息或是表單資料,這時可以使用checked binding搭配visible binding來控制資料顯示與否。



多個checkbox
checked binding除了可以使用在單一一個checkbox,也可使用於多個checkbox。在多個checkbox的情形下,checked binding裡的值就不是布林而是陣列,需透過observableArray去讀取。
<label>
    <input type="checkbox" value="webforms" data-bind="checked: techniques" />ASP.NET WebForms
</label>
<label>
    <input type="checkbox" value="mvc" data-bind="checked: techniques" />ASP.NET MVC
</label>
<label>
    <input type="checkbox" value="webapi" data-bind="checked: techniques" />ASP.NET Web API
</label>
<div data-bind="text: techniques().join(', ')"></div>
function ViewModel() {
    var self = this;
    self.techniques = ko.observableArray();
}

ko.applyBindings(new ViewModel());

techniques為一observableArray,當checkbox被勾選時,checkbox之值即被寫入techniques中。執行結果可參考



如果預設要將ASP.NET WebForms及ASP.NET MVC兩個checkbox勾選,可在ViewModel裡將techniques設為ko.observableArray(["webforms", "mvc"])

延伸閱讀
KnockoutJS - checkbox應用(2)

June 3, 2013

解決Twitter Bootstrap Date Picker無法更新KnockoutJS ViewModel問題

某個表單需要使用者填入出生年月日,格式為為yyyy-mm-dd。研究了幾個套件如Datepicker for Bootstrapbootstrap-datepickerDate/Time Picker for Twitter Boostrap,最後選用了Datepicker for Bootstrap。bootstrap-datepicker及Date/Time Picker for Twitter Boostrap都是從Datepicker for Bootstrap衍生而來,bootstrap-datepicker提供較Datepicker for Bootstrap多的函式,而Date/Time Picker for Twitter Boostrap增加了時間的選擇。

使用上很簡單,在需要使用date picker的textbox裡先設定其id(或是CSS類別),再透過jQuery呼叫datepicker函式,如
<input id="dob" type="text" data-date-format="yyyy-mm-dd" data-bind="value: DateOfBirth" />

$('#dob').datepicker();

設定成功的話,點選textbox就可以看到date picker出現在textbox下方。點選日期後也會將符合我們設定格式(yyyy-mm-dd)的日期顯示在textbox裡。


然而在使用上,會出現兩個問題。

第一個問題其實也不算真正的問題,比較算是操作上的偏好。你會發現在點選了date picker上的日期後date picker仍會停留在畫面上,直到textbox失去了focus,例如滑鼠點選了頁面其它地方。我個人比較喜歡在選完日期後,就讓date picker自動關閉。透過changeDate事件hide參數,在使用者選完日期後可將date picker關閉,如
$('#dob').datepicker().on('changeDate', function(e) {
    $(this).datepicker('hide');
});

第二個問題則是當使用者選完日期後,KnockoutJS的ViewModel(DateOfBirth屬性)並沒有被即時更新。原因是因為KnockoutJS預設是在控制項有先取得focus,資料變更並失去focus後才會更新ViewModel,而以date picker直接更新了textbox之值並沒有觸發change事件讓ViewModel被更新。要解決此問題,可以透過自訂KnockoutJS的custom binding,在日期被選擇後更新ViewModel。
        ko.bindingHandlers.datepicker = {
            init: function(element, valueAccessor, allBindingsAccessor) {
                var options = allBindingsAccessor().datepickerOptions || {};
                $(element).datepicker(options).on('changeDate', function(e) {
                    var observable = valueAccessor();
                    observable($(element).val());
                    $(element).datepicker("hide");
                });

                var value = ko.utils.unwrapObservable(valueAccessor());

                if (typeof (value) == "undefined" || value == "") {
                    return;
                }
                
                $(element).val(value);
                $(element).datepicker("setValue", value);
            }
        };

<input id="dob" type="text" data-bind="datepicker: DateOfBirth,
                   datepickerOptions: { 'format' : 'yyyy-mm-dd' }" />
KnockoutJS的custom binding分為兩個部份,initupdate。init主要用在初始化binding,update用於當ViewModel有更新時。詳細的參數說明可參考custom binding。在這裡我們僅需使用到init。

第3行:取得textbox裡的datapickerOptions binding,這個binding裡設定了date picker的options

第4~8行:將取得的options代入date picker中,設定當changeDate事件觸發時(即使用者選擇日期完),將textbox裡的值取出來並更新至ViewModel,最後再將date picker關閉

第10~17行:將ViewModel裡的屬性(DateOfBirth)值取出來,如果值不為undefined或是空字串,則將該值指派給textbox及date picker,因為ViewModel在一建立時可能有預設值

設定成功的話,選擇完日期date picker會自動關閉,ViewModel也會同時更新。

June 1, 2013

warning MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5" were not found

在使用Jenkins建置一個.NET 4.5函式庫專案時發現,雖然專案建置成功,但進入Console Output查看時卻出現了warning MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5" were not found的警告訊息,且bin資料夾也多了幾個沒見過的檔案。
我的Jenkins是安裝在Windows 2008 Server 64-bit R2 SP1上,系統沒有安裝任何Visual Studio版本,僅安裝.NET Framework 2.0/3.5/4.0/4.5。直覺告訴我,只要把Visual Studio 2012安裝上Jenkins  Server就可以解決此問題了。的確,在我安裝完VS 2012後,這個問題得到了解決,bin資料夾那些沒見過的檔案也不見了。然而,為了解決這個問題竟然要大費周章安裝VS 2012並佔用掉2~3 gigabytes的空間,也太不科學了。

經過明查暗訪,在stackoverflow發現國外網友也遇到了相同問題,一樣是在CI Server建置專案時遇到這個警告訊息,只不過該名網友是建置.NET 4.0專案,而我是.NET 4.5。文中的回應建議了兩種方式來解決。

方法1:安裝Windows SDK

如果是.NET 4.0專案,可以安裝Microsoft Windows SDK for Windows 7 and .NET Framework 4;.NET 4.5專案則安裝Windows Software Development Kit (SDK) for Windows 8。這裡以安裝Windows Software Development Kit (SDK) for Windows 8為例。

下載sdksetup.exe

執行sdksetup.exe,選擇Install the Windows Software Development Kit to this computer,點選Next

選擇No,點選Next

點選Accept

僅勾選.NET Framework 4.5 Software Development Kit,點選Install


安裝完成後點選Close。接下來再回到Jenkins建置一次專案可以發現警告訊息已不再出現,bin資料夾也沒有莫名的檔案。


方法2:複製安裝有VS 2012開發環境主機的Reference Assemblies資料夾

找一台有安裝VS 2012的主機,將C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5整個資料夾複製到Jenkins主機上的路徑C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v4.5


至Jenkins的Configure System頁面,將MSBuild的參數加入/p:FrameworkPathOverride="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v4.5"並儲存資料


再次建置.NET 4.5專案,可以看到專案已成功建置,警告訊息也未出現。