2014-11-10

[轉載] Javascript Char Codes (Key Codes)

轉載自:Javascript Char Codes (Key Codes)

KeyCode
backspace8
tab9
enter13
shift16
ctrl17
alt18
pause/break19
caps lock20
escape27
page up33
page down34
end35
home36
left arrow37
up arrow38
right arrow39
down arrow40
insert45
delete46
048
149
250
351
452
553
654
755
856
957
a65
b66
c67
d68
KeyCode
e69
f70
g71
h72
i73
j74
k75
l76
m77
n78
o79
p80
q81
r82
s83
t84
u85
v86
w87
x88
y89
z90
left window key91
right window key92
select key93
numpad 096
numpad 197
numpad 298
numpad 399
numpad 4100
numpad 5101
numpad 6102
numpad 7103
KeyCode
numpad 8104
numpad 9105
multiply106
add107
subtract109
decimal point110
divide111
f1112
f2113
f3114
f4115
f5116
f6117
f7118
f8119
f9120
f10121
f11122
f12123
num lock144
scroll lock145
semi-colon186
equal sign187
comma188
dash189
period190
forward slash191
grave accent192
open bracket219
back slash220
close braket221
single quote222
  
2014-10-02

[轉載] Superpower your JavaScript with 10 quick tips - For Beginners

轉載自:Superpower your JavaScript with 10 quick tips - For Beginners

Who doesn't love to write better code? In this article I will list 10 simple and quick JavaScript tips that will help beginners improve their code quality. You might know some of these already. In case you didn't feel free to buy me a beer later.


#1 - Use && and || Operators Like a Boss


Well, JavaScript gives you two awesome logical operators : && and ||. In other languages like Java or C++ they always return a boolean value. But things get a bit interesting when it comes to JavaScript. The && and || operators return the last evaluated operand. Have a look at the following

function getMeBeers(count){
    if(count){
        return count;
    } 
    else{
        return 1;
    }
}

Well, our function is simple and returns the required number of beers. If you specify no argument, it simply gives you a single beer. Here, you can use || to refactor the code as following:

function getMeBeers(count){
    return count || 1;
}

In case of || if the first operand is falsy, JavaScript will evaluate the next operand. So, if no count is specified while calling the function, 1 will be returned. On the other hand, if count is specified then it will be returned (as the first operand is truthy in this case).

Note: null, false,0 (the number), undefined, NaN, and '' (empty string) are falsy values. Any other value is considered truthy.

Now if you want to allow adults only, you can use && as following:

function getMeBeers(age, count){
    return (age>=18) && (count || 1);
}

Instead of :

function getMeBeers(age, count){
    if(age>=18){
        return count || 1;
    }
}

In the above case if age < 18, JavaScript won't even bother evaluating the next operand. So, our function will return. But if the age is actually >= 18, the first operand will evaluate to true and then the next operand will be evaluated.

By the way you should be a careful here. If you pass count as 0, the function will return 1 (as 0 is falsy). So, in a real world app you should be careful while handling numeric values.



#2 - Use === and !== Instead of == and !=


The operators == and != do automatic type conversion, if needed. But === and !== will consider both value and type while comparing and won't do any automatic type conversion. So, to reliably compare two values for equality/non-equality always use === and !==.

10 == '10' //true

10 === '10' //false

10 === 10 //true



#3 - Use Strict mode while writing JS


Strict mode was introduced in ECMA 5. It allows you to put a function or an entire script into strict operating context. The benefits are:

  1. It eliminates some of the silent JavaScript errors by throwing the errors explicitly.
  2. It throws exceptions when relatively unsafe actions take place.
  3. Sometimes strict mode code can run faster than non strict mode code.

<script type="text/javascript">
      'use strict'
      //Strict mode code goes here
</script>

Or,

function strictCode(){
    'use strict'
    //This is a strict mode function
}

As currently all the major browsers support this feature you should start using strict mode.



#4 - Use splice() to remove an array element


If you want to remove an element from an array use splice() instead of delete. delete sets the particular array item to undefined while splice() actually removes the item.

var array=[1,2,3];
delete array[1]; // now array= [1,undefined,3]

array.splice(1,1); //now array=[1,3]



#5 - Use [] to create new array


Use [] to create a new array. Writing var a=[1,2,3] will create a 3-element array whereas new Array(N) will create a physically empty array of N logical length.

Similarly, use var a={} to create an object rather than new Object(). The former one is compact, readable and takes less space. You can also instantly populate the object like:

var a = {
    name: 'John Doe',
    email: 'john@doe.com'
}



#6 - Use String.link() to create Hyperlinks


Many times in JavaScript you will need to generate HTML anchors on the fly. For that you will need some concatenation which may look ugly and messy.

var text="Cool JS Tips";
var url="http://www.htmlxprs.com";
var block='<a href="'+url+'">'+ text +'</a>';

Instead, how about this:

var block=text.link(url); // <a href="http://www.htmlxprs.com">Cool JS Tips</a>



#7 - Cache the length while looping


While looping through a JavaScript array you can cache the length so that the overall performance is better:

for (var i=0,length=array.length; i<length; i++){
    //awesome code goes here
}

Be careful while creating an inner loop. You need to name the length variable differently in the inner one.



#8 - Put 'var' before your variable name


While creating variables don't forget to use the var keyword. If you forget then the variable gets added to the global scope which is definitely a bad thing.

var localVar=12; //this is a local variable

globalVar=12; //variable goes to 'window' global



#9 - Use Closures and Self Executing Functions


Closures, if used carefully and precisely, can take your JS code to a whole new level. There are plenty of tutorials available which can teach you about closures. I will just give an example of closures and self executing functions for creating modular code:

var car=(function(){
    var _name='Benz Velo'; 
    return {
        getName: function(){
            return _name;
        }
    }
})(); //function created and invoked

console.log(car.getName()); //Logs Benz Velo

As you see we just created a function and immediately invoked it. These are called self executing functions or Immediately Invoked Function Expression (IIFE). The function returns an object which is stored in variable car. We also used closure because the object being returned can access the variable _name defined by the parent function. This is helpful for creating modules and emulating private variables.



#10 - There is always room for Optimization


Use jsHint or jsLint to check your code quality and improve further. Also don't forget to minify and concatenate your JS files while deploying your app. Finally, use SourceMaps to make debugging a breeze.



Conclusion


Tips can be unlimited. But I hope these 10 quick tips will help you write better code and superpower your JS-Foo. If you loved this you will love our newsletter as well. Don't forget to subscribe.
2014-05-08

[AngularJS] 製作 jQuery UI Sortable directive

Html
<div jq-sortable="selectedList">
    <div ng-repeat="item in selectedList">
        <img ng-src="{{item.src}}" />
    </div>
</div>


JavaScript
app.directive('jqSortable', ['$parse', function($parse) {
    return function(scope, element, attrs) {
        /*解析並取得表達式*/
        var expr = $parse(attrs['jqSortable']);
        var $oldChildren;

        element.sortable({
            opacity: 0.7,
            scroll: false,
            tolerance: "pointer",
            start: function() {
                /*紀錄移動前的 children 順序*/
                $oldChildren = element.children('[ng-repeat]');
            },
            update: function(){
                var newList = [];
                var oldList = expr(scope);
                var $children = element.children('[ng-repeat]');

                /*產生新順序的陣列*/
                $oldChildren.each(function(i){
                    var index = $children.index(this);
                    if(index == -1){ return; }

                    newList[index] = oldList[i];
                });

                /*將新順序的陣列寫回 scope 變數*/
                expr.assign(scope, newList);

                /*通知 scope 有異動發生*/
                scope.$digest();
            }
        });

        /*在 destroy 時解除 Sortable*/
        scope.$on('$destroy', function(){
            element.sortable('destroy');
        });
    };
}]);

[AngularJS] 製作 Mouse Drag Event directive

Html
<div ng-style="{'top': itemTop, 'left': itemLeft}"
    my-mousedrag="itemTop = itemTop - $deltaY; itemLeft = itemLeft - $deltaX"
></div>

JavaScript
app.directive('myMousedrag', function() {
    return function(scope, element, attrs) {
        var prevEvent;
        element.mousedown(function(event){
            prevEvent = event;

        }).mouseup(function(event){
            prevEvent = null;

        }).mousemove(function(event){
            if(!prevEvent){ return; }

            /*將 element 拖移事件傳遞到 scope 上*/
            scope.$eval(attrs['myMousedrag'], {
                $event: event,
                $deltaX: event.clientX - prevEvent.clientX,
                $deltaY: event.clientY - prevEvent.clientY
            });

            /*通知 scope 有異動發生*/
            scope.$digest();

            prevEvent = event;
        });

        /*在 destroy 時清除事件註冊*/
        scope.$on('$destroy', function(){
            element.off('mousedown mouseup mousemove');
        });
    };
});

[AngularJS] 製作 jQuery MouseWheel directive

相依套件:jquery-mousewheel

Html
<div jq-mousewheel="changeSize($event, $delta, $deltaX, $deltaY)"></div>

JavaScript
app.directive('jqMousewheel', function(){
    return function(scope, element, attrs) {

        /*將 element 滾輪事件傳遞到 scope 上*/
        element.on('mousewheel', function (event) {
            scope.$eval(attrs['jqMousewheel'], {
                $event: event,
                $delta: event.delta,
                $deltaX: event.deltaX,
                $deltaY: event.deltaY
            });

            /*通知 scope 有異動發生*/
            scope.$digest();
        });

        /*在 destroy 時清除事件註冊*/
        scope.$on('$destroy', function(){
            element.off('mousewheel');
        });
    };
});

[AngularJS] 製作 jQuery scrollTop scrollLeft directive

Html
<div jq-scroll-top="viewerScrollTop" 
      jq-scroll-left="viewerScrollLeft"
></div>

JavaScript
app.directive('jqScrollTop', ['$parse', function($parse){
    return function(scope, element, attrs) {
        /*解析並取得表達式*/
        var expr = $parse(attrs['jqScrollTop']);

        /*監聽變數異動,並更新到 element 上*/
        scope.$watch(attrs['jqScrollTop'], function(value) {
            element.scrollTop(value);
        });

        /*當 element 捲動時更新到變數上*/
        element.on('scroll', function() {
            expr.assign(scope, element.scrollTop());
        });

        /*在 destroy 時清除事件註冊*/
        scope.$on('$destroy', function(){
            element.off('scroll');
        });
    };
}]);

app.directive('jqScrollLeft', ['$parse', function($parse){
    return function(scope, element, attrs) {
        /*解析並取得表達式*/
        var expr = $parse(attrs['jqScrollLeft']);

        /*監聽變數異動,並更新到 element 上*/
        scope.$watch(attrs['jqScrollLeft'], function(value) {
            element.scrollLeft(value);
        });

        /*當 element 捲動時更新到變數上*/
        element.on('scroll', function() {
            expr.assign(scope, element.scrollLeft());
        });

        /*在 destroy 時清除事件註冊*/
        scope.$on('$destroy', function(){
            element.off('scroll');
        });
    };
}]);
2014-04-25

利用 Google Script 將 Blogger 備份到 Google Drive

在 Google Drive 中建立『指令碼』



然後選擇『空白專案』



貼上以下程式碼
/*
Ref:
https://developers.google.com/apps-script/reference/drive/drive-app
https://developers.google.com/apps-script/reference/drive/file
https://developers.google.com/apps-script/reference/drive/folder
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app
https://developers.google.com/apps-script/reference/url-fetch/o-auth-config
https://developers.google.com/apps-script/reference/base/blob
https://developers.google.com/apps-script/reference/utilities/utilities

*/

var _backupKeepAmount = 10;
var _backupFolderName = 'blogger_backup';
var _backupFolder;



function main(){
  var folders = DriveApp.getFoldersByName(_backupFolderName);

  if(folders.hasNext()){
    _backupFolder = folders.next();
  }else{
    _backupFolder = DriveApp.createFolder(_backupFolderName);
  }


  setAuth();

  backupBlogger('{my_blog_1}', '{blog_id}', false);
  backupBlogger('{my_blog_2}', '{blog_id}', false);

  MailApp.sendEmail(Session.getActiveUser().getEmail(), 'Backup Blogger To Google Drive', Logger.getLog());
}


/**
 * @param {String} prefixName 備份檔名的前綴
 * @param {String} blogId
 * @param {Boolean} isBigSize 如果備份的檔案超過 10M,請設為 true
 */
function backupBlogger(prefixName, blogId, isBigSize){
  logMsg('Backup Start', prefixName);


  /* 取得之前的備份清單 */
  var files = _backupFolder.getFilesByType('application/xml');
  var beforeFiles = [];
  while (files.hasNext()) {
    var file = files.next();
    if(file.getName().indexOf(prefixName) == -1){ continue; }

    beforeFiles.push(file);
  }

  beforeFiles.sort(function (a, b) {
    return b.getName().localeCompare(a.getName());
  });


  /* 下載並儲存檔案 */
  var isChange;
  if(isBigSize){
    isChange = downloadAndSaveBigSizeArchiveXml(prefixName, blogId, beforeFiles[0]);
  }else{
    isChange = downloadAndSaveArchiveXml(prefixName, blogId, beforeFiles[0]);
  }

  /*沒有異動結束處理*/
  if(!isChange){ return; }


  /* 刪除舊檔案 */
  var oleFiles = beforeFiles.slice(_backupKeepAmount);
  for(var i=0; i < oleFiles.length; i++){
    oleFiles[i].setTrashed(true);
  }


  logMsg('Backup Complete', prefixName);
}




function logMsg(status, msg){
  Logger.log('%s | %s', status, msg);
}



function setAuth(){
  var scope = "https://www.blogger.com/feeds/";
  var oAuthConfig = UrlFetchApp.addOAuthService("blogger");
  oAuthConfig.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope="+scope);
  oAuthConfig.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken");
  oAuthConfig.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");

//  oAuthConfig.setConsumerKey("anonymous");
//  oAuthConfig.setConsumerSecret("anonymous");
}



function getArchiveXmlResponse(blogId){
  var options = {
      "oAuthServiceName" : "blogger",
      "oAuthUseToken" : "always"
  };

  var url = 'https://www.blogger.com/feeds/' + blogId + '/archive';
  var response = UrlFetchApp.fetch(url, options);

  /*下載失敗,錯誤記錄*/
  if(response.getResponseCode() != 200){
    logMsg('Download Failed', response.getAllHeaders().toSource());
    return null;
  }

  return response;
}


function downloadAndSaveArchiveXml(prefixName, blogId, beforeFile){
  var response = getArchiveXmlResponse(blogId);
  if(!response){ return false; }

  var downloadContent = response.getContentText();

  /* 檢查檔案異動 */
  if(beforeFile){
    var content = beforeFile.getBlob().getDataAsString();

    if(beforeFile && Math.abs(content.length -downloadContent.length) < 100){
      logMsg('Not Change', prefixName);
      return false;
    }
  }

  /* 儲存下載 */
  var fileName = Utilities.formatDate(new Date(), "+8", "yyyyMMdd_HHmmss'.xml'");
  _backupFolder.createFile(prefixName + '_' + fileName, downloadContent, 'application/xml');

  return true;
}



function downloadAndSaveBigSizeArchiveXml(prefixName, blogId, beforeFile){
  var response = getArchiveXmlResponse(blogId);
  if(!response){ return false; }


  /* 儲存下載 */
  var fileName = Utilities.formatDate(new Date(), "+8", "yyyyMMdd_HHmmss'.xml'");
  var downloadBlob = response.getBlob();
  downloadBlob.setName(prefixName + '_' + fileName);
  downloadBlob.setContentType('application/xml');
  var downloadFile = _backupFolder.createFile(downloadBlob);


  /* 檢查檔案異動 */
  if(beforeFile && Math.abs(beforeFile.getSize() - downloadFile.getSize() ) < 1000){
    downloadFile.setTrashed(true);
    logMsg('Not Change', prefixName);
    return false;
  }

  return true;
}


修改 {my_blog_1} 及 {blog_id},{my_blog_1} 是備份檔名的前綴,{blog_id} 則可以在文章管理的網址上找到



儲存檔案,並取名為『Backup Blogger To Google Drive』




先來測試一下備份是否能正常執行,請選擇執行的函數為『main』,並按下『執行』,然後先為此程式授權





如果都沒問題,接著來設定排程,選擇『啟動程序』



新增一個觸發程序



選擇執行的函數『main』,然後時間定在晚上 11 點,接著選擇『通知』



這個設定主要是發生錯誤的時候可以寄送通知

2014-03-09

理解 PredicateBuilder 實作方式

PredicateBuilder 提供了以 OR 串接 bool 的 Lambda Expression,使用上會像下面的程式:
var predicate = PredicateBuilder.False<Product>();

predicate = predicate.Or(p => p.Name == 'Shoe');
predicate = predicate.Or(p => p.Price > 100);


它的原始碼如下:
public static Expression<Func<T, bool>> Or<T> (
    this Expression<Func<T, bool>> expr1,
         Expression<Func<T, bool>> expr2
)
{
    var invokedExpr = Expression.Invoke (
        expr2, 
        expr1.Parameters.Cast<Expression>()
    );
 
    return Expression.Lambda<Func<T, bool>>(
        Expression.OrElse (expr1.Body, invokedExpr), 
        expr1.Parameters
    );
}


上面的程式雖然不長,但實在有點小難懂,搞不懂他為什麼要這麼寫??讓我們逐步解釋吧!


假設 expr1 是 x => x.Name == 'Shoe'
假設 expr2 是 y => y.Price > 100

var invokedExpr = Expression.Invoke(
    expr2, 
    expr1.Parameters.Cast<Expression>()
);
先組合出( 利用 expr1 的參數去執行 expr2 )的表示式 invokedExpr
產生出了語法會像 (y => y.Price > 100) (x)

Expression.OrElse(expr1.Body, invokedExpr)
將 expr1.Body 與 invokedExpr 以 || 運算子組合起來
產生出了語法會像 x.Name == 'Shoe' || (y => y.Price > 100) (x)

Expression.Lambda<Func<T, bool>>(
    Expression.OrElse(expr1.Body, invokedExpr), 
    expr1.Parameters
);
將剛剛組合好的 || 運算表示式再組合成 Lambda 表示式
產生出了語法會像 x => (x.Name == 'Shoe' || (y => y.Price > 100) (x))


參考來源:
Dynamically Composing Expression Predicates
2014-03-05

利用 HTTP Status Codes 傳遞 Ajax 成功失敗的狀態

一般處理 Ajax 回應時會傳送的資訊種類有:資料、成功訊息、錯誤訊息、失敗訊息以及處理狀態,傳遞的資訊種類並不一致,再加上除了資料之外,通常還希望能傳遞處理狀態,這種情況大部分會選擇是以 JSON 的方式傳遞這兩個訊息,以下是常見的幾種格式:

{ code: 1, msg: "OK" }
{ success: true, result: "data", errorMsg: "" }
{ status: 'success', result: [], errorMsg: "" }
//...

但以執行狀態跟操作行為作一個歸納,可以區分以下幾種回傳結果:
資料操作 HTTP Method 成功 錯誤/失敗
檢視(Read) GET 資料 錯誤/失敗訊息
新增(Create)
修改(Update)
刪除(Delete)
POST 成功訊息 錯誤/失敗訊息

從上面的歸納可以看出規律性,接著只要有方法可以傳送處理的狀態,以及能夠區分資料的種類,其實就單純很多,而 HTTP Status Codes 就是用來傳遞 HTTP 的處理狀態,如果利用這個方式來傳遞自訂的處理狀態,這樣 HTTP Content 就可以很單純傳遞資料,讓資料格式不受限於 JSON,還可以使用其他格式(text, xml, html),而且 XMLHttpRequest 本身就有處理 HTTP Status Codes 的能力,而 jQuery.ajax 也有提供 error status 的處理,所以可以利用這個來定義狀態的處理,在 HTTP Status Codes 有幾個已經定義狀態,很適合用來回傳處理狀態的資訊:

400 Bad Request 錯誤的請求 適用在表單內容的錯誤,如必填欄位未填、Email 格式錯誤
403 Forbidden 沒有權限,被禁止的 適用在沒有登入或權限不足
500 Internal Server Error 內部服務器錯誤 適用在程式的錯誤


jQuery 接收資訊的範例
$.ajax({
    type: "POST",
    url: document.location,
    success: function (data, textStatus, jqXHR) {
        alert(data);
    },
    error: function (jqXHR, textStatus, errorThrown) {
        alert(jqXHR.responseText);
    }
});


PHP 傳遞錯誤訊息的範例
if (php_sapi_name() == 'cgi'){
    header("Status: 400 Bad Request");
}else{
    header("HTTP/1.0 400 Bad Request");
}
exit("儲存失敗!!");


C# MVC 傳遞錯誤訊息的範例
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = 400;
return Content("儲存失敗!!");

CSS 選擇器權重表

!important style="" #id htmlTag .class
權重 10000 1000 100 10 1

Ex:
.title {} /* 1 */

div span.title {} /* 21 */

#product-list .title {} /* 101 */

使用 Stream 讀取 cUrl 下載結果

使用 stream 的好處就是用多少拿多少,不會因為資料大小而占用大量的記憶體。

$url = 'http://www.google.com.tw';

/* 建立接收的 Temp File Stream */
$tmpfile = tmpfile();


$curl = curl_init();

/* 指定下載的的 URL */
curl_setopt($curl, CURLOPT_URL, $url); 

/* 指定存放的 File Stream */
curl_setopt($curl, CURLOPT_FILE, $tmpfile);

/* 執行並取得狀態 */
$status = curl_exec($curl);
curl_close($curl);

if(!$status){ 
    fclose($tmpfile); 
    exit('error'); 
}


/* Temp File Stream 指標歸零 */
fseek($tmpfile, 0);

/*一次讀取一行*/
while (($line = fgets($tmpfile)) !== false) {
    var_dump($line);
}

/* 關閉 Stream */
fclose($tmpfile);

使用 stream 讀取指令列結果

$cmd = "find ./ -path './*/*/*'";

/* 執行指令,並取得 stream */
$fp = popen($cmd, "r");

/*一次讀取一行*/
while (($filePath = fgets($fp)) !== false) {
    $filePath = trim($filePath);
    var_dump($filePath);
}

/* 關閉 stream */
pclose($fp);
2014-02-21

[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

[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');

[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

[C#] 取得 Domain 的 IP 列表

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


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

[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

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

[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

[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

[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);
}

[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 排除的對象。
2014-01-30

[CSS] display inline, block 與 inline-block

inline 行內 (例如: <b>,<span>),具有以下特性:
  • 會連接在文字及 inline 元素之後
  • 不具有 width, height 以及上下的 padding, margin (padding-top, padding-bottom, margin-top, margin-bottom)
  • 寬度、高度會內縮至所含文字所使用的空間

block 區塊 (例如: <p>,<div>),具有以下特性:
  • 會獨佔父元素一整行的空間
  • 具有完整 width, height 及 padding, margin 屬性
  • 當不指定寬度時,預設寬度會擴張至一整列
  • 當不指定高度時,預設高度會內縮至子元素所使用的空間

inline-block 行內-區塊 (例如: <img>,<button>),同時具備 inline 與 block 的特性:
  • 會連接在文字及 inline 元素之後
  • 具有完整 width, height 及 padding, margin 屬性
  • 當不指定寬度、高度時,預設會內縮至所含文字所使用的空間

[CSS] Selector Meaning

SelectorMeaning
ABA and B
A, BA or B
A Bforeach B whose parents contains A
A > Bforeach B whose parent is A
A + Bforeach B whose previous sibling is A
A ~ Bforeach B whose previous siblings contains A

[C#] 圖片縮圖

System.Drawing namespace 提供對 GDI+ 基本繪圖功能的存取,可以方便處理圖片的操作,下面是一個處理圖片縮圖的範例:

//using System.Drawing;

var image = Image.FromFile(@"D:\001.jpg");

int width = 120, height = 100;
float targetRatio = (float)width / (float)height;
float imageRatio = (float)image.Width / (float)image.Height;


if(imageRatio < targetRatio)
{
    width = Math.Max(1, height * image.Width / image.Height);
}
else
{
    height = Math.Max(1, width * image.Height / image.Width); 
}


var thumbnail = image.GetThumbnailImage(width, height, null, IntPtr.Zero);

thumbnail.Save(@"D:\thumb.jpg");

這個方式可以容易的做到圖片縮圖,但使用這個方式會很佔用記憶體,在處理尺寸大的圖片時有可能會出現 out of memory,而且在 MSDN 有以下的警示:
Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components.
2014-01-21

[JavaScript] 匿名 function 進階應用

前幾天 JS 新手村的同事說看不懂下面的程式,下面用了幾個問句解釋了他的疑惑。
(function(model){
    //...
})(model || model={});

JS 要如何宣告一個 function 呢?
function a(){
    //...
}

那改成變數的方式宣告呢?
var a = function(){
    //...
};

要怎麼呼叫上面宣告的 function?
a();

再來把 a 換成等於右邊的 function 宣告。
function(){
    //...
}();

上面的寫法不符合語法,再加上 () 吧!
(function(){
    //...
})();


接著 model || model={} 是透過 or 判斷句的特性,遇到第一個為真就回傳,看看下面的例子:
var a = 1 || 2 || 3;  // 1
var a = 0 || 2 || 3;  // 2
var a = 0 || 0 || 3;  // 3



為什麼要用這種寫法呢??

首先 JS 沒有所謂的 block 變數,在 for 內宣告的變數,外面也能直接存取,唯一能侷限變數存取域的只有 function,在 function 內宣告的變數只有內部能存取,外部是看不見的,所以透過這個方法可以避免變數命名衝突的問題。

再來透過傳入 function 的變數,外部是無法在更動的,變數的參考點在呼叫 function 就固定下來,外部無法在異動內部所存取的參考點,這樣可以避免參考的變數內容被其他套件程式修改,而造成執行錯誤。

[Less] 簡單做到背景漸層

對選顏色不擅長,又想在網頁做出 CSS 漸層效果,Less 提供了兩個方便的函數 lighten(加亮顏色)、darken(加深顏色),透過這兩個函數就可以輕鬆產生漸層所需要的色差,然後調整百比值就可以控制漸層的色階。

.toolbar {
    @color: #914;
    @lighten: lighten(@color, 3%);
    @darken: darken(@color, 3%);

    background-color: @color;
    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@lighten), to(@darken));
    background-image: -webkit-linear-gradient(top, @lighten, @darken);
    background-image: -moz-linear-gradient(top, @lighten, @darken);
    background-image: -ms-linear-gradient(top, @lighten, @darken);
    background-image: -o-linear-gradient(top, @lighten, @darken);
    background-image: linear-gradient(to bottom, @lighten, @darken);
}

上面除了用 linear-gradient 來產生漸層,額外還加上了 background-color 這個保險,讓不支援 CSS3 的 browser 也能有基本的底色。


這樣寫還是有點麻煩,如果包成 mixin 會更方便,如下:
.bg-vertical-gradient(@color, @amount:3%) {
    @lighten: lighten(@color, @amount);
    @darken: darken(@color, @amount);

    background-color: @color;
    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@lighten), to(@darken));
    background-image: -webkit-linear-gradient(top, @lighten, @darken);
    background-image: -moz-linear-gradient(top, @lighten, @darken);
    background-image: -ms-linear-gradient(top, @lighten, @darken);
    background-image: -o-linear-gradient(top, @lighten, @darken);
    background-image: linear-gradient(to bottom, @lighten, @darken);
}

.toolbar {
    .bg-vertical-gradient(#914);
}

.banner {
    .bg-vertical-gradient(#702, 5%);
}