2014-02-21 17:43

[T4] url, base64, sprite 三種格式的 icons.css 產生器

之前有寫過 [PHP] url, base64, sprite 三種格式的 icons.css 產生器,這次換用 C# T4 Text Templates 來製作這個功能:

int spriteGap = 30;  
var scanTypes = new string[]{".jpg", ".gif", ".png"};

string workDirectory = 
    Path.GetDirectoryName(this.Host.TemplateFile);


/* 取得圖檔資訊 */  
List<ImageInfo> imageList = Directory
    .EnumerateFiles(Path.Combine(workDirectory, "icons"))
    .Where(path => scanTypes.Contains(Path.GetExtension(path)))
    .Select(path => 
    {
        var image = Image.FromFile(path);

        return new ImageInfo{
            FilePath = path,
            FileName = Path.GetFileName(path),
            IconImage = image,
            IconName = Path.GetFileNameWithoutExtension(path),
            TopOffset = 0,
            Width = image.Width,
            Height = image.Height,
            IsAnimated = ImageAnimator.CanAnimate(image),
        };
    })
    .OrderBy(info => info.IsAnimated)
    .ToList();



/*檢查重複的圖檔名稱*/
List<string> repeatList = imageList.GroupBy(info => info.IconName)
    .Where(g => g.Count() > 1)
    .SelectMany(g => g.Select(x => x.FileName))
    .ToList();

if(repeatList.Count > 0){
    throw new Exception(
        "出現重複的圖檔名稱[" + String.Join(", ", repeatList) + "]"
    );
}   



/* 製作 CSS Sprite */
int sumHeight = imageList.Sum( info => info.Height);
int spriteWidth = imageList.Max( info => info.Width);
int spriteHeight = sumHeight + (imageList.Count - 1) * spriteGap;

var bitmap = new Bitmap(spriteWidth, spriteHeight);

using (Graphics graphics = Graphics.FromImage(bitmap))
{           
    int nextTop = 0;
    for(int i=0; i< imageList.Count; i++){
        /*忽略具有動畫的 GIF 圖片*/ 
        if(imageList[i].IsAnimated){ continue; } 

        imageList[i].TopOffset = nextTop;
        
        graphics.DrawImage(imageList[i].IconImage, 0, nextTop);
            
        nextTop = nextTop + imageList[i].Height + spriteGap;
    }
        
    graphics.Save();
}

SaveOutput("icons.sprite.png"); 
bitmap.Save(
    Path.Combine(workDirectory, "icons.sprite.png"), 
    ImageFormat.Png
);
bitmap.Dispose();

下載完整程式: t4_make_icons_css.zip
2014-02-18 22:46

[jQuery] $.log 與 $.fn.dump

記錄一下,偷懶的 console.log
if ($ && $.fn) {
    $.log = (window['console'] && typeof(console.log)=='function') ?
        function () { console.log.apply(console, arguments); } :
        $.noop;

    $.fn.dump = function (tag) {
        var args = Array.prototype.slice.call(arguments);
        args.push(this);

        $.log.apply($, args);
        return this;
    }
}


// use
$.log('ok');

$('img').dump();

$('div').dump('my tag');
2014-02-18 22:26

[jQuery] 製作 $.fn.delayAll

jQuery 有提供 delay 這個方法,可惜只能用在動畫操作上,想要做到下面的事情就只能用 setTimeout 了。
// not working
$('div').css('color','red').delay(200).css('color','blue');


// use setTimeout
$('div').css('color','red');

setTimeout(function(){
    $('div').css('color','blue');
}, 200);


稍微研究了一下,發現 jQuery 本身有製作 queue 的功能,這個用來存放 delay 後要執行動作的紀錄器,最後只要對 jQuery Object 作一個代理器的包裝,就可以達到想要的目的了。
var queueName = 'delayAll';

/*定義 jQuery Delay 代理類別*/
function jQueryDelay(jqObject, duration){
    /*將缺少的成員屬性補上*/
    for(var member in jqObject){
        if(!$.isFunction(jqObject[member]) || !this[member]){
            this[member] = jqObject[member];
        }
    }

    /*新增 delay 時間並啟動 queue*/
    jqObject
        .delay(duration, queueName)
        .dequeue(queueName);

    /*紀錄 jQuery 物件*/
    this._jqObject = jqObject;
};


/*為所有的 jQuery 方法製作 proxy*/
for(var member in $.fn){
    if(!$.isFunction($.fn[member])){ continue; }

    jQueryDelay.prototype[member] = function(){
        var jqObject = this._jqObject;
        var args = Array.prototype.slice.call(arguments);
        var mothed = arguments.callee.methodName;

        /*將需要執行動作加入 queue*/
        jqObject.queue(queueName, function(next) {
            jqObject[mothed].apply(jqObject, args);
            next();
        });

        return this;
    };

    /*紀錄方法名稱,在 proxy 時會需要參考到*/
    jQueryDelay.prototype[member].methodName = member;
}


/*允許多次串接的可能*/
jQueryDelay.prototype.delayAll = function(duration){
    this._jqObject.delay(duration, queueName);
    return this;
};


/*用 jQueryDelay 將原本的 jQuery Object 包裝起來*/
$.fn.delayAll = function(duration){
    return new jQueryDelay(this ,duration);
};


使用範例:
$('div').css('color','#f00')
    .delayAll(2000).css('color','#0f0')
    .delayAll(2000).css('color','#00f');


檔案下載:jquery.delayAll.js
2014-02-15 14:48

[C#] 取得 Domain 的 IP 列表

//using System.Net;
IPAddress[] ipList = Dns.GetHostAddresses("www.google.com");


來源:
Dns.GetHostAddresses 方法 (System.Net)

2014-02-15 14:45

[C#] delegate 到 Lambda Expressions 語法演進

一開始要看懂 Lambda Expressions 有點困難,下面會以演進方式來介紹如何做到語法省略。

首先定義一個單參數的 delegate
delegate int Del(int x);

以傳統 delegate 的語法來建構 delegate
Del a = delegate(int x) { return x + 2; };

去掉 delegate 改成 Lambda 表示式
Del a = (int x) => { return x + 2; };

由於大括號裡只有一句陳述式,而且是一個 return 的陳述式,所以可以省略大括號跟 return
Del a = (int x) => x + 2;

在 delegate 已經有定義輸入參數的型別,所以在小括號裡的型別可以省略
Del a = (x) => x + 2;

由於小括號裡面只有一個輸入參數,所以可以再進一步省略小括號
Del a = x => x + 2;

參考來源:
Lambda 運算式 (C# 程式設計手冊)
2014-02-13 23:54

HTTP GET 與 POST 的比較及使用時機

  GET POST
瀏覽器歷史紀錄 參數都會紀錄,因為都是URL的一部分 參數都不會紀錄
加入書籤 參數都會紀錄,因為都是URL的一部分 參數都不會紀錄
回上一頁/
重新載入
GET請求是重新執行,但被存儲在瀏覽器的快取,則不被重新提交到服務器 數據將被重新提交(瀏覽器通常會警告使用者該數據將需要重新提交)
編碼類型 application/x-www-form-urlencoded multipart/form-data 或 application/x-www-form-urlencoded,使用多編碼的二進制數據
參數大小限制 受限於 QueryString 長度限制(不超過 2KB 是最保險的,有些瀏覽器可以允許多達 64KB) 允許大量傳輸,包括上傳文件到服務器
參數傳輸方式 QueryString POST Data(message-body)
安全性 容易破解,因為參數是網址的一部分,所以它被紀錄在瀏覽器歷史記錄和明文服務器日誌 比較難破解
使用性 不應該被使用在發送密碼或其他敏感信息上 使用在發送密碼或其他敏感信息上
能見度 GET方法是對所有人可見(它會顯示在瀏覽器的地址欄) POST方法變量不顯示在URL中
執行速度 快,GET 比 POST 快 1.5 倍 慢,POST 多出需要發送數據的步驟
快取 瀏覽器會依據網址來快取資料,不同的網址有不同的快取 瀏覽器不會快取
自動重送 會,在回應過長時會重發請求,直到重試結束 不會,一個請求發出後會一直等待回應
適用行為 檢視(Read) 新增(Create)、修改(Update)、刪除(Delete)
情況包括 透過 <link href="">、<img src="">、<script src="">、<iframe src=""> 額外載入的 JavaScritp、CSS、圖片 透過 <form method="post"> 以及 Ajax post 發送的請求


GET 適合用在「檢視(Read)」的操作行為,由於檢視資料的操作會遠比資料異動來的更頻繁,所以需要更快的回應,而且有快取可以加快二次檢視,參數在網址上可以使每一個網址都代表一個網頁,在加入書籤的連結能夠返回對應的網頁,再者所需要的參數很少(例如:id=122&type=1),對於資料異動後快取沒更新的問題,可以在 QueryString 加上資料最後修改的時間戳記(例如:id=122&type=1&t=1392183597718)。


POST 適合用在「新增(Create)修改(Update)刪除(Delete)」的操作行為,資料異動所需要傳送的參數很可能超過 QueryString 的限制,不適合用 GET 來處理資料異動的傳送,GET 在等待過久會重新發送請求,這會造成重複的請求,如果在新增儲存就多新增一筆資料,而 POST 在一個請求發出後會一直等待回應,這可以保障在傳送的過程中請求是唯一的,新增資料的請求如果被記錄在書籤或歷史紀錄中,使用者點擊連結網址就新增一筆資料,這真是一件恐怖的事,所以在資料異動上的請求必須使用 POST 來傳送。


參考來源:
GET vs POST - Difference and Comparison | Diffen
HTTP Methods: GET vs. POST
寻根究底:Ajax请求的GET与POST方式比较
2014-02-10 23:43

[C#] 對 FirstOrDefault 新的認識

FirstOrDefault 會依據最後的型別去決定 Default 時回傳的值,例如下面的範例:
int? a = (new List<int>{2}).Select(x => x).FirstOrDefault();
// 2

int? b = (new List<int>{}).Select(x => x).FirstOrDefault();
// 0


int? c = (new List<int>{2}).Select(x => (int?)x).FirstOrDefault();
// 2

int? d = (new List<int>{}).Select(x => (int?)x).FirstOrDefault();
// null
2014-02-10 23:38

[C#] 用 LINQ 將字串切割成整數陣列

int id;
int[] idArray = "1,2,3,4,5".Split(',')
    .Where(idStr => Int32.TryParse(idStr, out id))
    .Select(Int32.Parse)
    .ToArray();

這裡用到將 Int32.Parse 這個一般 method 指向給 delegate 的技巧。
2014-02-05 20:53

[C#] HttpUtility.ParseQueryString 的隱藏密技

在使用 Request.QueryString 發現 ToString 會產生 URL 的 query 字串,嘗試用 NameValueCollection 的 ToString 卻不是產生 URL 的 query 字串,這一整個就很奇怪,明明都是 NameValueCollection 確有不一樣的結果,透過 Reflector 發現 Request.QueryString 的 instance 型別是一個 HttpValueCollection,想說可以直接 new HttpValueCollection 出來使用,但 HttpValueCollection 卻是 System.Web 的內部 Class,外部是無法直接 new 出來使用,還好在又發現 HttpUtility.ParseQueryString 回傳的 NameValueCollection 的 instance 是 HttpValueCollection 這個型別,所以可以透過 HttpUtility.ParseQueryString 來建立 HttpValueCollection。

// using System.Web;

var qs1 = HttpUtility.ParseQueryString("id=5&type=1");
qs1.ToString(); // "id=5&type=1"

var qs2 = HttpUtility.ParseQueryString(String.Empty);
qs2["id"] = "11";
qs2["name"] = "Tom"; 

qs2.ToString(); // "id=11&name=Tom"


HttpValueCollection 的簽名
[Serializable]     
internal class HttpValueCollection : NameValueCollection     
{
}


ParseQueryString 的簽名
public static NameValueCollection ParseQueryString(string query, Encoding encoding)
{
    if (query == null)
    {
        throw new ArgumentNullException("query");
    }
    if (encoding == null)
    {
        throw new ArgumentNullException("encoding");
    }
    if ((query.Length > 0) && (query[0] == '?'))
    {
        query = query.Substring(1);
    }
    return new HttpValueCollection(query, false, true, encoding);
}
2014-02-05 20:25

[CSS] float 與 clear

float

一開始是用來定義文繞圖的呈現,後來其浮動特性很適合用來排版佈局,所以大部分的網頁都用這種方式排版。

特性:
  • 不佔用父元素的空間
  • 寬高會內縮至子元素所呈現的大小
  • 會排擠其他相鄰的 inline 元素
  • 在所定位的空間不足時會自動換行

<div style="border:1px solid #f00; float:left;">div 1</div>
<div style="background:#0f0;">div 2</div>
div1
div2

透過上面的範例可以看出因為 div1 不佔用空間而讓 div2 的位子上移了,然而呈現與 div1 重疊的效果,以及可以看到 float 排擠文字的特性,而讓 div2 的文字被擠到 div1 之後。



clear

清除在元素相鄰邊上的 float 元素

屬性:
  • none 不做任何 clear 動作
  • left 將元素向下換行,來排除具有 float:left 的相鄰元素
  • right 將元素向下換行,來排除具有 float:right 的相鄰元素
  • both 將元素向下換行,來排除具有 float:left 或 float:right 的相鄰元素

<div style="border:1px solid #f00; float:left;">div1</div>
<div style="background:#0f0; clear:left;">div2</div>
div1
div2

這個範例在 div2 加上 clear:left 的屬性,讓 div2 根據前一個具有 float:left 元素之後向下換行,來排除具有 float:left 的相鄰元素。


<div style="border:1px solid #f00; float:left;">div1</div>
<div style="background:#0f0; clear:right;">div2</div>
div1
div2

這個範例則是將 div2 換成 clear:right,可以看到 div1 與 div2 仍舊重疊在一起,這是因為 div2 前一個具有 float:right 不存在,而 div1 的 float:left 不是 div2 clear:right 排除的對象。