2013年12月26日 星期四

【jQuery】keyup event v.s. keypress event

目的:檔案命名時,限制某些字元無法輸入!

限制字元: \ / | ? :

在此種情況下,使用 keypress 事件較為合適
keydown event 會判斷是鍵盤上哪個鍵被按下,
而 keypress event 則會判斷是哪個字元被輸入
舉例來說,輸入小寫字元 "a",
keypress 事件會回傳 97
keydown 事件回傳 65
輸入大寫字元 "A",
keypress 事件回傳 65
keydown 事件回傳 65
由上例可知, keypress 可以準確分辨大寫 "A" 與小寫 "a",
而 keydwon 則因為回傳皆為 65,無法辨識輸入字元為大寫 "A" 或是 小寫 "a"
但 keydown event 能夠偵測某些特殊按鍵,像是 Shift 或是 方向鍵

在本例中,因為希望限制 : 輸入,而 ; 則可以正常輸入
使用 keydown event,則 event.which 值皆為 186
使用 keypress event, ; reported as 59, : reported as 58
所以 keypress event 會較符合此次限制檔案命名需求!

2013年12月2日 星期一

【jQuery】Trigger keyup event

需求:刪除檔案可以點擊"刪除"圖示或是按下鍵盤上"DEL"按鍵

實作:因為兩個方式都是實作刪除檔案,所以可以先寫好其中一個 function,
另一個就直接觸發該 function 即可。

想法:先實作出使用"DEL"鍵刪除;點擊 Icon 則模擬鍵盤事件即可。

$('item').keyup(function(event) {
    if (event.which == 46) {
        // to do delete file
    }
});

$('.icon').click({
    var e = $.Event('keyup');
    e.which = 46; // Delete
    $('item').trigger(e);
});

另外要注意的是,一般 html tag (ex:div) 需要加上 tabindex 屬性才可以觸發鍵盤事件
而 input tag 則不需要額外加上 tabindex 屬性

2013年11月29日 星期五

【Javascript】XMLHttpRequest 解決 Cross Domain 檔案下載

需求為 Frontend.js 與 實際檔案放置在不同主機上,
所以需要解決 Cross Domain 資料存取的限制。

解決辦法為採用 XMLHttpRequest 物件來突破跨區存取限制。
直接看 Code
downloadFile = function( _fileUrl ) {
    var xhr = new XMLHttpRequest();

    if ("withCredentials" in xhr) {
        xhr.open('GET', _fileUrl, true);
    }    
    else if (typeof XDomainRequest != "undefined") {
        xhr = new XDomainRequest();
        xhr.open('GET', _fileUrl );
    }
        
    xhr.responseType = "blob";
    xhr.onreadystatechange = function () { 
        if (xhr.readyState == 4) {
            var a = document.createElement('a');
            a.href = window.URL.createObjectURL(xhr.response);
            a.download = fileName;
            a.click();
        }
    };

    xhr.send();
}

downloadFile 這個函式接收一個參數 _fileUrl 指出檔案路徑
var xhr = new XMLHttpRequest() 建立一個 xhr 物件
withCredentials這個屬性預設為false,
因為 XMLHTTPRequest2 物件才允許CORS(Cross-Origin Resource Sharing),
所以檢查是否有支援withCredentials屬性,
該屬性指示是否使用如cookie或授權標頭檔等憑證進行跨站存取控制(cross-site Access-Control)請求。

而IE 8與IE 9只能使用 XDomainRequest 物件處理 CORS 請求(IE8,9不支援XHR2)
responseType 屬性指定回應狀態,
可以為 blob, json, text 等等,
為了方便後續創造連結,所以指定回應狀態為"blob"
onreadystatechange 這個函式會在 readyState 屬性改變時被呼叫
而 readyState 屬性值為 4 時,表示作業完成。
此時便可以創造一個元素 a,並且呼叫 createObjectURL 方法賦予 href 屬性值,
a.download可以為下載檔案命名,最後呼叫 click 方法完成檔案下載。

還有一件事情須處理,存放檔案的主機要設定允許跨領域存取的權限
後端採用 Node.js 與 Express framework 進行開發
app.configure(function(){ 
  app.use(function(req, res, next) {
      res.header('Access-Control-Allow-Origin', "*");
      res.header('Access-Control-Allow-Credentials', true);
      res.header('Access-Control-Allow-Methods', "GET");
      next();
  });
});

簡單講要幫 response 設定允許跨領域存取的 Header,
'Access-Control-Allow-Origin' 設定允許的請求來源
'Access-Control-Allow-Methods'設定允許的方法,如 GET, POST 等
如果前端 xhr.setRequestHeader 有自行定義 Header 的話
那 Server 這邊也要多加 'Access-Control-Allow-Headers' 權限
res.header('Access-Control-Allow-Headers', 'X-Requested-With');

就允許帶有 header 為 'X-Requested-With' 的請求。
但要注意的是,因為安全性的關係,XDomainRequest不支援客製化 Header
也限制 Method only GET or POST,Protocol only HTTP or HTTPS
還好IE 10開始支援 XMLHTTPRequest2

2013年11月20日 星期三

【jQuery】hide() 與 fadeOut()

jQuery 的兩個動畫函式 hide() 與 fadeOut() 都具有隱藏效果。

需求是當游標滑入指定區域時,浮現該按鈕,點擊按鈕後可使按鈕180度旋轉。

實現旋轉方式是設定 CSS 的 transform 值, rotate( '旋轉度數' deg )。

若使用 hide() 與 show() 來實作隱藏與浮現效果,
元素隱藏之前的旋轉度數會被保留。

但使用 fadeOut() 與 fadeIn() 來實作的話,
旋轉度數的 CSS 效果會消失。

探究其原因,在於 fadeOut 與 fadeIn 會重寫 DOM element 的 style,
導致原本 style 內的 transform 值消失,因此無法保留旋轉度數。

順便紀錄一下觀察到的 hide() 與 fadeOut() 差異,
相同之處:
  1. display值,設為none
  2. opacity值,變更為0

相異之處:
  1. hide() 會將 width 與 height 的值也變更為0,fadeOut() 不改變
  2. hide() 會保留 style 內的 transform 值

2013年11月12日 星期二

【Node.js】Create Http Server

自己以前寫網站習慣,前端使用JavaScript(or jQuery),後端使用PHP,使用Apache建立Server。

而Node.js則打破此框架,簡單講就是,伺服器端的JavaScript! 

為了實現此概念,Node.js借助Google V8引擎在後端運行JavaScript。

想從建立一個基礎Http Server入門,範例code包含三個觀念,使用模組、函數傳遞以及回呼(callback)。
var http = require("http");

function onRequest(request, response) {
  console.log("Request received.");
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.write("Hello World");
  response.end();
}

http.createServer(onRequest).listen(8888);

console.log("Server has started.");
第一行code很簡單,呼叫Node.js內建http模組並存入http變數。

接下來寫一個onRequest函數處理http的請求,這邊我們可以看到http使用createServer方法時,onRequest這個函數被當成是參數來傳遞,此即所謂的函數傳遞。

而Server監聽8888 port同時等待HTTP的請求,當有請求發生時,執行onRequest函式,此即所謂callback。
callback:給甲方法傳遞乙函式,等到對應事件發生時,才執行乙函式。
以上述code來看的話→
甲方法 ─ createServer
乙函式 ─ onRequest
對應事件 ─ HTTP的請求
補充一下,乙函式也很常使用匿名函式。

最後一行code會在命令行上輸出"Server has started.",幫助我們測試程式流程與了解事件的觸發順序,此程式流程為非同步流程,因為http.createServer後,程式會繼續執行,等到有http請求才去執行onRequest函式。

當我們執行腳本後

當我們在瀏覽器輸入localhost:8888後

最後回到命令行會看到
當我們使用瀏覽器讀取網頁時,我們的伺服器有可能會輸出兩次"Request received."。原因為何?我們可以修改code增加解析路徑的功能。

修改code如下
var http = require("http"),
    url = require("url");

function start() {
    function onRequest(request, response) {
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello Node.js");
        response.end();
    }
    
    http.createServer(onRequest).listen(8888);
    console.log("Server has started.");
}

exports.start = start;

使用Node.js內建url模組,並呼叫其parse方法幫我們解析request.url,
即分析HTTP的請求路徑為何。

腳本編譯完成後,我們一樣開啟browser輸入localhost:8888
然後回到命令列的地方觀看log訊息

觀看log訊息可以知道請求路徑。由此得知,當我們在存取http://localhost:8888的當下, 伺服器也嘗試存取 http://localhost:8888/favicon.ico 。 因此我們上一段code,log訊息才會出現兩次"Request received."。

2013年11月5日 星期二

【jQuery】將桌面檔案拖曳後上傳

想要將桌面的檔案使用拖曳的方式
拉到指定的區域
就可以啟動檔案上傳的功能

作法為

  1. 改寫dragover與dragenter兩個事件,取消這兩個事件的預設效果(主要為over事件,先取消over事件的效果,否則會無法觸發drop事件)
  2. 將檔案上傳功能寫進drop事件即可完成此項功能

先來看第一步的code
        
        $("div.content").on(
          'dragover',
          function(event) {
            event.preventDefault();
            event.stopPropagation();
          }
        );
        
        $("div.content").on(
          'dragenter',
          function(event) {
            event.preventDefault();
            event.stopPropagation();
          }
        );

接下來改寫drop事件
        
        $("div.content").on(
            'drop',
            function(event){
                if(event.originalEvent.dataTransfer){
                    if(event.originalEvent.dataTransfer.files.length) {
                        event.preventDefault();
                        event.stopPropagation();
                        /* Write Upload Function Here */
                        fileUpload();
                    }   
                }
            }
        );

2013年9月27日 星期五

【Javascript】basic callback

最近上班學習使用node.js這個framework開發Web Application
node.js使用 V8 Javascript Engine
Javascript是一個很自由的語言
可以是 Object Oriented 形式或是 functional 形式
若以事件驅動的概念來寫 Javascript
我覺得callback是最基本的觀念與核心

假設寫了兩個事件,一個是煮水餃事件,另一個是吃水餃事件。
在現實當中,水餃煮熟了才能開始吃,而煮水餃這件事情需要一些時間。
因此在程式邏輯上
會希望吃水餃這個事件等煮水餃事件完成以後才發生
先看一下不使用 callback 的情形
function do_a(){
 console.log("'do_a':開始煮水餃");
 setTimeout( function(){
  console.log("'do_a':水餃煮熟了");
 }, 1000 );
}

function do_b(){
 console.log("'do_b':吃水餃");
}

do_a();
do_b();


會發現雖然do_a()在do_b()前面執行
但因為do_a()執行需要一段時間
所以就會發生水餃還沒煮熟就開始吃了 XD

再來看看使用 callback 的解決辦法
function do_a( callback ){
 console.log("'do_a':開始煮水餃");
 setTimeout( function(){
  console.log("'do_a':水餃煮熟了");
  callback && callback();
 }, 1000 );
}

function do_b(){
 console.log("'do_b':吃水餃");
}

do_a( function(){
 do_b();
} );
使用 callback 寫法的話
就可以確保do_b()在do_a()完成後才被執行
這是使用 callback 最基本的寫法
但 callback 實際上在使用都會帶參數
了解 callback 後,才有辦法學習 node.js 的 events

2013年1月1日 星期二

【jQuery UI】使用drap與drop實作sortable

最近在研究jQuert UI,功能很強大且程式碼很簡潔
研究的重點在於利用拖曳的功能作出圖片的排序
使用draggable來使DOM物件能夠被拖曳
使用droppable來讓DOM物件作為容器
HTML當中以block為容器而img為拖曳物件
先來看一下HTML有哪些目標容器與拖曳物件
    


第一個要注意的地方在於:如何得知drag物件是從哪個block拖曳到另一個block
我是採用drop的事件out與drop來判斷
但要特別注意out event如果拖曳有經過別的容器也會觸發
所以增加一個變數flag來判斷第一次out event的source block

第二個要注意的地方在於:要特別注意事件被觸發的順序
若事件為draggable start與stop以及droppable out與drop
根據我的理解,觸發順序為start→out→drop→stop
另外特別注意droppable事件over與out,我發現在預設的情況下
有可能只觸發over事件,而尚未觸發out事件
因為沒注意到此情況,導致某些情況下拖曳效果會失敗,產生嚴重瑕疵,不可不慎

第三個要注意的地方在於:因為我是用append來使拖曳物件移動到目標容器
但這樣產生的問題是,會被判斷成拖曳失敗而發生revert動畫
後來發現原因是append使拖曳物件到達目標容器後位移效果並沒有消失
可以用css( 'left', '0' )來取消位移
這樣就可以防止拖曳失敗而產生revert動畫

以上三點我覺得最為重要!

下面提幾個小細節的地方:
$(".line")用來顯示定位線,而且要判斷drag物件的out與drop block是左移還右移
為了維持定位線跑的感覺,當觸發over event處理定位線位置
$(".line").addClass('line_clear');先清除定位線再重新繪製
例如:block4→block1,那輔助線就要在block左邊;
反之,則要在block右邊

若要使拖曳物件永遠在top
我的作法是觸發start事件時將拖曳物件的zIndex升高
stop事件則將拖曳物件的zIndex歸零