Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

【笨問題】無法輸入英文小寫字母

$
0
0

開了一份新PowerPoint範本,發現標題打不出英文小寫。

第一直覺是鍵盤卡住或輸入法故障,但狂按Shift、Caps Lock、切換輸入法都無法解決。接著換到IE地址列測試,大小寫切換是正常的,判定為PowerPoint設定問題。

最後找到原因,原來字型設定區有個【全部大寫】項目,這個範本將標題字型的全部大寫設為啟用:

又學到一招。


HTML轉PDF - 使用Pechkin套件

$
0
0

剛好跟人討論到HTML轉PDF需求,便對工具進行簡單評估以備不時之需。

網路上比較多人推的是WkHtmlToPdf,如果是用.NET開發,已經有人包成NuGet套件,直接搜尋pechkin就可找到,它有兩個版本: Pechkin適用單執行緒,如要非同步執行請選用Pechkin.Synchronized。

安裝NuGet套件後,相關Unmanage DLL也會一併下載並加入專案,不用額外安裝HkHtmlToPdf就可開始寫程式,十分方便。但由於Unmanaged部分為32位元,記得要將專案目標平台切成x86。

參考Pechkin作者在GitHub的FAQ,我寫了一個簡單範例,分別將Google新聞首頁及自己產生的HTML轉成PDF:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Pechkin;
 
namespace Html2Pdf
{
class Program
    {
staticvoid Main(string[] args)
        {
            var config = new GlobalConfig();
            var pechkin = new SimplePechkin(config);
            ObjectConfig oc = new ObjectConfig();
            oc.SetPrintBackground(true)
                .SetLoadImages(true)
                .SetPageUri("http://news.google.com.tw/");
byte[] pdf = pechkin.Convert(oc);
            File.WriteAllBytes("d:\\temp\\google-news.pdf", pdf);
 
string html = @"
<html><head><style>
body { background: #ccc; }
div { 
width: 200px; height: 100px; border: 1px solid red; border-radius: 5px;
margin: 20px; padding: 4px; text-align: center;
}
</style></head>
<body>
<div>Jeffrey</div>
<script>document.write('<span>Generated by JavaScript</span>');</script>
</body></html>
";
            pdf = pechkin.Convert(oc, html);
            File.WriteAllBytes("d:\\temp\\myhtml.pdf", pdf);
        }
 
    }
}

實測效果挺不錯,跟實際網站所看到的很接近: (但不完全相同)

在自製HTML測試中,我使用<script>docuemt.write(…)</script>在網頁中插入內容,在PDF中也正確顯示。

但在某些狀況下,產生的PDF會與實際網頁差異頗大(例如: 我的Blog首頁轉PDF後排版就亂了),有可能是網頁的某些Style寫法或複雜JavaScript超出WkHtmlToPdf預期,也有可能是元件的Bug,感覺眉角還很多,留待實際遇到時再深究。

KO範例24 – Kendo輸入欄位唯讀切換

$
0
0

knockout.js內建enable繫結,可透過ViewModel的布林值切換<input>、<textarea>及<select>啟用與否產生唯讀效果。工作專案中有些輸入欄位採用的Knockout-Kendo所繫結的Kendo DatePicker及NumericTextBox就不在enable支援之列。為此,我寫了一個自訂繫結kendoEnable,支援DatePciker、NumericTextBox(視需要可再擴充),也能繫結特定屬性切換唯讀:

//由特定值的true/false決定啟用或停用Kendo DatePicker及NumericTextBox
    ko.bindingHandlers["kendoEnable"] = {
        update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var $inp = $(element);
var kendoObject = $inp.data("kendoDatePicker") || $inp.data("kendoNumericTextBox");
var val = ko.utils.unwrapObservable(valueAccessor());
if (val) {
                kendoObject.enable();
                $inp.removeClass("a-readonly");
            }
else {
                kendoObject.readonly();
                $inp.addClass("a-readonly");
            }
        }
    };

操作示範如下:

線上展示(JS Bin)

[KO系列]

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

同時開啟兩個Excel檔案於多螢幕並列顯示

$
0
0

Excel有個討厭特性,開啟多份Excel檔案時不像Word每個文件一個視窗,而是預設開在同一視窗,每次只能顯示其中一個檔案,如果要左右並陳對照,可以取消個檔案的最大化,在同一個視窗中並列,只是這麼做不能善用多螢幕的優勢,雖然可以將視窗拉大橫跨兩個螢幕,但有螢幕解析不同,以及無法使用快捷鍵調整視窗尺寸位置的問題,用起來不甚順手,還是覺得像Word一樣各自成為獨立視窗操作比較方便。

平日上班使用Excel 2010,有個解決方案不要直接點選Excel澢,而由程式集的Exce捷徑l開啟新l視窗,點選兩次就會產生兩個獨立的Excel視窗,再透過各自的【檔案/開啟舊檔】選取不同Excel檔開啟,就能實現同時開啟兩個Excel檔並列顯示。(但會有些限制,例如: 工作表不能在兩個Excel檔間複製或搬移)

先啟動Excel再開啟Excel檔有點繁瑣,網路上有人找到另一種解法: 修改或增加Excel檔Shell指令,允許Excel檔在新的Excel視窗開啟,操作起來如下圖:

要增加Shell指令,需修改Registry。將以下的內容存成.reg,再點選執行即可為Excel檔新增"在新視窗開啟"選項。注意: 實際EXCEL.EXE安裝路徑可能有所差異,請自行依實際位置修改調整。

Windows Registry Editor Version 5.00
 
[HKEY_CLASSES_ROOT\Excel.Sheet.8\shell\Open_in_New_Excel_Instance]
@="在新視窗開啟(&W)"
 
[HKEY_CLASSES_ROOT\Excel.Sheet.8\shell\Open_in_New_Excel_Instance\command]
@="\"C:\\Program Files (x86)\\Microsoft Office\\Office14\\EXCEL.EXE\" \"%1\""
 
[HKEY_CLASSES_ROOT\Excel.Sheet.12\shell\Open_in_New_Excel_Instance]
@="在新視窗開啟(&W)"
 
[HKEY_CLASSES_ROOT\Excel.Sheet.12\shell\Open_in_New_Excel_Instance\command]
@="\"C:\\Program Files (x86)\\Microsoft Office\\Office14\\EXCEL.EXE\" \"%1\""

以上範例中,Excel.Sheet.8為xls檔、Excel.Sheet.12為xlsx檔,如要為xlsm等格式也加上指令,可自行比照設定,但實務上設定xls及xlsx應該就夠用。

PS: 終於,Excel 2013起改為一個檔案一個視窗(SDI模式),要在多螢幕下兩個檔案並列對照方便多了。

Kendo UI內建圖示清單

$
0
0

Kendo UI內建了一組圖示,若網頁已參照kendo.common.min.css以及kendo.default.min.css(或其他主題),只需加入SPAN元素並指定k-icon及k-i-*樣式,就可在網頁插入Kendo UI圖示,例如:
<span class="k-icon k-i-calendar"></span>

沒有在Kendo UI網站找到像Bootstrap一樣的圖示清單,便決定自己做一張,有需要的朋友請自行參考:

這裡有JSBin線上版(不同主題配合的圖示不同,可修改kendo.*.min.css檢視結果),以下則是產生清單HTML的小程式:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
 
namespace ShowKendoIcons
{
class Program
    {
staticvoid Main(string[] args)
        {
string css = File.ReadAllText("d:\\kendo.common.min.css");
            List<string> list = new List<string>();
foreach (Match m in Regex.Matches(css, "[.]k-i-[a-z-]+"))
            {
string c = m.Value;
if (list.Contains(c)) continue;
                list.Add(c);
            }
            StringBuilder sb = new StringBuilder();
foreach (string s in list.OrderBy(o => o))
            {
                sb.AppendFormat(
"<div><span class='t'>{0}</span><span class='i k-icon {1}'></span></div>\n",
                    s, s.TrimStart('.'));
            }
            File.WriteAllText("d:\\kendo.html", sb.ToString());
        }
    }
}

KO範例25 – 狀態切換鈕

$
0
0

專案遇到的小需求,如上圖所示,某UI有四種操作模式,規格要求提供四個按鈕,用來切換模式。當模式啟用時,該按鈕要呈現反白,方便使用者掌握模式狀態。

這個需求用Knockout不難實作,利用click繫結將四顆按鈕的點擊事件都對應到setMode函數,由於四鈕共用一個函數,我們需要由event.target識別按鈕來源,模式值被藏在data-mode Attribute。切換背景色的工作則交給css繫結,比對mode()是否與該按鈕對應模式相符,決定要不要加上樣式"on"。搞定收工~

<!DOCTYPEhtml>
<html>
<head>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例25 - 狀態切換鈕</title>
<style>
    .on { background-color: blue; color: white; }
</style>
</head>
<body>
<div>
<input type="button" value="依編號" data-mode="N"
    data-bind="css: { on: mode()=='N' }, click: chgMode" />
<input type="button" value="依名稱" data-mode="M"
    data-bind="css: { on: mode()=='M' }, click: chgMode" />
<input type="button" value="依類別" data-mode="T"
    data-bind="css: { on: mode()=='T' }, click: chgMode" />
<input type="button" value="依廠牌" data-mode="B"
    data-bind="css: { on: mode()=='B' }, click: chgMode" />
</div>
<div>
    Mode = <span data-bind="text: mode"></span>
</div>
<script>
function myViewModel() {
var self = this;
      self.mode = ko.observable("N");
      self.chgMode = function(data, event) {
//可由event.target取得觸發事件的來源元素
        self.mode(event.target.getAttribute("data-mode"));
      };
    }
    ko.applyBindings(new myViewModel());
</script>
</body>
</html>

線上展示

[KO系列]

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

KO範例26 - ko.computed()的效能考量

$
0
0

ko.computed()能追蹤所依賴的ko.observable()或ko.observableArray(),在其變化時自動重算,開發時依直覺寫出關連邏輯,屬性間便會依預期變化。使用起來固然方便,但是當依賴對象連續變化時,要留意反覆重算的必要性以及對效能的衝擊。用一個範例來說明:

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例26 - 利用throttle改善computed效率(改善前)</title>
</head>
<body>
<div>
    Max: <span data-bind="text: max"></span>
    Min: <span data-bind="text: min"></span>
    Sum: <span data-bind="text: sum"></span>
    Avg: <span data-bind="text: avg"></span>
</div>
<script>
    function myViewModel() {
      var self = this;
      self.items = ko.observableArray();
      self.max = ko.computed(function() {
        var ary = self.items();
        var max = null;
for (var i = 0; i < ary.length; i++) {
if (max == null || ary[i] > max) max = ary[i];
        }
return max;
      });
      self.min = ko.computed(function() {
        var ary = self.items();
        var min = null;
for (var i = 0; i < ary.length; i++) {
if (min == null || ary[i] < min) min = ary[i];
        }
return min;
      });
      self.sum = ko.computed(function() {
        var ary = self.items();
        var sum = 0;
for (var i = 0; i < ary.length; i++) {
          sum += ary[i];
        }
return sum
      });
      self.avg = ko.computed(function() {
        var ary = self.items();
if (ary.length == 0) return 0;
return parseFloat(self.sum()) / ary.length;
      });
    }
    var vm = new myViewModel();
    ko.applyBindings(vm);
for (var i = 1; i < 10000; i++) {
      vm.items.push(i);
    }
</script>
</body>
</html>

在以上範例中,ViewMode用了一個observableArray存放數字陣列,另外有四個屬性: max, min, sum, avg分別用以統計該數字陣列的最大值、最小值、總和及平均值,最直覺的寫法是四個屬性用ko.computed各寫各的,跑迴圈取出陣列的每一個數字處理。但可以預期,只要陣列每次加入新元素,就會觸發max, min, sum各跑一次迴圈(avg直接由sum值除以陣列長度計算,不必跑迴圈)。當我們連續在陣列加入從1到9999,共9999個數字,猜猜ko.computed要執行幾次? 理論上會觸發9999次,再上三組computed跑迴圈,第一次跑1圈,第2次2圈,第3次3圈,...,第9999次跑9999圈,CPU耗用量不少,使用IE10實測,耗時3.814秒。

先撇開Knockout特性不談,程式邏輯面存在一些無效率,先對三組computed跑迴圈的部分開刀,其實只需要跑一次迴圈,就可以一次算出max, min, sum及avg,故可以將max、min、avg都改為純ko.observable(),只留下sum為ko.computed(),加總同時一併求出最大值、最小值及平均,再分別寫入max、min及avg。

function myViewModel() {
var self = this;
      self.items = ko.observableArray();
      self.max = ko.observable();
      self.min = ko.observable();
      self.avg = ko.observable();
      self.sum = ko.computed(function() {
var ary = self.items();
var sum = 0;
var max = null;
var min = null;
for (var i = 0; i < ary.length; i++) {
if (min == null || ary[i] < min) min = ary[i];
if (max == null || ary[i] > max) max = ary[i];
          sum += ary[i];
        }
        self.max(max);
        self.min(min);
        self.avg(ary.length == 0 ? 0 : parseFloat(self.sum()) / ary.length);
return sum;
      });
    }
var vm = new myViewModel();
    ko.applyBindings(vm);
for (var i = 1; i < 10000; i++) {
      vm.items.push(i);
    }

改良後,發現原本會執行39,892次的evaluatePossibleAsync減少到9,955次,而耗時也由3.8秒降低到2.351秒。

還有改善空間嗎? 當然有! 我們關心的是最大值、最小值、加總、平均的最後計算結果,在將1-9999塞入陣列期間的變化過程一點也不重要,因此並不需要每塞一個數字就重新計算一次,等資料全部就緒再計算就好。先前在AJAX範例介紹過的throttle擴充方法是解決這個問題的好方法,只需在computed後方套上throttle,則observableArray的元素有變化時,不會立刻重算,會等待一小段時間,確認資料不再變化後才進行重算,如此便可抑制連續新增元素期間多餘的重算動作,有效改善效能。

function myViewModel() {
var self = this;
      self.items = ko.observableArray();
      self.max = ko.observable();
      self.min = ko.observable();
      self.avg = ko.observable();
      self.sum = ko.computed(function() {
var ary = self.items();
var sum = 0;
var max = null;
var min = null;
for (var i = 0; i < ary.length; i++) {
if (min == null || ary[i] < min) min = ary[i];
if (max == null || ary[i] > max) max = ary[i];
          sum += ary[i];
        }
        self.max(max);
        self.min(min);
        self.avg(ary.length == 0 ? 0 : parseFloat(self.sum()) / ary.length);
return sum;
      }).extend({ throttle: 200 });
    }
var vm = new myViewModel();
    ko.applyBindings(vm);
for (var i = 1; i < 10000; i++) {
      vm.items.push(i);
    }

最終改良版果然沒讓我們失望,耗時由2.351秒一舉縮短到136ms,而樹狀節點中的setTimeout、clearTimeout,便是throttle透過延遲執行改善效能的痕跡。

【結論】在設計ko.computed()時,記得評估其被呼叫次數與時機,避免短時間反覆大量執行,必要時可使用throttle擴充方法改善,才不會寫出吃光CPU的怪獸網頁。

[KO系列]

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

【CSS新手筆記】小探position: abolute應用

$
0
0

寫了好幾年網頁,卻一直是CSS新手,最近一年才比較認真研究個中奧妙,也慢慢解開一些原本搞不定的小眉角,說穿了不複雜,但相信我肯定不是地球最後一個發現的網頁攻城獅,故筆記分享,如高手發現有誤或另有妙法,請不吝指正。

我對於position: absolute的認識是很粗淺的,印象還停留在ASP.NET 1.1時代WebForm的Grid Layout Mode,啟用後網頁的控制項會變成像Window Form一樣採絕對座標,可拖拉擺放到任意位置。當時知道背後是透過position: absolute再加上top、left等CSS設定完成的,近十年下來一直以為所謂的position: absolute就是"以整個網頁為座標基準擺放元素"。

直到最近,我才學習到: position: absolute的定址基準,不一定是整個網頁,也可以相對於父元素;另外,定址依據並非只有top及left,還有right及bottom,如以下的例子:

<!DOCTYPEhtml>
<html>
<head>
<metacharset=utf-8/>
<title>position absolute的應用</title>
<style>
  #out,#in { border: 2px solid gray; }
  #out {
      margin: 100px;
      background-color: #ddd;
      width: 200px;
      height: 200px;
      position: relative;
  }
  #in {
      background-color: red;
      width: 50px;
      height: 50px;
      position: absolute;
      right: 10px; bottom: 10px;
  }    
</style>
</head>
<body>
<divid="out">
<divid="in"></div>
</div>
</body>
</html>

內部的紅色<div>被設為position: absolute、right: 10px、bottom: 10px,便可將其定位在灰色容器<div>右下角。線上展示

但其中有點要注意,灰方塊必須設定成position: relative,紅方塊才會以它計算座標,要是拿掉relative設定,紅方塊會變成貼在整張網頁的右下角。線上展示

position: absolute的文件,說明了此一行為的依據:

Absolute positioning

Elements that are positioned relatively are still considered to be in the normal flow of elements in the document. In contrast, an element that is positioned absolutely is taken out of the flow and thus takes up no space when placing other elements. The absolutely positioned element is positioned relative to nearest positioned ancestor. If a positioned ancestor doesn't exist, the initial container is used.

關鍵在其容器或其再上層的容器元素是否為"positioned ancestor",如果不是,便會以"initial container"(<body>,也就是整張網頁)為準。而positioned元素的定義為: an element whose computed position property is relative, absolute, or fixed. 這就是灰方塊設定relative才能成為紅方塊定位基準的理由。

而以上特性,還可做出單靠CSS實現隨容器尺寸自動放大縮小的效果! 紅方格不要指定width、height,透過position: absolute並指定top/left/right/bottom,就能產生類似Window Form Docking+Margin的結果: 線上展示

雖然自己都覺得Lag嚴重,但現在學會總比還不知道好,哈!


KO範例27 - ViewModel物件的擴充

$
0
0

為了確保Server端及Client端的ViewModel一致,在專案中我使用T4自動產生對應C# Class及JavaScript function,如此可確保ASP.NET端拋出的ViewModel與Client端的ViewModel完全一致,雖然這點Knockout Mapping Plug-In也辦得到,但自己產生的JavaScript ViewModel還可以加上JavaScript Documentation註解,配合Visual Studio強大的JS Intellisense功能,爽度是無法相比滴~

但有個小問題: 除了自動產生的屬性外,常會因開發需要還得額外加入UI控制用途的ko.observable或ko.computed,這部分在開發過程常會機動修改調整,不適合綁進自動產生程序,事後外加是較好抉擇。一開始我想得天真,以為用vm.prototype.anotherProp就可輕鬆搞定,後來卻發現這招處理ko.observable可行,遇到ko.computed時會因無法存取當下Instance而陷入困境。在ko.computed函數中,我們無法透過this取得當時物件,而宣告prototype時物件個體尚不存在,故也不可能當成ko.computed的第二個參數傳入,結果空有ko.computed卻無法依賴其他屬性進行運算。

<!DOCTYPEhtml>
<html>
<head>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例27 - 擴充ViewModel(失敗)</title>
</head>
<body>
<input data-bind="value: foo" />
<div>fooPlusX = <span data-bind="text: fooPlusX"></span></div>
<script>
//透過CodeGen自動產生的ViewModel
function VMBoo() {
var self = this;
      self.foo = ko.observable(1);
    }
//事後想為ViewModel多加一個fooPlusX屬性,
//天真地想透過prototype加掛ko.computed直接搞定
    VMBoo.prototype.fooPlusX = ko.computed(function() {
try {
//問題來了,在ko.computed中無從存取當時的instance
returnthis.foo() + "X";
      }
catch (err) {
return err.message;
      }
    }); //instnace在此時當不存在,也無法當成參數傳給ko.computed
var vm = new VMBoo();
    ko.applyBindings(vm);
</script>
</body>
</html>

在以上的失敗範例中,只會得到fooPlusX = Object [object global] has no method 'foo'的結果。線上展示

最後我找到一個解法,先在ViewModel的建構函式中埋下伏筆:
if (this.init) this.init(self);

而這個init函式可以透過vm.prototype.init = function(self) { … }加以定義,如此在init便能取得self(當時的物件個體)為所欲為,跟在建構式的寫法一致。線上展示

<!DOCTYPEhtml>
<html>
<head>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例27 - 擴充ViewModel(成功)</title>
</head>
<body>
<input data-bind="value: foo" />
<div>fooPlusX = <span data-bind="text: fooPlusX"></span></div>
<script>
//透過CodeGen自動產生的ViewModel
function VMBoo() {
var self = this;
      self.foo = ko.observable(1);
//CodeGen時額外呼叫透過prototype定義的init
if (this.init) this.init(self);
    }
//宣告額外的init函式
    VMBoo.prototype.init = function(self) {
      self.fooPlusX = ko.computed(function() {
return self.foo() + "X";
      });
    };
var vm = new VMBoo();
    ko.applyBindings(vm);
</script>
</body>
</html>

就醬,又排除掉偉大航道上的一個小障礙囉~

[KO系列]

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

【CSS新手筆記】左欄固定,右欄佔滿剩餘空間

$
0
0

寫了好幾年網頁,卻一直是CSS新手,最近一年才比較認真研究個中奧妙,也慢慢解開一些原本搞不定的小眉角,說穿了不複雜,但相信我肯定不是地球最後一個發現的網頁攻城獅,故筆記分享,如高手發現有誤或另有妙法,請不吝指正。

有個常遇到的需求,我卻從沒弄懂過: 分成多個直欄,左方直欄寬度固定,最右欄佔滿剩餘寬度。直到後來學會了利用float排列,才慢慢找到解法:

以上解法是將左側的兩個<div>設成float: left,使其向左貼齊排列,而最後一個<div>要使用overflow: hidden的密技,讓它變成一個BFC(Block Formatting Context),BFC會自成一個區塊,不允許其中的浮動元素跨出去,也不准相臨的浮動元素跨進來,於是最後一個div變成一般區塊,吃下前兩個float div以外的空間。(延伸閱讀: Expand div to take remaining widthThe magic of “overflow: hidden” )

完整程式碼如下:

<!DOCTYPEhtml>
<html>
<head>
<metacharset=utf-8/>
<title>佔滿剩餘寬度(float法)</title>
<style>
    html,body { height: 100%; }
    .container 
    {
      height: 80%;
      width: 80%;
      padding: 0px;
    }
    .container>div
    { 
      background-color: white; 
      border: 1px solid purple;
      padding: 6px;
      height: 100%;
    }
    .col-left { 
      float: left; 
      width: 100px; 
    }
    .col-remaining { 
      /* 利用overflow: hidden使其成為BFC */
      overflow: hidden;
    }
</style>
</head>
<body>
<divclass='container'>
<divclass='col-left'>
        LEFT 100px Wide
</div>
<divclass='col-left'>
        LEFT 100px Wide
</div>
<divclass='col-remaining'>
        REMAINING
</div>
</div>
</body>
</html>

除了利用float,還有第二種做法,是利用position: absolute,後兩個div算好前面元素佔用的空間,調整left值使其接在後面顯示,只是這種做法需要預先掌握左側元素的寬度(記得要加上margin、padding),運用起來不若float法便利,但也是種解決方案。完整範例如下: 線上展示

<!DOCTYPEhtml>
<html>
<head>
<metacharset=utf-8/>
<title>佔滿剩餘寬度(absolute法)</title>
<style>
    html,body { height: 100%; }
    .container 
    {
      position: absolute;
      height: 80%;
      width: 80%;
      padding: 0px;
    }
    .container>div
    { 
      /* 絕對定位,指定top/bottom為0可使其佔滿容器高度 */
      position: absolute;
      top: 0px; bottom: 0px;
      background-color: white; 
      border: 1px solid purple;
      padding: 6px;
    }
    .col-one,.col-two { 
      width: 100px; 
    }
    .col-two { 
      left: 112px; /* 寬度100還需加上左右padding 6*2 */
    }
    .col-remaining { 
      left: 224px;
      /* 指定right使其向右擴展到容器右側 */
      right: 0px;
    }
</style>
</head>
<body>
<divclass='container'>
<divclass='col-one'>
        LEFT 100px Wide
</div>
<divclass='col-two'>
        LEFT 100px Wide
</div>
<divclass='col-remaining'>
        REMAINING
</div>
</div>
</body>
</html>

CODE-為Self-Hosting ASP.NET Web API加上Log功能

$
0
0

自從學會Self-Hosting ASP.NET Web API,遇到Console Application/Window Form/WPF程式需提供API介面的場合,我都二話不說召喚它出來打怪,開開心心地在EXE裡寫Web,好不快意~

最近有個情境,需要為Web API保留存取Log記錄,查了一下,HttpSelfHostServer似乎沒提供內建的Logging機制。沒想太多,決定利用MessageHandler配合NLog自己寫一個。

LogHandler只需繼承DelegatingHandler實作SendAsync(),攔截Request及Reponse輸出到NLog,程式碼不多:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.ServiceModel.Channels;
using System.Text;
using NLog;
 
namespace MySelfHostWebApi
{
publicclass LogHandler : DelegatingHandler
    {
privatestatic Logger logger = NLog.LogManager.GetCurrentClassLogger();
 
//REF: http://blog.kkbruce.net/2012/05/aspnet-web-api-8-http-http-message.html
protectedoverride System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, 
            System.Threading.CancellationToken cancellationToken)
        {
//取得Method、IP、Url等等,稍後寫入NLog
string method = request.Method.ToString();
//REF: http://bit.ly/16lpGKM
//在Self-Hosted模式,透過RemoteEndpointMessageProperty取得IP
string ip = "Unknown";
if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
                ip = ((RemoteEndpointMessageProperty)
                    request.Properties[RemoteEndpointMessageProperty.Name]).Address;
string url = request.RequestUri.ToString();
returnbase.SendAsync(request, cancellationToken)
                .ContinueWith((task) =>
                {
//Response回傳時,連同StatusCode一起寫入NLog
                    HttpResponseMessage resp = task.Result as HttpResponseMessage;
                    logger.Log(LogLevel.Trace, string.Format("{0} {1} {2} {3}", 
                        ip, method, url, (int)resp.StatusCode));
return resp;
                });
        }
    }
}

使用方法很簡單,將LogHandler加進HttpSelfHostConfiguration.MessageHandlers,再設妥NLog.config,就大功告成囉!

//設定路由
            config.Routes.MapHttpRoute("API", 
"{id}",
new { 
                     controller = "download",
                     action = "index"
                 });
//加上LogHandler,記錄存取Log
            config.MessageHandlers.Add(new LogHandler());
 
            httpServer = new HttpSelfHostServer(config);

記錄的Log檔範例如下:

2013-08-01 05:14:46.4144 192.168.1.5 GET httq://192.168.1.1:4567/test.jpg 404
2013-08-01 05:18:15.9233 192.168.1.18 GET httq://192.168.1.1:4567/66fd.png 200

【延伸閱讀】

使用自訂確認對話框取代window.confirm

$
0
0

專案規格有一條機車要求: 對於刪除或覆寫資料前的確認程序,希望以自訂風格的確認對話框取代簡陋的window.confirm()。

舉例來說,按鈕後原本要透過window.confirm()請使用者確認後再執行,現在要改用自訂HTML元素呈現確認文字、按鈕進行確認,就如以下改用Kendo UI Window實作確認對話框的效果:

用HTML打造自訂對話框並在適當時機顯示是小事一椿,較有挑戰性的部分是原本window.confirm()執行為同步式,程式碼會停住等使用者回應再繼續往下走。想依confirm()結果決定不同動作只需寫成:

if (window.confirm("確定嗎?")) {
    //…使用者回答【是】時的動作
} else {
    //…使用者回答【否】時的動作
}

但要在JavaScript做到"卡住流程直到特定條件再繼續"並非易事,曾看過一種做法是跑while無窮迴圈並於特定時機跳出,但因JavaScript不像C#有Thread.Sleep可用,無窮迴圈會莫名吃光CPU很不環保。也有人想出藉由同步式XHR到Server端存取虛設延遲網頁模擬Thread.Sleep的招術,但靠著無謂網路傳輸來節省CPU,還徒增Server負擔,想來也不怎麼高明。最後,還是決定用jQuery的Deferred來處理非同步。

原理是先宣告Deferred物件,當呼叫確認對話框時,傳回Deferred.promise()給呼叫端,呼叫端可透過.done()指定使用者按【是】時要執行的動作、在.fail()指定使用者按【否】時要執行的動作。如此,再依使用者按鈕決定呼叫Deferred.resolve()或Deferred.reject(),就能控制該觸發done()還是fail(),達到依操作結果決定不同執行動作的效果。先不管華麗的UI元素,以下是示範用Deferred依按鈕結果決定動作的簡單範例: 線上展示

<!DOCTYPEhtml>
<html>
<head>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<meta charset=utf-8 />
<title>使用Deferred建立自訂確認對話框</title>
<script>
function myConfirm(msg) {
var df = $.Deferred(); //建立Deferred物件
var $div = $("<div id='C'></div>");
//由樣版複製建立一次性div元素
      $div.html($(".dialog").html())
//加上按鈕事件
      .on("click", "input", function() {
        $div.remove(); //將對話框移除
if (this.value == "Yes") 
          df.resolve(); //使用者按下Yes
else
          df.reject(); //使用者按下No
      })
      .find(".m").text(msg); //設定顯示訊息
//將div加入網頁
      $div.appendTo("body");
return df.promise();
    }
    $(function() {
      $("#btnTest").click(function() {
        myConfirm("Are you sure?")
        .done(function() { //按下Yes時
          alert("You are sure");
        })
        .fail(function() { //按下No時
          alert("You are not sure");
        });
      });
    });
</script>
</head>
<body>
<inputtype='button'value='Test'id='btnTest'/>
<divclass='dialog'style='display:none'>
<divstyle='border: 1px solid blue; padding: 12px;'>
<spanclass='m'></span>
<inputtype='button'value='Yes'/>
<inputtype='button'value='No'/>
</div>
</div>
</body>
</html>

其操作結果如下:

最後,運用同樣原理再招喚Kendo UI的Window套件上場,就能實現一開始展示的華麗版確認對話框囉~ 完整程式碼如下: 線上展示

<!DOCTYPEhtml>
<html>
<head>
<title>使用Deferred建立自訂確認對話框(Kendo UI版)</title>
<linkhref="http://cdn.kendostatic.com/2013.2.716/styles/kendo.common.min.css"
rel="stylesheet"type="text/css"/>
<linkhref="http://cdn.kendostatic.com/2013.2.716/styles/kendo.default.min.css"
rel="stylesheet"type="text/css"/>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js"></script>
<script src="http://cdn.kendostatic.com/2013.2.716/js/kendo.web.min.js"></script>
<meta charset=utf-8 />
<style>
    .cnfrm-msg { color: red; padding: 12px; font-size: 12pt; }
    .cnfrm-yes,.cnfrm-no { }
</style>
<script>
//參考: http://jsfiddle.net/gyoshev/HRcKK/
    (function($) {
var h = [];
      h.push("<div class='cnfrm-block'>");
      h.push("<div class='cnfrm-msg'></div>");
      h.push("<input type='button' class='cnfrm-yes' />");
      h.push("<input type='button' class='cnfrm-no' />");
      h.push("</div>");
var html = h.join("");
      $.kendoConfirm = function(title, msg, yesText, noText) {
var $div = $(html);
        $div.find(".cnfrm-msg").text(msg);
        $div.find(".cnfrm-yes").val(yesText || "Yes");
        $div.find(".cnfrm-no").val(noText || "No");
var win = $div.kendoWindow({
          title: title || "Confirmation",
          resizable: false,
          modal: true,
          deactivate: function() {
this.destroy(); //remove itself after close
          }
        }).data("kendoWindow");
        win.center().open();
var dfd = $.Deferred();
        $div.find(":button").click(function() {
          win.close();
if (this.className == "cnfrm-yes")
            dfd.resolve();
else
            dfd.reject();          
        });
return dfd.promise();
      };
    })(jQuery);
    $(function() {
      $("#btnTest").click(function() {
var dfd = 
        $.kendoConfirm(
"Please confirm...",
"Are you sure to delete it?",
"Yeeees", "No no no");
        dfd.done(function() { //按下Yes時
          alert("You are sure");
        })
        .fail(function() { //按下No時
          alert("You are not sure");
        });
      });
    });
</script>
</head>
<body>
<inputtype='button'value='Test'id='btnTest'/>
<divclass='dialog'style='display:none'>
<divstyle='border: 1px solid blue; padding: 12px;'>
<spanclass='m'></span>
<inputtype='button'value='Yes'/>
<inputtype='button'value='No'/>
</div>
</div>
</body>
</html>

Kendo UI AutoComplete一次搜尋多欄

$
0
0

專案用到Kendo UI的自動完成,提示資料的物件陣列存在Client端,資料有多個欄位,希望做到任一個欄位包含關鍵字就顯示,AutoComplete有個filter參數,設定為"contains"就可以做到"包含"就算數,但搜尋對象參數dataTextField卻只能指定單一欄位,無法一次查詢多欄。

有個解決辦法是自行在物件上將多個欄位另外併成一欄,例如以下的codePage物件,textForSearch屬性等於代碼加空白加名稱:

var $code = $("#code");
function codePage(code, name) {
this.codePage= code;
this.name = name;
this.textForSearch = code + " " + name;
      }
var data = [
new codePage("869", "Greek"),
new codePage("932", "Japanese"),
new codePage("936", "Simplified Chinese"),
new codePage("949", "Korea"),
new codePage("950", "Traditional Chinese")
      ];
      $code.kendoAutoComplete({
        dataTextField: "textForSearch",
        dataValueField: "codePage", 
        dataSource: data,
        filter: "contains",
        select: function (e) {
//get index of <LI>
var idx = $.inArray(e.item[0], e.sender.items());
var data = e.sender.dataItem(idx);
//set name
            $("#name").text(data.name);
        }
      });

如此,將dataTextField設為textForSearch就能同時搜尋CodePage代碼及名稱:

但有個問題,雖然欄位輸入CodePage代碼或名稱都能搜尋,但我們希望自動完成欄位固定只輸入代碼,名稱則由另一個<SPAN>顯示。當AutoComplete的dataTextField設為textForSearch,點選提示項目後欄位填入的也會是textForSearch的值,會同時有代碼及名稱,與期望不符。

要解決這個問題,有個不錯的方法是透過dataValueField參數指定要填入<INPUT>的屬性名稱。要增加dataValueField功能得修改Kendo UI原始碼,不過修改3rd Party程式庫會增加版控複雜性,追完原始碼,我找到事後置換kendoAutoComplete物件的_select函式的玩法,可不修改原始碼JS就加入新功能,於是用以下的"Patch"函式,傳入data("kendoAutoComplete")取得的物件,將.select換成修改版(其實只改了一行),完成Hacking:

var SELECTED = "k-state-selected", List = kendo.ui.List;
function patchKendoAutoComp(autoCompObject) {
//add optionValueField support to 2013.2.716 version 
        autoCompObject._select = function (li) {
var that = this,
                separator = that.options.separator,
                data = that._data(),
                text,
                idx;
 
            li = $(li);
 
if (li[0] && !li.hasClass(SELECTED)) {
                idx = List.inArray(li[0], that.ul[0]);
 
if (idx > -1) {
                    data = data[idx];
//if dataValueField provided, use _value
                    text = that.options.dataValueField ? that._value(data) : 
                           that._text(data);
 
if (separator) {
                        text = replaceWordAtCaret(caretPosition(that.element[0]), that._accessor(), text, separator);
                    }
 
                    that._accessor(text);
                    that.current(li.addClass(SELECTED));
                }
            }
        }
    }

為AutoComplete增加dataValueField參數有助提高應用彈性,在官方討論區PO了提議,希望未來有機會加入規格,就不需要這個額外Hacking囉!

線上展示

解決Kendo UI NumericTextBox注音輸入法數字輸入問題

$
0
0

自己用是倉頡輸入法,Kendo UI數字欄位(NumericTextBox)一直用得很開心,直到同事回報: NumericTextBox配上IE瀏覽器,使用注音輸入法時無法用鍵盤數字區(鍵盤右方的數字盤)輸入數字,使用Chrome或Firefox則無此問題。[Bug示範 (用IE+注音輸入法可重現問題)]

追進原始碼,判斷問題出在NumericTextBox的keydown事件,使用注音輸入法時,數字盤的按鍵代碼在IE與其他瀏覽器可能不同導致差異。原本已經捲好袖子要開始Hacking,福至心靈想到另一個巧妙解法: -ms-ime-mode!

既然是IE獨有的問題,用IE獨有的功能解決剛剛好! 遇到數字欄位時強迫把中文輸入法關掉,就不用煩惱注音輸入法的按鍵代碼跟別人有什麼不同。在CSS中加入一條新規則:

/* kendoNumericTextBox 注音數字輸入問題修正 */
.k-numeric-wrap > input
{
/* 在IE下啟用注音輸入法無法用數字盤輸入,故停用IME */
    -ms-ime-mode: disabled;
    ime-mode: disabled;
}

靠著一條CSS規則,談笑間茶包灰飛煙滅,不亦快哉! 線上展示

【CSS新手筆記】Box-Sizing

$
0
0

為了解決padding影響寬度造成破版的問題,新學會一個CSS屬性: Box-Sizing

Box-Sizing只決定一事件: 矩型元素在計算寬度及高度時,border及padding為內含還是外加? (參考: Box Model) 而其設定值有三種: content-box、border-box及padding-box。預設值為content-box,意指元素實際寬高等於CSS指定寬高再加上border/padding(外加)、border-box時則border/padding為內含,元素的實際寬高即為CSS所指定寬高,而扣除border/padding後才是呈顯內容元素的範圍。支援padding-box的瀏覽器還不多,暫且忽略。

舉例來說: 一個width 200px的<DIV>,若padding為10px、border也是10px,預設box-sizing: content-box時,<DIV>實際寬度將為220px(200+10+10);若box-sizing改為border-box,則<DIV>實際寬度為200px,其內容物範圍寬度為180px(200-10-10)。

來個實例進行驗證:

<!DOCTYPEhtml>
<html>
<head>
<metacharset=utf-8/>
<title>CSS3 Box-Sizing 示範</title>
<style>
    .width200 {
      border: 10px solid #444;
      margin: 20px;
      padding: 10px;
      width: 200px;
      height: 100px;
      background-color: yellow;
    }
    .width200 > div {
      height: 100%;
      background-color: gray;
    }
</style>
</head>
<body>
<divclass="width200"
style="box-sizing: border-box; margin-left: 40px">
<div></div>
</div>
<divclass="width200">
<div></div>
</div>
</body>
</html>

圖中的兩個<DIV>都被設為width: 200px; height: 100px; padding: 10px; border: 10px;,差別在於上方box-sizing設成border-box(另外加了margin-left: 20px以便與下方左右標齊),下方則不指定box-sizing,維持預設值(content-box)。

由結果可看出,設為border-box時,<DIV>尺寸為200x100,而內部淺灰區域被壓縮到只剩180x80;而content-box時,<DIV>尺寸為220x120,內部淺灰內容區域維持200x100,面積與上方<DIV>相同,由此例可看出border/padding外加與內含的差異,也說明box-sizing的可應用時機。


網頁元素異動在IE9出現延遲顯示現象

$
0
0

在IE9遇到一個問題: 透過Knockout text繫結變更SPAN內容,IE9無法立即顯示更新結果,但開啟IE Dev Tools要開始偵查時才回神連忙顯示。切到IE8/7相容模式時不會發生,在其他版本IE(IE10、IE8)或Chrome、Firefox也不曾遇過,推測是IE9渲染(Render)引擎的Bug。

KO範例7剛好可以用來重現問題。理論上勾選最前方的Checkbox,後方的"完成!"字樣應立刻顯現、取消勾選應立即消失,但如以下示範,使用IE9測試時必須要將滑鼠移至後方文字處點擊一下,"完成!"文字才會顯示或消失。(猜想是點擊觸發了Repaint之類的事件,強迫IE重新產生內容)

嘗試後,找到一個簡單的Workround – 將<SPAN>設為position: relative,顯示結果未變但IE渲染運算的邏輯有別,似乎就避開有Bug的程式碼,問題就消失囉~

CODE-封裝Office繁簡轉換服務

$
0
0

手邊的專案涉及多國語系,之前研究過使用Excel維護多國語系字串資源檔,意外發現Office的繁簡轉換功能威猛過人,不單只是置換字元編碼,還能做到詞彙轉換,將字彙轉換成對應的說法,例如: 交易資料->事务数据、預設記憶體->默认内存... 等等,放著神兵利器不用,豈不暴殄天物? 於是,延續先前開發Word套表服務的概念,裝著Word當引擎的裝甲車登場囉~

程式碼的重點在於Word Document物件的共用與資源確實回收(操作Office Interop的注意事項先前也討論過)。至於轉換核心,一行搞定 -- TCSCConverter

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Office.Interop.Word;
 
publicclass ChineseStrConverter : IDisposable
{
private Application wordApp = null;
private Document doc = null;
 
public ChineseStrConverter()
    {
        wordApp = new Application();
        wordApp.Visible = false;
        doc = wordApp.Documents.Add();
    }
 
publicstring ConvChineseString(string src, bool chs2cht = false)
    {
string result = null;
try
        {
            doc.Content.Text = src;
            doc.Content.TCSCConverter(
                chs2cht ? //由參數決定簡->繁或是繁->簡
                WdTCSCConverterDirection.wdTCSCConverterDirectionSCTC : 
                WdTCSCConverterDirection.wdTCSCConverterDirectionTCSC,
true, true);
//取回轉換結果時,結尾會多出\r
            result = doc.Content.Text.TrimEnd('\r');
        }
catch (Exception ex)
        {
            result = "Error: " + ex.Message;
        }
return result;
    }
 
 
publicvoid Dispose()
    {
//確實關閉Word Application
try
        {
//關閉Word檔
object dontSave = WdSaveOptions.wdDoNotSaveChanges;
            ((_Document)doc).Close(ref dontSave);
//確保Document COM+釋放
if (doc != null)
                Marshal.FinalReleaseComObject(doc);
            doc = null;
            ((_Application)wordApp).Quit(ref dontSave);
        }
finally
        {
            Marshal.FinalReleaseComObject(wordApp);
        }
    }
}

不過,詞彙轉換涉及一定複雜度,故得留意轉換效率。網路上有一篇很棒的文章: C# 繁簡轉換效能大車拚,比較了四種不同繁簡轉換方式的速度,其中Word是最慢的,但也是唯一支援詞彙轉換的,所以沒啥好抉擇,了解其執行效率,應用時心裡有數就好。

寫個小程式測試,轉換功能正常,至於速度,在我的環境(CPU i7-2600 + Win2008R2 + Word 2010),對字串(約30個字元)進行200次轉換(繁轉簡、簡轉繁各100次)耗時3.6秒,故每次轉換約18ms;762個字元的繁體文章轉為簡體100次,耗時也差不多3.6秒,每次轉換36ms。此一效能表現用於批次或預先轉換,應是綽綽有餘了!

又到了呼口號時間,大家一起為本日MVP歡呼: Office 好威呀!

【茶包射手筆記】Word無法在Windows Service中執行

$
0
0

嘗試將先前提過的Word套表服務寫成Windows Service,原本使用Console Application + Self-Hosting ASP.NET Web API測試無誤,移進Windows Service卻壞了。試過將服務執行身分調為本機系統(LocalSystem)或AD網域帳號,錯誤依舊。使用Visual Studio追蹤進程式找到錯誤源頭:

doc = wordApp.Documents.Open(FileName: ref filePath, ReadOnly: ref readOnly);
doc.Activate();

filePath指向的範本docx確定存在,Documents.Open()也未出錯,但執行完doc == null,導致下一列要執行doc.Activate()時爆出NullReferenceException。

同一服務裝在Windows 2003運作順利,在我自己的Windows 2008 R2上也沒問題,問題只發生在另一台Windows 7 32bit開發機器上,一時鬼打牆,瞎抓好一陣子才想到是老問題: c:\windows\syswow64\config\systemprofile\desktop (開始曾一度懷疑,但不知怎麼錯記成它只發生在64bit,連口供都沒問就把人放走... orz)

在32位元Windows 7上開個C:\Windows\System32\config\systemprofile\Desktop資料夾,搞定收工。

補記一點: 掛成Windows Service的程式還能逐行偵錯嗎? Yes, You Can!

執行Windows Service後,用DEBUG/Attach to Process找到該程序掛上去,接下來設中斷點、逐步偵錯的做法就跟直接由VS啟動沒有兩樣囉~ 向地表上最強大的IDE – Visual Studio敬禮!!

程式師語錄 - 黑暗選集

$
0
0

語錄有兩種,有一種在於闡述智慧哲理,使人樂觀向上,積極進取;另一種則直搗人生無奈,看完會心一笑,撫慰心靈。方式不同,但都是能帶來正面能量的心靈雞湯。最近看到網友but翻譯自日本程式鄉民的開發感想彙整,顯然是後者,讓搞程式專案快20年的我,讀來心有戚戚焉,忍不住頭點如搗蒜。

摘要整理一些特別有fu的(部分有額外融入自已的觀點及體驗,作了改寫),預備於未來身處專案煉獄或遭程式花惹發纏身時咀嚼回味,追求心靈平靜,有消彌輕生念頭之效... (與讀金剛經有相同功效來著 XD)

  • 每天有24小時。所謂的「今天之內」是指明天早上9點之前。
  • 程式不會照你所想的方式跑,只會照你所寫的方式跑。
  • 多想10秒鐘,你可以不用說「嗯,這個做得到」。
  • 需求規格在程式寫完後才會敲定;基本規格要客戶看到成品後才會決定;詳細規格要使用者用過後才會確定。
  • 軟體設計的奧義: 讓軟體設計單純到很明顯不會出錯,不然就是複雜到有錯也看不出來。
  • 先說「沒辦法」的人贏,有意見的話你自己寫。
  • 要殺一個程式設計師不需要刀,改三次規格就好囉!
  • 詳細設計要寫在程式碼註解裡,註解常是唯一救贖,記得要讓自己看得懂。
  • 程式異常該稱為「Bug」還是「規格限制」是看Deadline還有多久決定。
  • 地獄期在一段時間後,充滿殺氣的怒吼會變多;
    再持續一段時間,說話變少牢騷會變多,開始壟罩在凝重氣氛裡。
    再持續下去反而會海闊天空,四周洋溢充滿活力的聲音。
    這種狀態稱為「Programmer’s High」,也是開始有人倒下的時候。
    (註: 馬拉松有所謂Runner's High,指跑者到後期反而精神舒暢的迴光返照期)
  • 老手用來提振精神的魔法格言:「比起以前來說這算不了什麼…」
    新人用來提起幹勁的魔法格言:「把這件工作做完的話…」(他們還不知道工作是沒有終點的。)
  • 能夠迅速想到解法的程式設計師太多了,他們用一分鐘想到方法,用一天去寫程式;而不是花一小時想解法,再用一小時去寫程式。
  • 改掉舊的Bug總是會導致新的Bug -- 這是所謂的「Bug不滅定律」。
  • 無論規格多晚確定,結案期限永遠不會變 -- 這是所謂的「期限守恆定理」。
  • 不懂電腦的操作者是找出Bug的天才,很遺憾,同一個Bug他們通常沒法表演第二次。
  • 規格與規格書是兩碼事。
  • 將沒發現任何Bug的系統送上線 -- 恐怖啊! 恐怖到了極點~
  • 當某人寫的程式碼出現Bug,當事人通常不在(墨菲定理?)
  • 系統交付日是為了延後而存在的。
  • 當你有「啊,加上這功能吧」的念頭時,還是不要想太多,早點去睡結果會比較好。
  • 熟悉程式語言不表示就會寫軟體;徹底熟悉程式語言後,軟體會寫得更慢~
  • 發現問題如何解決不是最重要的,發現哪裡是問題比較重要。
  • 還沒付錢的,不是客戶;已經付清的,也不是客戶。
  • 認真聽足客戶抱怨兩小時,問題就算解決一半,有時連程式都不用改就可結案。
  • 接受「口頭規格」,就像開一張空白支票給別人一樣。
  • 要證明 Bug 不是自己的責任,往往比修好它更花時間。
  • Bug是很害羞的,跟你獨處時明明很大方,有別人來看時就躲起來了。
  • 程式的養分來自程式設計師的鮮血。
  • 當程式碼的規模超過臨界點,就會脫離程式設計師的手擁有自己的意志。
  • SA總是很扯地跟程式師說: "怎麼會沒辦法?";
    業務總是沒辦法跟客戶說: "這個很扯"
  • 機器感受到龐大的時程壓力,所以它也崩潰了 orz
  • 沒被發現的 Bug,就不算Bug!

原始出處:
http://but.tw/2008/10/programmers_rule/
http://but.tw/2011/10/programming-rule-next/

使用PhantomJs產生網頁擷圖

$
0
0

將網頁內容另存圖檔是專案裡三不五時會冒出的需求,但一直沒找到順手好使的兵刃。

不久前介紹過HTML轉PDF的元件 -- Pechkin套件,網頁存成PDF已多少有保留擷圖的意義,但文末對本部落格的實測讓人失望,失真嚴重。最近的專案又被逼著設法將現成網頁(由JavaScript動態產生內容)轉存圖檔供其他系統應用,省去另外開發匯出模組的工程。再次Survey解決方案,想起先前流浪小風在Chutzpath介紹提過另一個webkit核心的網頁操作引擎 – PhantomJs

小試之後驚為天人,簡便易用卻威力無窮,PhantomJs可輕易實現以下功能:

  1. 不依賴任何瀏覽器,直接以命令列工具方式載入網頁,並開放以指令存取網頁DOM。這個基礎建立後,衍生的好玩應用就多了。
  2. 能操作DOM物件並檢查結果,要做到網頁自動測試不是夢。(有個術語叫Headless Website Testing,意指不需瀏覽器不顯示任何網頁UI就完成測試)
  3. 一行指令就能將網頁顯示結果另存成圖檔。
  4. 除了HTML5及CSS3,還支援Canvas及SVG。
  5. 監控DOM載入及AJAX傳輸過程,進行效能分析。
  6. 提供互動式操作模式(REPL),可逐次下指令、看結果,即興演出。
  7. 除了測試網頁,撰寫程式操作網頁還能用來幹些邪惡的勾當執行特殊任務,例如: 搶春節車票、在馬拉松報名秒殺戰裡突圍、發動網路灌票... 等等。理論上用瀏覽器外掛或寫程式模擬POST Request也能達成相同效果,用PhantomJs在效能及簡便性上具一定的優勢。

使用PhantomJs不需安裝,到網站下載ZIP檔,解壓縮後有個phantomjs.exe,靠它就可以開始變戲法。例如,將以下幾行程式存成lab.js,放在phantomjs.exe同目錄下,開啟命令列視窗,CD路徑到phantomjs.exe所在資料夾,執行phantomjs lab.js,就能抓取本部落格的擷圖:

var page = require('webpage').create();
page.open('http://blog.darkthread.net', function() {
    page.render('d:\\darkthread.png');
    phantom.exit();
});

產生的圖檔十分逼近網頁的實際檢視結果! (主要只差Flash部分無法顯示)

另外,我還寫了一個簡單的Canvas + SVG測試網頁,用PhantomJs也成功儲存顯示結果,令人感動到起雞皮疙瘩~

新武器入手,未來搞網站自動測試、綱頁擷圖、為非作歹突圍奇襲又多了神兵利器可用。啊~~ 福氣啦!

Viewing all 2311 articles
Browse latest View live