May 30, 2013

與Jenkins共舞 - 建置Visual Studio專案

與Jenkins共舞 - 安裝MSBuild Plugin中我們完成MSBuild plugin的安裝與設定,本篇文章將介紹如何設定Jenkins的Build Job,讓Jenkins為我們建置Visual Studio專案。

瀏覽首頁,點選New Job

輸入Job name(如專案名稱)後按下OK


Source Code Management區塊中撰擇原始碼控管系統,這裡以Subversion為例。
Repository URL為原始碼存放位址,如https://192.168.0.63/svn/Pete/MyProject/trunk/src/Project/Pete.Domain。Jenkins會嘗試連線到Subversion的Repository,如果需要認證,Jenkins會出現如下頁面

認證成功後Jenkins會將認證資訊儲存起來,之後如果建立其它的Job用到相同位址就不必再認證一次。


Build Triggers區塊中有三個選項


  • Build after other projects are built表示當指定的Job執行完後,才會執行當前的Job
  • Build periodically表示定時執行當前的Job,典型的例子像是nightly build
  • Poll SCM表示定時輪詢原始碼控管系統,當Repository有變動時Jenkins會取出最新的程式碼並執行當前Job

在這裡建議可先選擇Poll SCM,並設定Schedule* * * * *,如此Jenkins會每分鐘輪詢一次Subversion。如要知道Schedule的格式如何設定,可點選旁邊的說明圖示。


Build區塊中選擇Build a Visual Studio project or solution using MSBuild
  • MSBuild Version為在Configure System頁面中所設定的MSBuild,你可以選擇符合當前Job所需的MSBuild
  • MSBuild Build File為Visual Studio的專案檔或方案檔檔名,如Pete.Domain.csproj
  • Command Line Arguments為MSBuild可接受的參數,例如/p:Configuration=Release表示要以Release模式來建置專案

到這個步驟結束我們已經完成了Build Job的設定,按下Save儲存資料。接下來重回Jenkins首頁,可以看到剛剛設定完成的Job。


接著將滑鼠移到Job名稱上會出現一個浮動選單並點選Build Now,Jenkins會開始建置Visual Studio專案。
在頁面左側可以看到Job執行的進度


Job執行成功的話,會顯示藍色圖示,反之則為紅色。如果要詳細再確認的話,可以到workspace資料夾裡或點選Job名稱,進入Workspace看看bin資料夾是否有被產生出來。


另外,也可以到Console Output(點選Job名稱,觀看Build History最後一筆執行紀錄)瀏覽Jenkins執行Job的歷程以確認結果。

May 29, 2013

KnockoutJS - radio button與textbox混搭應用

有一組表單控制項佈置如下

我們希望可以達到以下需求,如何以KnockoutJS來實作?
  1. 選擇Other時,textbox才可以輸入資料
  2. 選擇Other時,textbox要自動取得focus
  3. 選擇Other輸入資料後,再選擇其它項目時,將textbox內的資料清空

建立View
<h3>What's your favorite fruit?</h5>
<label>
    <input name="fruit" type="radio" value="Apple" />Apple</label>
<label>
    <input name="fruit" type="radio" value="Banana" />Banana</label>
<label>
    <input name="fruit" type="radio" value="Other" />Other:</label>
<input type="text" />


建立ViewModel
function ViewModel() {
    var self = this;
    self.fruit = ko.observable("");
    self.hasOther = ko.computed(function () {
        return self.fruit() == "Other";
    });

    self.hasOther.subscribe(function (newValue) {
        if (!newValue) {
            self.other('');
        }
    });

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

ko.applyBindings(new ViewModel());
  • fruit屬性表示所選擇的水果種類
  • hasOther屬性表示是否選擇Other自行輸入水果名稱。hasOther為一個computed屬性,如果使用者選擇的是Other則hasOther為true
  • other屬性表示使用者於textbox裡輸入的值
  • hasOther屬性使用subscribe函式,當使用者選擇的不是Other時就將屬性other清空。

建立Data Bindings
<h3>What's your favorite fruit?</h5>
<label>
    <input name="fruit" type="radio" value="Apple" data-bind="checked: fruit" />Apple</label>
<label>
    <input name="fruit" type="radio" value="Banana" data-bind="checked: fruit" />Banana</label>
<label>
    <input name="fruit" type="radio" value="Other" data-bind="checked: fruit" />Other:</label>
<input type="text" data-bind="value: other, enable: hasOther(), hasfocus: hasOther()" />
  • radio button設定checked binding來記錄fruit屬性值
  • textbox設定value binding記錄使用者輸入的水果名稱
  • textbox設定enable bindng,當hasOther屬性為true(使用者撰擇Other)時,textbox方能編輯
  • textbox設定hasfocus binding,當hasOther屬性為true(使用者撰擇Other)時,textbox就能取得focus
執行結果請參考http://jsfiddle.net/petekcchen/NZTD4/

May 28, 2013

KnockoutJS - checkbox與textbox混搭應用 (2)

KnockoutJS - checkbox與textbox混搭應用 (1),我們希望在使用者輸入完資料到textbox後,表單控制項下方可直接顯示輸入結果,並且在取消勾選checkbox時,將textbox中的資料與下方輸入結果一併清空。

建立ViewModel
function ViewModel() {
    var self = this;
    self.hasOther = ko.observable(false);
    self.other = ko.observable("");
}

ko.applyBindings(new ViewModel());
新增了other屬性,用來存放textbox之值。

建立Data Bindings
<label>
    <input type="checkbox" data-bind="checked: hasOther" />Others:</label>
<input type="text" data-bind="value: other, valueUpdate: 'afterkeydown', 
                                enable: hasOther, hasfocus: hasOther()" />
<div data-bind="text: other"></div>
textbox中,我們新增了value binding及它的參數valueUpdate,並設為afterkeydown,表示當使用者一按下鍵盤,KnockoutJS就隨即把textbox中的資料更新到ViewModel的other屬性裡。而在表單控制項下方我們佈置了一個div,設定其text binding,如此當other屬性一有更新,KnockoutJS就隨即更新UI。

看一下執行結果,在textbox中一輸入資料,所輸入的資料就馬上顯示在下方的div區塊裡。

到目前為止,已完成了前半段需求 - 在使用者輸入完資料到textbox後,表單控制項下方可直接顯示輸入結果,接下來要實作後半段需求 - 取消勾選checkbox時,將textbox中的資料與下方輸入結果一併清空

實作這個需求,有兩種可行方式。第一種從View來著手,另一種則是從ViewModel來達成。

View
<label>
    <input type="checkbox" data-bind="checked: hasOther" />Others:</label>
<input type="text" data-bind="value: hasOther() ? other : '', valueUpdate: 'afterkeydown',
                                enable: hasOther, hasfocus: hasOther()" />
<div data-bind="text: hasOther() ? other() : ''"></div>

value binding修改為 hasOther() ? other : '',表示checkbox被取消勾選時,hasOther會被設為false,textbox的值就會是空的。div裡的text binding也是相同道理。從http://jsfiddle.net/z4YJe/5/可以看到執行結果,在checkbox取消勾選時,textbox被disabled外,其值被清除,下方div區塊內的資料也不會顯示出來。不過當再次勾選checkbox時,會發現之前輸入的資料又顯示出來了,這是因為我們只是在顯示資料上作手腳,實際上ViewModel裡的資料還存在,KnockoutJS馬上就透過data binding將資料顯示在UI上。

ViewModel

要徹底清除資料的話需從ViewModel著手,只要清除了other屬性內的資料,當資料繫結到UI時就會是清空的狀態。但要如何在checked或是enable binding中,讓textbox及div裡的資料清空?透過subscribe函式訂閱hasOther屬性。
    <input type="checkbox" data-bind="checked: hasOther" />Others:</label>
<input type="text" data-bind="value: other, valueUpdate: 'afterkeydown',
                                enable: hasOther, hasfocus: hasOther()" />
<div data-bind="text: other"></div>

function ViewModel() {
    var self = this;
    self.hasOther = ko.observable(false);
    self.hasOther.subscribe(function (newValue) {
        if (!newValue) {
            self.other('');
        }
    });

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

ko.applyBindings(new ViewModel());

在subscribe函式中,檢查hasOther被更新的值為false時,將other屬性值清空,textbox及div內的資料就會一併清空。執行結果可參考http://jsfiddle.net/z4YJe/6/

KnockoutJS - checkbox與textbox混搭應用 (1)


建立ViewModel
function ViewModel() {
    var self = this;
    self.hasOther = ko.observable(false);
}

ko.applyBindings(new ViewModel());

hasOther表示checkbox是否被勾選

設定View的bindings
<label>
    <input type="checkbox" data-bind="checked: hasOther" />Others:</label>
<input type="text" data-bind="enable: hasOther" />

checkbox裡用了checked binding,當checkbox被勾選時,ViewModel裡的hasOther會被更新成true
textbox裡則用了enable binding,當hasOther為true時,textbox就會被enabled。當然你也可以選擇用disable binding來實作。

到目前為止已達到需求的前半段 - 有勾選checkbox時,textbox才可以編輯,執行結果可參考http://jsfiddle.net/petekcchen/z4YJe/

接下來實作後半段的需求 - checkbox被勾選的話,textbox要取得focus。看到focus,會想到hasfocus binding,只要將hasfocus binding設為hasOther,就可以達到當hasOther為true時,hasfocus也會是true,textbox就會有focus。OK,將hasfocus binding加入View中(請注意,hasfocus的focus首字是小寫)。
<label>
    <input type="checkbox" data-bind="checked: hasOther" />Others:</label>
<input type="text" data-bind="enable: hasOther, hasfocus: hasOther" />

執行結果如http://jsfiddle.net/petekcchen/z4YJe/2/,勾選checkbox,嗯!不錯,textobx取得了focus。取消勾選checkbox,咦?取消不了了!

事實上是我誤會hasfocus的意思,hasfocus binding是雙向的(two-way binding),也就是說當勾選checkbox第一次時,因為checkbox將hasOther設為true了,KnockoutJS也就將focus給textbox。但第二次要取消勾選checkbox時,因為離開了textbox(失去focus),所以KnockoutJS就將hasOther馬上設為false,接著checkbox也被KnockoutJS取消勾選。但消取掉後因為此時滑鼠點擊下去了,所以又將checkbox勾選起來,自然textbox又取得focus了。

那要怎麼解決呢?相當簡單,以hasOther()來讀取ViewModel中的hasOther屬性,hasOther在focus變動時就不會寫回ViewModel了,執行結果可參考http://jsfiddle.net/petekcchen/z4YJe/3/,這種作法有點像是讓hasfocus只能唯讀取得hasOther,事實上KnockoutJS的Documentation也有提到讀取ViewModel屬性的方式(Not all browsers support JavaScript getters and setters (* cough * IE * cough *), so for compatibility, ko.observable objects are actually functions.)。另一種可能的做法則是透過subscribe函式訂閱hasOther屬性的變動,在函式內判斷當新的值為true時,設定textbox取得focus。

May 26, 2013

前端體驗之表單製作 - 使用KnockoutJS及Twitter Bootstrap (2)

訪客連結到線上表單填寫資料,可送出、暫存及列印表單。送出表單後,資料也會同步到CRM裡。多麼簡單的需求描述,可不是嗎?但這裡並不打算詳述實作上的細節,僅分享可行的實作方式及想法。

表單製作

Boostrap在這部份主要是扮演排版的角色,其提供的CSS類別可讓表單風格看起來整齊一致。由於需要提供使用者輸入生日的功能,找到了幾個師出同門的套件如Datepicker for Bootstrapbootstrap-datepickerDate/Time Picker for Twitter Boostrap,最後選用了Datepicker for Bootstrap

在表單欄位的配置上,因為有textbox、checkbox、radio button、drop-down list、button,所以必須對KnockoutJS的幾個部份有一定程度的了解,如Creating view models with observablesUsing computed observablesWorking with observable arraysThe visible bindingThe text bindingThe foreach bindingThe with bindingThe click bindingThe submit bindingThe enable bindingThe value bindingThe checked bindingThe options bindingThe data-bind syntaxThe binding context

表單送出

表單資料送出到Server端時一定會先做資料驗証。KnockoutJS本身並沒有提供驗證的功能,不過已有現成的extender可以使用 - KnockoutJS Validation,當然你也可以使用jQuery Validation Plugin,如jQuery驗証EmailDropDownList控制項的資料驗証 - 使用RequiredFieldValidator & jQuery Validation Plugin。我選擇使用KnockoutJS Validation,因為它可以優雅地與KnockoutJS整合,使用上就如同在Server端的ViewModel加上Data Annotation那樣簡單。

在送出資料時有兩種選擇,同步與非同步,我選用非同步機制。在KnockoutJS使用非同步機制送出資料,請務必參考Documentation裡的Loading and saving JSON data及Tutorials裡的Loading and saving data。至於Server端接收資料,有更多種選擇,可以是ASP.NET MVC的Action、Web Services、WCF Services、泛型處理常式或是ASP.NET Web Forms的PageMethods。我使用了PageMethods來實作,因為之前有經驗且它可自動幫我將JSON反序列化回物件。

表單暫存

由於需要輸入的欄位不少,我們希望提供使用者一個暫存的機制,以防萬一。Cookie可能因資料大小限制關係不敷使用,所以先以Session用來存放暫存至Server端的反序列化物件,這未必是個好方法,不過以目前需求來說,足以。既然有暫存,表示我們需要將它取出。由PageMethods取出Server端物件時會自動轉換為JSON物件,這時我們就可將這JSON物件更新回ViewModel,接著KnockoutJS會自動幫我們更新相對應的UI。取回的JSON物件要更新回ViewModel,除了手動處理外,也可參考The mapping plugin

表單列印

要列印表單,可以將使用者畫面切到預覽頁面並提供列印按鈕,所以你可能需要準備兩個版本的表單,一個是填寫用的表單,一個是列印用的表單,再以ViewModel搭配Knockout的visible binding做畫面上的切換,如此便不需要自行操作DOM。如果CSS能力不錯,甚至可能只要準備一個列印用的CSS類別再做畫面切換即可。在列印表單時,如果想要控制列印版面配置,可參考The @page RuleW3C的CSS規格文件

前端、後端及CRM整合

整個需求實際上是由兩個人負責實作,我負責前端與部份後端,而同事負責後端與CRM的整合。後端與CRM溝通是透過WCF Service,我們在後端與CRM中間安插了Service Agent,如此我不需要知道如何使用這支WCF Service或與CRM溝通的實作細節如何,只需提供Service Agent需要的資料即可。

前端體驗之表單製作 - 使用KnockoutJS及Twitter Bootstrap (1)

最近應產品部門需求,將一份服務申請表(PDF格式)轉換為線上表單讓使用者填寫申請。由於輸入欄位不少且是個全新的功能,試著導入一些前端框架看能不能加速開發或是提昇UX,而選用的框架為KnockoutJSTwitter Bootstrap

KnockoutJS是在一年多前研究MVVM pattern時注意到它,當時對它處理前端的方式頗為讚嘆,因為使用它時可以大大地降低自己對DOM的處理(甚至完全不用自己處理DOM),大部份時間只需要操作好ViewModel即可。但這一年多來苦無機會在實際專案使用它,直到最近才有機會。選用Twitter Bootstrap的原因則很單純,因為在一個沒有UI/UX designer的團隊裡,能有一個堪用且支援度不錯的UI框架可以使用對Developer來說是個不錯的選擇。

KnockoutJS

KnockoutJS的官方文件及範例寫得相當不錯,在開始實戰前先至Tutorials觀看教學,看看它能做些什麼。Turtorials有5個sections,先瀏覽IntroductionWorking with Lists and CollectionsLoading and saving data,看完後對KnockoutJS會有基本的認知。基本上看完後就可以準備實戰,並以官方的Documentation做為實戰時的參考,也建議有空的話可以把整個Documentation快速看過一遍,對操作KnockoutJS的能力會大大地提升也省去一些google的時間,因為Documentation介紹得蠻詳細的。接下來到Download / Install頁面下載安裝KnockoutJS,因為我是以VS 2008做為開發工具所以需手動安裝,VS2010以上則使用nuget安裝即可。

Twitter Bootstrap

Bootstrap的官方文件及範例也寫的不錯。在此需求中Bootstrap用來製作表單頁面及排版,主要使用到了Grid SystemTablesFormsButtonsIcons by GlyphiconsAlerts,其中Forms是最常參考的文件。Boostrap的安裝可參考ASP.NET MVC 4中使用Bootstrap或手動下載自行安裝

接下來我們看看如何實作這需求,前端體驗之表單製作 - 使用KnockoutJS及Twitter Boostrap (2)

May 25, 2013

Bootstrap Modal內含iframe裡的按鈕如何關閉整個Modal

Bootstrap實作modal window一文實作出一個內含iframe表單頁面的Modal後,我們希望增加另一個功能,讓使用者填寫完表單資料並送出後,顯示出一段文字並加上一個按鈕,使用者可點選此按鈕關閉Modal(當然你也可以仿照官方範例,配置一個關閉按鈕在Modal右上角)。


如果我們的Modal是以內建的showModalDialog來實作的話,很簡單,只要使用window.close()便可將Modal關閉,但在Boostrap Modal中無法以此方式關閉Modal,必須利用其API .modal('hide')來達成。但iframe裡的按鈕要怎麼與iframe外的Modal做溝通呢?利用window.parent

一個方法是在按下按鈕時直接呼叫window.parent.$('#modal').modal('hide'),如onclick="window.parent.$('#modal').modal('hide')"

另一個方法則是先在主頁面上宣告一個呼叫Modal API的function,如
window.closeModal = function() {
    $('#modal').modal('hide');
};

在iframe內的按鈕則透過window.parent呼叫closeModal這個function來關閉Modal,如onclick="window.parent.closeModal()"

May 13, 2013

使用Bootstrap實作modal window

jQuery BlockUI在window.showModalDialog失效問題一文提到了我以javascript內建的showModalDialog搭配jQuery BlockUI實作modal window。實作modal window的方式有很多種,市面上更是有很多的套件可以支援這種效果,如jQuery UI

雖然javascript內建的showModalDialog搭配jQuery BlockUI順利地實作出greyed-out modal window,Product Manager在iPad中瀏覽時卻發現modal window無法正常顯示,而且這似乎是個已知問題,所以決定改以Bootstrap來實作greyed-out modal window。

安裝Bootstrap可以透過nuget來完成,不過因為我的開發環境是Visual Studio 2008且我只需要Modal的功能,所以手動下載。

瀏覽http://twitter.github.io/bootstrap/customize.html,此頁面允許我們下載在Bootstrap中所需的功能。因為我只需要Modal,所以在Choose components中我勾選MiscellaneousClose iconJS ComponentsModals


Select jQuery plugins中則勾選Modals

接著點選Customize and Download下載客制化後的Bootstrap。解壓縮後將bootstrap.min.cssbootstrap.min.js加入專案即可,不過建議加入前將檔名修改成較為直覺的檔名如bootstrap-modal.min.css或bootstrap-modal.min.js。

有一點要注意的是,當我加入bootstrap.min.css後,網頁版面整個跑掉了,檢查了一下bootstrap.min.css原始碼,發現有幾個與Modal較無關係的class,將它們移除後版面即恢復正常(僅留下.modal開頭的class)。


在Bootstrap Modal中,資料都是顯示在<div class="modal-body"></div>中,因為要顯示的資料是一份aspx且內含使用者需要輸入的表單資料,所以我以jQuery在載入Bootstrap Modal時動態載入一個內含此aspx的iframe。

$('.modal-body').html('<iframe width="100%" height="520px" frameborder="0" scrolling="no" allowtransparency="true" src="OnlineEnquiry.aspx"></iframe>');
$('#modal').modal({ backdrop: 'static' });

backdrop屬性設定為static是為了讓瀏覽者強制回應greyed-out modal window。到這個步驟,Bootstrap Modal已可成功地顯示表單頁面。

補充 (May 16, 2013)
在iPad上瀏覽Bootstrap Modal發現,iframe內的表單內容太長時,無法使用捲軸將Modal內的頁面往下拉,參考Modal overflow does not scroll on iOS,在Bootstrap樣式表的modal-body類別中加入-webkit-overflow-scrolling: touch;即可解決此問題。

May 12, 2013

jQuery BlockUI在window.showModalDialog失效問題

最近在幫忙公司調整官網UI,product manager希望能有個來信詢問的功能,讓瀏灠者按下按鈕後出現一個modal window並填寫相關資料。想有個modal window很簡單,用javascript內建的showModalDialog很快就可以辦的到。
window.showModalDialog('OnlineEnquiry.aspx', '', 'dialogHeight=525px;dialogWidth=450px;scroll=no');
demo後,product manager又希望在modal window開啟時可以將背景grey out。這部份也不是個難題,可以交由jquery BlockUI plugin來完成。
$.blockUI({ message: '' });
window.showModalDialog('OnlineEnquiry.aspx', '', 'dialogHeight=525px;dialogWidth=450px;scroll=no');
$.unblockUI();

測試一下。結果在按下按鈕後BlockUI並沒有出現,反而是在關閉modal window後BlockUI才淡入又隨即淡出。檢查後發現原來是BlockUI淡入的速度跑的比modal window慢,所以在按下按鈕後,modal window先開啟了,隨即讓畫面需強制回應,關掉後才繼續淡入的效果,接著再淡出。

由於我不需要淡入的效果,透過屬性fadeIn設為0將淡入的效果關閉。
$.blockUI({ message: '', fadeIn: 0 });
window.showModalDialog('OnlineEnquiry.aspx', '', 'dialogHeight=525px;dialogWidth=450px;scroll=no');
$.unblockUI();
再次開啟modal window可以看到背景已先grey out。

May 2, 2013

Legit Log Viewer無法讀取中文字

用Legit Log Viewer開啟含有中文字的紀錄檔時,發現中文字變成亂碼。


原本以為是因為NLog設定檔沒有指定encoding的關係,所以加上了encoding="utf-8"在target中。


設定完再產出紀錄檔,這次先用Notepad++開啟查看。很好,亂碼沒了。


再用Legit Log Viewer開啟紀錄檔,卻仍然是亂碼。猜測可能是與BOM有關。用Notepad++看了一下紀錄檔的格式是Encode in UTF-8 without BOM,改成Encode in UTF-8並存檔。


再以Legit Log Viewer開啟紀錄檔,Bang!中文正常顯示。


查了一下發現這似乎是個已知的問題,NLog寫檔時並未將BOM寫入造成Legit Log Viewer讀取UTF-8字元時出現上述的亂碼,可以透過修改NLog原始碼的方式解決這個問題。不過話說回來要是遇到不檢查BOM的讀取程式不也是一樣出現錯誤?