顯示具有 JQuery 標籤的文章。 顯示所有文章
顯示具有 JQuery 標籤的文章。 顯示所有文章

2022年11月27日 星期日

練習自製簡單的 Javascript Promise 物件

繼  jQuery的Callbacks 及 jQuery之Deferred的原理(參看源碼) 兩篇文後,
今天想以自己的想法參考兩篇文來自製一個簡單的、API 介面仿照
Javascript 原生 Promise 物件的 MyPromise 物件,
主要是希望能在自己實作的過程中,能夠更理解對程式的邏輯及設計思路。

在實作之前,首先要來簡單的介紹 Javascript 原生的 Promise:
Promise 的功能及性質類似 JQuery 的 $.Deferred() 及 Angularjs 的 $q 等,
在早期瀏覽器還沒有實作 Promise 物件時,JQuery 就已經自己實作了 Deferred ,
可以用來方便地管理非同步異步操作。

建立 Promise 物件的方法是 new 關鍵字,例如:

var promise = new Promise(function(resolve, reject){
    setTimeout(function(){
    	var resolvedData = 1;
    	resolve(resolvedData);
    }, 1000);
});

Promise 的建構式 (constructor) 接受一個函式,函式可以得到兩個各叫做 resolve 和 reject 的函式,
當異步操作完成後,可以執行 resolve(resolvedData) 函式,並可以送入一個想要傳遞給下一個異步操作的 resovledData 資料作參數。

當 resolve() 被執行後,可以以串接的方式用 then() 函式來串接下一個想要執行的異步操作,
例如:

var promise = new Promise(function(resolve, reject){
	setTimeout(function(){
		console.log(1);
		resolve(2);
	}, 3000);
})
.then(function(resolvedData){
	setTimeout(function(){
		console.log(resolvedData);
		resolve(3);
	}, 2000);
})
.then(function(resolvedData){
	setTimeout(function(){
		console.log(resolvedData);
		resolve();
	}, 1000);
});

上面的例子會在 3 秒後在 console 印出 1,之後再 2 秒印出2,再 1 秒印出3

原生的 Promise 實際上瀏覽器是如何實作它們的我並不清楚,
不過在這篇文中我嘗試試著以自己的想法模擬實現部份的介面功能來作為練習,
主要實現的部份有:

  1. 自己實作的 Class 名稱名命為 MyPromise
  2. 能夠以
    new Promise(function xxxFunction(resolve){
    	//do somthing
    	resolve(resolvedData);
    }).then(function(resolvedData){
    	//do somthing
    	return new Promise(function(resolve){
    		//do somthing
    	});
    }).then(function(resolvedData){
    	//do somthing
    	return new Promise(function(resolve){
    		//do somthing
    	});
    })
    的方式建立 Promise 的物件,並以傳入 xxxFunction(resolve) 的 resolve 函式來 resolve Promise,其中可以傳入 resolvedData 資料到下一個 Promise 中,下一個 Promise 用 then(fujction (rsolvedData)) 來接收 resolvedDatat 並可以繼續回傳新的 Promise 來以同樣的方式用 resolve 串接到下個 Promise。
  3. 只實現 resolve 相關的部分,忽略了 reject 的部份,也省略了一些如 Promise.all, Promise.race 等的函式實作。
  4. 實現方式參考了 JQuery 的 Deferred 物件實作思路,使用一個佇列列隊 (queue) 的方式來實現,其中:
    resolve(resolvedData) 相當於是執行佇列中的所有函式,並且之後有函式被添加至佇列時也會馬上被執行,resovledData 會被儲存起來供要執行佇列中的函式時作為輸入用。
    then() 被執行時會回傳一個 Promise,這裡暫且稱為 Promise2 ,
    Promise2 可以繼續地用 then() 往下串接。
    then() 的行為是把要執行的函式添加至佇列中,
    這裡暫稱要執行的函式為 thenFun(),
    thenFunc() 被執行後會回傳另一個 Promise,這裡暫且稱為 Promise3,
    我們會把 Promise2 的 resolve() 函式加進 Promise3 的佇列中,
    這樣在 Promise3 在 thenFunc() 被執行 reolsve() 後,就會執行被放在 Promise3 佇列中的 Promise2 的 resolve(),
    進而讓設定在 Promise2 佇列中的函式被執行。
下面是實現的程式碼和使用的範例:
function MyPromise(callbackFunc){
  var promise = this;
  
  promise.status = "pending"; //pending, resolved
  promise.callbackQueue = [];
  promise.resolvedData = null;
  
  promise.addCallbackFuncToQueue = function(callbackFunc){
  	promise.callbackQueue.push(callbackFunc);
    if (promise.status == "resolved"){
    	promise.fireCallbackFuncInQueue();
    }
  }

  promise.resolve = function(resolvedData){
  	if (promise.status != "resolved"){
      promise.resolvedData = resolvedData;
      promise.status = "resolved";
      promise.fireCallbackFuncInQueue();
    }
  }
  
  promise.fireCallbackFuncInQueue = function(){
    while(promise.callbackQueue.length > 0){
      var callbackFuncToFire = promise.callbackQueue.shift();
      callbackFuncToFire.call(promise, promise.resolvedData);
    }
  }
  
  promise.then = function(thenCallbackFunc){
  	return new MyPromise(function(resolve){
    	promise.addCallbackFuncToQueue(function(data){
      	var returnedPromiseFromThenCallbackFunc = thenCallbackFunc.call(promise, data);
        returnedPromiseFromThenCallbackFunc.addCallbackFuncToQueue(resolve);
      });
    });
  }

  callbackFunc.call(promise, this.resolve);
}
/////////////////////////////////////////////////
new MyPromise(function(resolve){
	console.log(1);
  setTimeout(function(){
  	resolve(2);
  }, 2000);
})
.then(function(data){	
  return new MyPromise(function(resolve){
  	console.log(data);
  	setTimeout(function(){
  	resolve(3);
  }, 1000);
  });
})
.then(function(data){	
  return new MyPromise(function(resolve){
  	console.log(data);
  	resolve();
  });
});
JSFiddle 的線上範例:


參考資料:

2021年1月11日 星期一

實作能偵測 textarea 內容並自動調整高度的 angularjs directive

實作了能偵測 textarea 內容並自動調整高度的 angularjs directive。

當 <textarea> 裡的文字變多需要高多行的 textarea 時,即需要更高的 textrea 時,

可以自動的調高高度,

而當文字變少時也能自動減少高度。

Note:

因為有使用到一些 jQuery 的 function,例如 innerHeight,所有需要先 include jQuery

直接上程式碼及範例:

html:
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>

This &lt;textarea&gt; will change its height to contain content.
<div ng-app="app" ng-controller="controller as ctrl">
  <textarea textarea-auto-resizer></textarea>
</div>

javascript:
angular.module("app", [])
  .controller("controller", ["$scope", function($scope) {
  	var self = this;
    self.text = "long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words - long words"
  }])
  .directive('textareaAutoResizer', ["$sce", function($sce) {
    return {
      link: function(scope, element, attrs, ngModel) {
        autoResizeTextArea();

        scope.$watch(attrs.ngModel, function(newValue, oldValue) {
        	autoResizeTextArea();
        });
        
        element.on("input", autoResizeTextArea);

        function autoResizeTextArea(event) {
          var dom = angular.element(element);

          dom.css({
            height: ""
          });
          if (dom.prop("scrollHeight") > dom.innerHeight()) {
            //set dom height(without padding part) to scrollHeight - padding part
            dom.height(dom.prop("scrollHeight") - (dom.innerHeight() - dom.height()));
          }
        }
      }
    };
  }]);


參考:

  1. Creating a Directive that Adds Event Listeners

2019年9月23日 星期一

使用RxJs 實作 Hover 的下拉選單 - RxJs

RxJs 是一個能幫助處理Javascript 各種事件(event)的好工具,
尤其是要偵測的事件是一組事件,而不是單一的事件時會非常的好用,

今天要來利用 RxJs 來實作 Hover 的下來選單,
成品如下:

首先在螢幕上方有一個 "Menu" 區塊,
在 "Menu" 區塊中有一個 "Menu Child" 區塊。

當滑鼠移到 "Menu" 區塊時,下方會滑出一塊 "Dropdown" 區塊,
裡面有一個 "Dropdown Child" 區塊。

當滑鼠移出 "Menu" 區塊及 "Dropdown" ,稍微等一下 (例如 100 毫秒),
如果此時滑鼠不在 "Menu" 區塊或 "Dropdown" 區塊,就將 "Dropdown" 區塊上來關掉,
不然就不做任何事 (即 "Dropdown" 區塊繼續維持下拉開著)。

在這裡我們有一個一組的事件需要處理,就是 :

滑鼠移出  "Menu" 區塊及 "Dropdown" --> 等一下 -->  滑鼠是否在 "Menu" 區塊或 "Dropdown" 區塊上

如果使用純 Javascript,我們可能會需要使用綁定 mouseenter, mouseleave 並配合 setTimeout 來處理,
也就是在 mouseenter 上做紀錄是否滑鼠移動進 "Menu" 或 "Dropdown" 區塊,
並在 mouseleave 後,用setTimeout 等一下讓 mouseenter 觸發上述紀錄的程式 (因為 mouseleave 會比 mouseenter先提早觸發) ,
等 mouseenter 紀錄完後在判斷要不要關掉 "Dropdown" 區塊。

但使用 RxJS 的話,將會使程式碼簡化許多。

先來看完整的程式碼:
使用的工具:
RxJS : 5.0.1 (下載)
jQuery : 1.9.1

HTML:
<div class="wrapper">
  <div class="menu">
    Menu
    <div class="menu_child">
      Menu Child
    </div>
  </div>
  <div class="dropdown" style="display:none;">
    Dropdown
    <div class="dropdown_child">
      Dropdown Child
    </div>
  </div>
</div>

CSS :
.wrapper {
  width: 500px;
  margin: 0 auto;
}

.menu {
  background-color: yellow;
}

.menu_child {
  background-color: #00ffd0;
  width: 300px;
  margin: 0 auto;
}

.dropdown {
  background-color: green;
  height: 300px;
}

.dropdown_child {
  background-color: #bc0eda;
  width: 300px;
  margin: 0 auto;
}

Javascript :
var $menu = $(".menu");
var $menu = $(".menu");
var $dropdown = $(".dropdown");

$menu.on("mouseenter", function() {
  $dropdown.slideDown();
});

var $menuMouseLeaveEvent = Rx.Observable.fromEvent($menu[0], "mouseleave");
var $dropdownMouseLeaveEvent = Rx.Observable.fromEvent($dropdown[0], "mouseleave");
var $menuMouseEnterEvent = Rx.Observable.fromEvent($menu[0], "mouseenter")
var $dropdownMouseEnterEvent = Rx.Observable.fromEvent($dropdown[0], "mouseenter")

var mouseLeaveEvent = Rx.Observable.merge($menuMouseLeaveEvent, $dropdownMouseLeaveEvent);
var mouseEnterEvent = Rx.Observable.merge($menuMouseEnterEvent, $dropdownMouseEnterEvent);

mouseLeaveEvent.map(function(e){
 return mouseEnterEvent.buffer(Rx.Observable.timer(100));
})
.concatAll()
.subscribe({
  next: function(value) {
   //if value.length > 0, it means one or more than one mouseEnterEvent 
    // was observed during 100 ms.
    if (value.length > 0) {
      $dropdown.slideDown();
    } else {
      $dropdown.slideUp();
    }
  }
});

說明:

  1. mouseEnterEvent.buffer(Rx.Observable.timer(100));
    的效果是,在一定時間後 (這裡是 100 毫秒),
    將這段時間中觀察到的 event 丟出,在此例中,就是 mouseEnterEvent。
  2. 使用 mouseenter 和 mouseleave 而不使用 mouseover 和 mouseout 事件,
    這樣才會不在滑鼠在"Menu", "Dropdown" 的子層元素 (Child DOM ) 進出時觸發了mouseover和mouseout事件。

--------------------------------------------------------------------------------------------------------------------------

改良版:
參考 Build dropdown list with RxJS 了這篇文章以後,
非常喜歡他的思考方式,
跟我之前單純用事件去想的方式不同,
他採用了狀態的概念,下面說明:

使用狀態來表示滑鼠目前是在 Menu 還是 Dropdown 上面,
這邊先用 isOnMenu 來表示是否在 Menu 上的狀態,
用 isOnDropdown 來表示是否在 Dropdown 上的狀態,
isOnMenu叨和 isOnDropdown 的值都為布林值 (boolean),即 true 或 false。

如果我們能正確的得到 isOnMenu 和 isOnDropdown 的值,
那我們就可以歸納出如下的結果:
isOnMenu = true isOnMenu = false
isOnDropdown = true Dropdown 要打開 Dropdown 要打開
isOnDropdown = false Dropdown 要打開 Dropdown 不要打開

可以從上表看出,只有在 isOnMenu 和 isOnDropdown 都為 false 的時候,
Dropdown 才不需要被打開。

接著就是如何正確地得到 isOnMenu 和 isOnDropdown 的值。
以 isOnMenu 為例 (isOnDropdown 同理),
方式就是用 RxJS 偵測滑鼠對於 Menu 的
mouseenter 和 mouseleave 事件,將事件 map 成我們要的資訊。

例如 map 成一個物件 Object,
其中存了 isOnMenu 的布林值
和一些其他資訊 (例如在有多個 Menu 的情況,滑鼠是 hover 了哪個 Menu 的訊息),
並且把兩個事件流結合 (Merge) 起來,取最後得到的事件 (即 Menu 的最後狀態)。

這樣我們就可以根據最後得到的 isOnMenu 和 isOnDropdown 狀態 (用 CombineLatest 來 combine isOnMenu 和 isOnDropdown 事件流) 來決定是否要開關 Dropdown 了。

以下直接以實作例子來舉例,
先看在 jsFiddle 上的成品如下:


情境為畫面上有兩個 (或多個) Menu 及各所屬的 Dropdown,

Html:
<div class="wrapper">
  <div class="menu" targetDropdown="dropdown1">
    Menu1
    <div class="menu_child">
      Menu1 Child
    </div>
  </div>
  <div class="menu" targetDropdown="dropdown2">
    Menu2
    <div class="menu_child">
      Menu2 Child
    </div>
  </div>
  
  <div class="dropdown" id="dropdown1">
    Dropdown1
    <div class="dropdown_child">
      Dropdown1 Child
    </div>
  </div>
  <div class="dropdown" id="dropdown2">
    Dropdown2
    <div class="dropdown_child">
      Dropdown2 Child
    </div>
  </div>
</div>


CSS:
.wrapper {
  width: 500px;
  margin: 0 auto;
}

.menu {
  background-color: yellow;
  display: inline-block;
  width: 45%;
  margin: 0;
}

.menu_child {
  background-color: #00ffd0;
  width: 30%;
  margin: 0 auto;
}

.dropdown {
  background-color: green;
  width: 80%;
  height: 0px;
  overflow: hidden;
  position: absolute;
}

.dropdown_child {
  background-color: #bc0eda;
  width: 300px;
  margin: 0 auto;
}

Javascript 程式碼如下,已將說明寫在程式碼之中:
//取得 Menu 及 Dropdown 的 DOM
var $menu = $(".menu");
var $dropdown = $(".dropdown");

// 偵測 Menu 及 Dropdown 的 mouseenter 和 mouseleave 事件
var $menuMouseLeaveEvent = Rx.Observable.fromEvent($menu, "mouseleave");
var $dropdownMouseLeaveEvent = Rx.Observable.fromEvent($dropdown, "mouseleave");
var $menuMouseEnterEvent = Rx.Observable.fromEvent($menu, "mouseenter");
var $dropdownMouseEnterEvent = Rx.Observable.fromEvent($dropdown, "mouseenter");

//將 Menu 的 mouseenter 和 mouseleave 事件轉成對應的資訊物件 
//(這裡儲存了滑鼠有無在 Menu 上面、滑鼠滑進 (或滑出) 了哪個 Menu DOM 的資訊),
//並將 mouseenter 和 mouseleave 事件 merge 起來,只要看最後的狀態就好
var isMouseOnMenuEvent = Rx.Observable.merge(
  $menuMouseEnterEvent.map(function(e) {
    return {
    	isHovered : true,
      hoveredDom : e.currentTarget
    };
  }),
  $menuMouseLeaveEvent.map(function(e) {
    return {
    	isHovered : false,
      hoveredDom : e.currentTarget
    };
  }));

//將 Dropdown 的 mouseenter 和 mouseleave 事件轉成對應的資訊物件 
//(這裡儲存了滑鼠有無在 Dropdown 上面、滑鼠滑進 (或滑出) 了哪個 Dropdown DOM 的資訊)
//並將 mouseenter 和 mouseleave 事件 merge 起來,只要看最後的狀態就好
var isMouseOnDropDownEvent = Rx.Observable.merge(
  $dropdownMouseEnterEvent.map(function() {
    return {
    	isHovered : true
    };
  }),
  $dropdownMouseLeaveEvent.map(function() {
    return {
    	isHovered :false
    };
  })).startWith({ 
    // 因為 RxJS 的 combineLatest() 不能 combine NULL 的事件 (會出錯),
    // 而一開始 Dropdown 沒有被打開時,不會有 Dropdown 的 mouseenter 和 mouseleave 事件,
    // 所以在這邊用 startWith 來手動增加一個 event 在最前面
  	isHovered :false
	});

// 將 isMouseOnMenuEvent 和 isMouseOnDropDownEvent 兩個事件流 Combine 在一起,
// 只看各自的最後一個狀態。
// 因為可能存在在短時間中,事件還沒有被觸發的情況,在這之中的狀態會不準而不對,
// 例如: 
// 滑鼠滑出了 Menu,要準備滑進 Dropdown 但還沒滑進的這一段時間中,
// 此時 isOnMenu = false,但 isDropdown 也等於 false,這時用這組狀態判斷 Dropdown 的開關就會不正確,
// 所以在這使用了 debounceTime() 來延長事件偵測的時間,例如滑鼠移出 Menu 後,等 100 毫秒看有沒有其他事件再決定送出事件
var mouseHoverStatus = Rx.Observable.combineLatest(isMouseOnMenuEvent, isMouseOnDropDownEvent)
                        .debounceTime(100);

// 在註冊事件 (subscribe) 裡判斷狀態,決定是否要開關 Dropdown、和要開關哪個 Dropdown
mouseHoverStatus.subscribe(function(e){
	if (e[0].isHovered || e[1].isHovered){
    var targetDropdown = $("#" + $(e[0].hoveredDom).attr("targetDropdown"));
    
    $dropdown.not(targetDropdown).stop(true).animate({
    	height: 0
    });
    
  	targetDropdown.stop(true)
    .animate({
    	height: 300
    });
  }else {
  	$dropdown.stop(true).animate({
    	height: 0
    });
  }
});



參考資料:

  1. 30 天精通 RxJS (01):認識 RxJS
  2. Element: mouseenter event
  3. Build dropdown list with RxJS

2017年4月29日 星期六

Instagram access token 的取得

今天要介紹的是如何取得Instagram的Access Token,及
用Access Token來取得使用者的資訊,如發佈的貼文(Instagram的貼文都是圖片或影片)圖片url等。

今天的需求是:

  1. 想要取得自己在Instagram上面的貼文圖片。
  2. 想要將取得的貼文圖片展示在網頁上。

使用技術:

  1. JSP
  2. Jquery

先講Instagram 官方步驟

  1. 一開始如果沒有Instagram帳號,請註冊一個。
  2. 進入Instagram的developer console,填好相關資訊註冊成為Developer。
  3. 自己建立一個網頁,假設網址為REDIRECT-URI
  4. Manage Clients建立一個App,其中"Valid redirect URIs"填上步驟3的REDIRECT-URI
  5. 進入
    https://api.instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=code
    其中 CLIENT-ID 為已建立之App的 CLIENT-ID , REDIRECT-URI 即為自己建立網頁的網址。
  6. 待使用者登入並授權以後,會被引導至 REDIRECT-URI 頁面,並會以GET方式在Url後面帶上一個 code 參數。
  7. 這個 code 只能用一次,
    以以下參數
    client_id : App的client_id
    client_secret : App的client_secret
    grant_type : "authorization_code"
    redirect_uri : App的redirect_uri
    code : 剛剛得到的code
    請求 https://api.instagram.com/oauth/access_token,
    成功的話會在Response中得到access_token的參數,即我們要PO文資訊時需要的access_token。
  8. 要到access_token以後,就可以用來要各種使用者的資料了,在Developer console的左邊"Endpoints"中有提供各種Endpoints,這邊我們使用下面這個就好:
    https://api.instagram.com/v1/users/self/media/recent/?access_token=ACCESS_TOKEN
    請在ACCESS_TOKEN填上剛要到的,代表使用者的ACCESS_TOKEN,成功的話會回傳使用者最近的照片PO文資料。

下面以實測的程式碼來演示,

2016年12月12日 星期一

JQuery Ajax傳遞陣列時參數名不要帶中括弧的方法

JQuery的Ajax如果使用POST的話,似乎在1.4+版本後都會把陣列的參數名加上中括弧,例如以下:
$.ajax({
 method: "POST",
 url: "/xxx.do",
 data: {
    arrayParameter : ['1','2','3','4','5']         
 }
});

預設arrayParameter會以arrayParameter[]的參數名,值為1,2,3,4,5的方式傳進url指定的後台,例如果用Servlet去接的話就要用
String[] arrayParameter = request.getParameterValues("arrayParameter[]");
才能接到。
為了能夠讓JQuery傳的陣列參數不要帶上中括弧,必須指定ajax的traditional為true,例如:

$.ajax({
 traditional: true,
 method: "POST",
 url: "/xxx.do",
 data: {
    arrayParameter : ['1','2','3','4','5']         
 }
});

或是設定全局設定,讓每個ajax都能使用traditional的參數傳遞方式:
$.ajaxSetup({traditional: true});

參考資料:

  1. How do I remove the square brackets at the end of a JS variable name during AJAX calls?

2016年9月17日 星期六

typescript + SystemJS + npm + jQuery (Netbeans, Eclipse)

這篇主要紀錄在Netbeans和Eclipse中使用Typescript,並配合SystemJS的範例,再來我們要利用npm來安裝JQuery並用SystemJS引入JQuery模組,學習建立自己的javascript module。

這篇要實現的需求如下:
  1. 在Netbeans和Eclipse中使用typescript外掛編譯我們的ts檔成js檔。
  2. 使用npm安裝JQuery。
  3. 使用SystemJS引入JQuery模組並使用。
  4. 建立一個自製的JS module, Greeter,配製好相應的package.json和export設定,放到node_modules中。
  5. import自制的Greeter,並建立一個myGreeter去entend(繼承)Greeter擴充功能。
以下是我們完成的目錄結構,需注意到的是,藍色框線標示的js及map檔是ts檔被typescript編譯出來的檔案:



接下來講解步驟:

2016年9月10日 星期六

使用raycaster實現Object的選取 - three.js

three.js可以在Scene放置許多的Object3D,但如果我們想要讓User跟那些Obejct做互動,那要如何做呢,例如User在Camera的畫面中看到許多的物件,想用滑鼠去點擊那些物件去做互動。

在three.js中,有一個Raycaster類可以做到上述需求,它的原裡是,想像有一條光束(ray)從Camera畫面中滑鼠指的位置,以垂直於Camera的X-Y平面的方式向Camera的負Z方向射出,並且計算在Scene的children中有哪些物件被這條線射中(ray可以穿過所有Object3D)。 

在這裡,我要來示範一個使用three.js的Raycaster的範例,需求是這樣的:

需求:
  1. 畫面上有許多的Object3D物件,有各自的顏色。
  2. 當滑鼠指到(hover)某一個Object時,將指到的第一個Object變成紅色(即它後面的Object不會變色)。
  3. 滑鼠移開後,剛剛指到但現在沒指的Object變回原來的顏色。
接下來一步步的講解每一個步驟:

2016年8月20日 星期六

使用Ajax上傳檔案 - (Javascript, JQuery or AngularJS) + FormData

在以前我們要在網頁中傳送檔案資料時,通常會需要設計一個html Form,並設定Form的encype=multipart/form-data和準備一個input type="file",在User按下submit按鈕後,跟據Form
所設定的action="URL",將整個頁面request移動到action所指定的地方,例如Servlet,
等處理完後在將User導至其他網頁頁面。

但有沒有可能不要讓User被導到其他頁面,留在原頁面就完成檔案資訊的傳送呢?
答案是可以的,這邊就要利用新的javascript類別FormData和Ajax的技術來達到Ajax傳
送檔案(也可順便傳遞其他input資料)資料。


在這邊我們要利用Netbeans、Servlet3.0、Tomcat 8來實做我們的範例,
實現了三個版本的檔案上傳:Javascript、JQuery、AngularJS
首頁是專案檔案結構,如下圖:

所用的版本JQuery為v2.0.3、AngularJS為v1.5.8,
主要的重要檔案有:

  1. fileUploadAjaxExample.html
    給User上傳檔案的html網頁。
  2. FileUploadAjax.js
    處理檔案資料上傳的Javascript File.
    包含Javascript、JQuery、AngularJS三個版本。
  3. FileUploadAction.java
    用來接收檔案資料的servlet,這裡設定URL patter為/fileUpload.do
  4. web.xml
    設定檔,其中必須在要接收檔案的servlet中設定的tag,
    tag裡可以有以下的子tag設定:
    <location>         檔案存放位置 (使用Part.write(fileName)可以在寫入檔案,但如果fileName
                               為絕對路徑則以絕對路徑為準)
    <max-file-size>  最大檔案size
    <max-request-size>   最大request size  (例如POST的request size)
    <file-size-threshold>
       超過file-size-threshold的檔案request將會以臨時暫存的方式存到硬                                       碟中,預設為0
接下來我以來各別看下每個實作檔案的內容:

2016年6月8日 星期三

AngularJS - 自製跟ng-repeat一樣能力的directive (my-repeat)

在這篇文中,我們要來製作一個跟AngularJS的ng-repeat有幾乎相同能力的自製directive(當然ng-repeat更完整,有更多的可用變數、接受表達示等)。

我們先定義出自製directive要擁有的能力,在這裡我們取名我們的directive叫作my-repeat:
  1. 使用型式如同ng-repeat,可用attribute的型式放在html element tag的attribute中使用。
  2. 其中,屬性裡面可以用 "XX in YYY" 這種型式的表達示來表示要對哪個array型式(YYY)的內部資料(XX)做迴圈。
  3. 在directive中,可以解析YYY中的XX來顯示資料,也可解析其他資料,例如controller中的變數。
  4. 能夠使用 "-start" 和 "-end" 的multiElement的用法。
  5. 此directive要可以嵌套到另一個directive中(其他的directive或自己)。

下面就是實現出的程式碼,可以在Result看到,我們的my-repeat擁有和ng-repeat相同的上述能力。(**有使用jQuery做$(element)和before()的動作)


在程式碼中已經寫了詳細的註解,不過我們還必須對執行完上述程式碼後,AngularJS產生出了什麼樣的Scope關係。
在程式碼裡,我們已經在有scope出現的地方進行了console.log()的輸出,可以由其中的屬性物件、$id、_proto_了解到scope之間的關係,以下已經用簡單圖示來表示從console.log()結果中看出的關係:

2016年5月6日 星期五

html5 的 setCustomValidity(message) 觀念釐清

在這篇文中主要是要釐清在使用setCustomValidity(message)的一些觀念,
setCustomValidity(message)可以跟據送入的message是否是空字串來蓋住input元素的validity狀態,但是要注意的是,並不會蓋掉input元素的validity狀態,input元素的真實validity狀態並不會因此而改變。

為了驗證這個現像,我們可以看一下下面這個例子:
當我們呼叫
setCustomValidity("自訂訊息");
時,input的真正valid訊息會被隱藏起來,而這時去看input的validity.valid的話就得到false的結果,就算我們在input中打字也是一樣,
但其實input在有打字內容時其直正的validity.valid應該是true。
我們可以呼叫setCustomValidity("")來讓input的真正validity.valid不要被蓋往,這時我們可以從isValid(after setCustomValidity("")):來得到之前被隱藏的正確結果。

========================================================================

這裡我再舉出一個比較常用的使用setCustomValidity("")的例子:

可以看到我們再input的oninvalid和oninput都呼叫check(this),這是因為當在input中打字時,就算打的內容不符合設定的pattern,也不會觸發oninvalid,只會觸發oninput。

我們在check(target)中先呼叫setCustomValidity("")得到input的真正valid狀態,如果真正valid為false就呼叫setCustomValidity("自訂訊息"),而因為此呼叫會讓之後valid的真正狀態被隱藏,所以我們之後會再呼叫check時呼叫setCustomValidity("")來讓input的真正valid狀態不要被隱藏起來。

這樣之後再用if()詢問input的狀態時,就不會得到錯的、與實際不符的valid狀態了。
重點就是,在檢查valid的狀態時必須要真正的(沒被隱藏的),再判斷要不要設定自訂訊息後。

========================================================================

接下來我再舉出一個例子,有時候我們會想要對一個Group,即一群name一樣的input元素去做判斷,例如對於input="radio"只會想要同一個Group裡的radio被選取,像下面這樣:


如果想在radio的oninvalid和onclick使用上述的check(target)方法的話,會造成錯誤。
如果有兩個設置rquired的同name的radio且都沒有被點擊時,按下submit後會對每一個radio呼叫check(target)並都被隱藏真正valid狀態。
當點擊第二個radio時,會只對第二個radio呼叫check(target),此時第二個radio的valid會是true,但對一個radio的valid還是false,所以此時按下submit會無法submit,接著又因為submit會讓每一個check(target)都被呼叫,所以此時每一個radio的valid狀態都才會正常,再按一次submit就可以成功submit了。

結果是submit要按兩下才會成功,這很顯然不是我們想要的,所以我們要做一點改良。

以下就是改良的方法,checkValid(target),在其中也對一個input用radio來設置不同的pattern check:

重點就是在checkValid(target)裡面必須對所有group裡面的input都去做檢查並設置正確的valid狀態,如此一來,不管是submit還是onclick就都能對所有的input設置正確的valid狀態了
(當然submit會輪詢一次所有input,而checkValid(target)又會輪詢一次,所以會輪詢兩次造成效能上的浪費,不過這裡就忽略此issue了)。



2016年4月13日 星期三

AngularJS的$compile() - 動態增加DOM

在AngularJS中,會在瀏覽器對html解析成DOM後,先對AngularJS(ng-app)的進行編譯,給定scope後才會進行數據綁定。

但是如果是用例如JQuery等方式新增了新的DOM元素後,因為沒有用AngularJS編譯的關係,所以就算裡面有AngularJS的語法也不會呈現預期的結果。

例如像以下的例子:

HTML:
<div ng-app="myApp">
  <div id='DOMWrapper' ng-controller="myCtrl">
    <button ng-click='addDOM()'>Add DOM</button>
  </div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', '$compile', function($scope, $compile) {
	$scope.textOfDOM = 'Text show successfully';

  $scope.addDOM = function() {
    var DOMToAdd = $('<p>{{textOfDOM}}</p>');
    
    //Wrong code -- start
    $('#DOMWrapper').append(DOMToAdd);
    //Wrong code -- end    
    
    //Right code -- start
    //var linkOfDOMToAdd = $compile(DOMToAdd);
    //var nodeOfCompiledDOM = linkOfDOMToAdd($scope);
    //$('#DOMWrapper').append(nodeOfCompiledDOM);
    //Right code -- end
  };
}]);

 從結果中可以看到當按下"Add DOM"按鈕時,出現的字是 "{{textOfDOM}}",而不是我們預期的 "Text show successfully"。

這是因為要新增的DOM沒有被AngularJS編譯的關係,以下才是正確的程式碼:
HTML:
<div ng-app="myApp">
  <div id='DOMWrapper' ng-controller="myCtrl">
    <button ng-click='addDOM()'>Add DOM</button>
  </div>
</div>
Javascript:
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', '$compile', function($scope, $compile) {
	$scope.textOfDOM = 'Text show successfully';

  $scope.addDOM = function() {
    var DOMToAdd = $('<p>{{textOfDOM}}</p>');
    
    //Wrong code -- start
    //$('#DOMWrapper').append(DOMToAdd);
    //Wrong code -- end
    
    //Right code -- start
    var linkOfDOMToAdd = $compile(DOMToAdd);
    var nodeOfCompiledDOM = linkOfDOMToAdd($scope);
    $('#DOMWrapper').append(nodeOfCompiledDOM);
    //Right code -- end
  };
}]);
我們可以呼叫$compile()並把要編譯的DOM送進去當參數,它會返回一個 link 函式,呼叫 link 函式並把scope當作輸入參數,就可以將scope與被編譯的DOM產生連結,所以DOM就可以得到textOfDOM的值,link 函式會返回已經跟資料連結好的新的DOM物件,接著我們再把新的DOM物件用JQuery的語法放到 #DOMWrapper裡面,就可以正確的得到我們要的 "Text show successfully"了。

參考資料:

  1. 18.5. Compile的细节
  2. $compile

2015年12月13日 星期日

jQuery之Deffered的應用

jQuery有Deferred及Promise物件,可以幫助我們處理異步函式的callback順序關係。
在這邊舉兩個例子來說明其可能的兩個應用:

一、用Deferred實現異步函式的callback順序關係
Deferred可以實現異步函式的callback順序關係,在下例中,我們有三個函式,f1()、f2()、f3(),其中都有setTimeout模擬一秒的異步延遲,並且希望能f1做完才執行f2、f2做完才執行f3。如果我們沒有處理好callback的順序關係的話,就會發生f1的setTimeout還沒執行完就執行f2的情況。

在此例中,我們利用Deferred.then()來確保f1,f2,f3的執行順序關係,在f1,f2,f3中,會個自建立一個Deferred物件並傳回。
而setTimeout結束後,會呼叫Deferred.resolve()來告知函式執行完成,並可以選擇送進一個參數(param)來給之後的callback函式使用,接著會執行then()裡面的函式。

以上說明是簡單的行為了解。其實真正的實作比較複雜,可以參考jQuery之Deferred的原理,在這邊稍微講一下大概的背後過程:

  1. f1()回傳了第一個Deferred物件。
  2. f1('f1').then(f2)建立了第二個Deferred物件,並為第一個Deferred物件設定done(ff2),ff2是callback函式,ff2的內容是執行f2(),並取得f2()回傳的第三個Deferred物件,接著設定第三個物件的done(fff2),其中fff2=第二個Deferred物件.resolve。
  3. 所以當呼叫then(f2)時,會回傳一個Deferred物件(即第二個Deferred物件。
  4. 當f1()裡呼叫defer.resolve()時,會執行f2()。
  5. 當f2()裡呼叫defer.resolve()時,會引發第三個Deferred物件的done(),接著就會引發第二個Deferred物件的resolve()。
  6. 以此類推,第二個Deferred物件被resolve()後就會去執行f3()。


HTML:
<div id='result'>
  Result:
</div>
Javascript:
function f1(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f1 got parameter : ' + param);
    //將'f2'當參數傳給don()或then()之後的function
    defer.resolve('f2');
  }, 1000);
  return defer;
}

function f2(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f2 got parameter : ' + param);
    //將'f3'當參數傳給don()或then()之後的function
    defer.resolve('f3');
  }, 1000);
  return defer;
}

function f3(param) {
  var defer = $.Deferred();
  setTimeout(function() {
    $('#result').html($('#result').html() + '<br/>f3 got parameter : ' + param);
    //defer.resolve(param + ' f3');
  }, 1000);
  return defer;
}
//Deferred參數的傳遞
f1('f1').then(f2).then(f3);
二、合併多個異步函式的callback處理,即個別異步函式都執行完後才執行callback

在此例中我們用到了$.when(),它可以被傳入多個Deferred或Promise物件,並在所有的Deferred或Promise物件都被resolve()或reject()後才執行callback。

在這裡我們有三個動畫,都為一串文字由右往左移動,每個動畫的時間都不一樣,第一個是用CSS的動畫,其他兩個是用jQuery的animate()做的動畫。

對於每個動畫,我們都建立一個新的Deferred物件給它,並在動畫執行完後將它得到的Deferred給resolve()。而每個Deferred物件我們都放進一個叫des的Array中。

最後我們把Array裡面的三個Deferred物件丟給$.when()裡當參數,利用then()或done()來設定三個Deferred物件都resolve()後才要執行的callback。

P.S.

  1. 因為$.when()不能接受Array型式的參數,所以這裡利用了apply($,des)來將des中的內容傳給when(),其中第一個參數為要代替when()中的this的參考,第二個參數是一個Array,其內容為要傳給when()的參數。
  2. resolveDeferred()接受一個Deferred物件,並傳回一個function,在傳回的function中其this指向呼叫resolveDeferred的物件;而deferred指向function被建立的域(即resolveDeferred())中的deferred。(可參見閉包(Closure))


HTML:
<div class='animSection'>
  <div id='anim1'>Watch me move1</div>
  <div id='anim2'>Watch me move2</div>
  <div id='anim3'>Watch me move3</div>
</div>
<br/>
<div id='text' class="text">Detect Result:</div>
CSS:
.animSection div{
  padding-left: 100%;
}

.animClass {
  animation-name: myAnim;
  animation-duration: 6s;
  animation-fill-mode: forwards;
}

@keyframes myAnim {
  0% {
    padding-left: 100%;
  }
  100% {
    padding-left: 0%;
  }
}
Javascript:
var des = []; //用來放Deferred或Promise物件的array
var deferred;

deferred = $.Deferred();
des.push(deferred.promise()); 
//也可以寫成des.push(deferred),deferred.promise()回傳的為Promise物件,為不可修改狀態只供查詢狀態的物件,即沒有resolve()等方法
//
$('#anim1').addClass('animClass').one('animationend', resolveDeferred(deferred));

deferred = $.Deferred();
des.push(deferred.promise());
$('#anim2').animate({
  paddingLeft: "-=100%"
}, 9000, resolveDeferred(deferred));

deferred = $.Deferred();
des.push(deferred.promise());
$('#anim3').animate({
  paddingLeft: "-=100%"
}, 5000, resolveDeferred(deferred));
//因為$.when()只能接收一個一個用逗號分開的參數,所以利用apply來將array以個別
//參數的方式傳給when(),when()可接受Deferred及Promise物件
$.when.apply($, des).done(function() {
  $('#text').html($('#text').html() + '<br/>All finished!');
});
//也可以寫成以下,then可以給兩個參數,第一個為done()的callback函式,
//第二個為fail()的callback函式
//$.when.apply($, des).then(function(){
//	$('#text').html($('#text').html() + '<br/>All finished!');
//});

function resolveDeferred(deferred) {
  return function() {
    $('#text').html($('#text').html() + '<br/>#' + this.id + ' finished!');
    deferred.resolve();
  }
}

2015年12月12日 星期六

jQuery之Deferred的原理(參看源碼)

了解了jQuery的Callbacks以後,我們可以藉由觀看jQuery的源始碼(jquery-1.11.3.js)來較清楚地了解Deferred到底做了什麼事及怎麼實現的,以更好的運用Deferred及其相關API。

首先,我們先把Deferred的jQuery源碼貼出來:

 Deferred: function( func ) {
  var tuples = [
    // action, add listener, listener list, final state
    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
    [ "notify", "progress", jQuery.Callbacks("memory") ]
   ],
   state = "pending",
   promise = {
    state: function() {
     return state;
    },
    always: function() {
     deferred.done( arguments ).fail( arguments );
     return this;
    },
    then: function( /* fnDone, fnFail, fnProgress */ ) {
     var fns = arguments;
     return jQuery.Deferred(function( newDefer ) {
      jQuery.each( tuples, function( i, tuple ) {
       var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
       // deferred[ done | fail | progress ] for forwarding actions to newDefer
       deferred[ tuple[1] ](function() {
        var returned = fn && fn.apply( this, arguments );
        if ( returned && jQuery.isFunction( returned.promise ) ) {
         returned.promise()
          .done( newDefer.resolve )
          .fail( newDefer.reject )
          .progress( newDefer.notify );
        } else {
         newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
        }
       });
      });
      fns = null;
     }).promise();
    },
    // Get a promise for this deferred
    // If obj is provided, the promise aspect is added to the object
    promise: function( obj ) {
     return obj != null ? jQuery.extend( obj, promise ) : promise;
    }
   },
   deferred = {};

  // Keep pipe for back-compat
  promise.pipe = promise.then;

  // Add list-specific methods
  jQuery.each( tuples, function( i, tuple ) {
   var list = tuple[ 2 ],
    stateString = tuple[ 3 ];

   // promise[ done | fail | progress ] = list.add
   promise[ tuple[1] ] = list.add;

   // Handle state
   if ( stateString ) {
    list.add(function() {
     // state = [ resolved | rejected ]
     state = stateString;

    // [ reject_list | resolve_list ].disable; progress_list.lock
    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
   }

   // deferred[ resolve | reject | notify ]
   deferred[ tuple[0] ] = function() {
    deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
    return this;
   };
   deferred[ tuple[0] + "With" ] = list.fireWith;
  });

  // Make the deferred a promise
  promise.promise( deferred );

  // Call given func if any
  if ( func ) {
   func.call( deferred, deferred );
  }

  // All done!
  return deferred;
 }

再來一步步解析:

jQuery的Callbacks

jQuery有一個對控制異步函式時很好用的類別Deferred,還有它的簡少API版本,Promise(無法更改state)。

我們可以藉由觀看jQuery的源始碼(jquery-1.11.3.js)來較清楚地了解Deferred到底做了什麼事及怎麼實現的,以更好的運用Deferred及其相關API。

在了解Deferred之前,我們可以先來了解Callbacks這個類別。在這邊用形象的方式來了解Callbacks的運作方式。

Callbacks可以被當成是一個佇列式的列隊(先進先出,FIFO),我們可以把想要做的function傳進列隊中(add()),並在想要的時候觸發列隊中的function執行(fire() or fireWith()),在這邊是一次執行所有的function,雖然有照順序開始執行,但並無法保證一個function執行完才執行下一個(例如要花時間的異步函式)。

而Deferred則是利用Callbacks的性質來實作的,基本上就是預先將異步函式做完時要執行的回調函式先放到列隊中,並執行異步函式,等異步函式執行完後手動決定要觸發列隊裡函式並執行的時機。

jQuery的Callbacks有以下常用的API function
  1. $.Callbacks([flags]) : 創建並返回一個Callbacks類別。
    • flags : 可選,可以接受多個,用空格的String來表示,例如有以下幾種:
      •  once:
        • 列隊只能被觸發一次(fire)。
      •  memory:
        • 列隊觸發後,一有函式放進(add)列隊會被馬上執行。
      •  unique:
        • 列隊中的同樣函式(指的是其參考一樣的 reference)再一次觸發時只會被執行一次。
  2. Callbacks.add(callbacks) : 將callbacks函式放進Callbacks列隊中。
  3. Callbacks.fire([arguments]) : 觸發列隊執行其中的函式。
    • arguments : 可選,可傳入參數給列隊中各函式當input value。
  4. Callbacks.fireWith([context] [, arguments]) : 跟fire()相似,也是觸發列隊執行其中的函式,不過可以指定上下文(context),即函式中的this指誰。
  5. Callbacks.disable() : 將callback列隊disable掉,即之後不能在做fire()之類的呼叫(呼叫了也不會有行為)
範例 :

2015年12月5日 星期六

CSS與jQuery的動畫(animation)比較

jQuery與CSS都能實現對網頁上的DOM進行動畫特效,但兩者有一些差異,特別在這邊紀錄:

jQuery的動畫:

  1. 可使用$(selector).animate(styles,speed,easing,callback)來進行動畫,為有數字參數的css屬性進行動畫(無法用於只有字串值的css屬性,例如background-color:red)。
  2. 可使用$(selector).stop(stopAll,goToEnd)來停止動畫,可以只停止當前動畫並繼續 queue中的其他動畫。也可以停止當前動畫並情空動畫列隊(只限queue中的animation列隊成員)(參見queue),此時可以選擇直接將元素(DOM element)的動畫效果跳至當前動畫的最後狀態。
  3. 沒有暫停的方法(pause),即無法中途暫停,之後再從暫停的地方播放剩餘的動畫(resume)。目前要有pasue、resume的方法只能用plug-in等的方式解決,這裡有一些還沒試過的plug-in可以參考: PausejQuery-FxQueues
  4. 動畫結束後並不會引發animation事件。
CSS的動畫
  1. 為DOM element加上附有動畫屬性的CSS來實現動畫。
  2. 在CSS中使用animation-name屬性來指定keyframes (參見CSS3 Animations)。故無法同時在同一個DOM element上指定兩個animation-name的CSS屬性,會被最後決定的屬性覆蓋過去,即一次只能一個動畫。
  3. 可以設定許多不同的屬性來達到不同的效果。例如用animation-iteration-count來設定重覆次數
  4. 可以使用animation-play-state來設定運行(running)、暫停(paused),pased及running可以互相切換,動畫不會被停止無法繼續播(即paused之後再running等同resume效果)。
  5. 動畫播放完以後nimation-play-state會被設為paused。
  6. 動畫無法停止
  7. animation-timing-function可以設定時間與output效果的關係(參見<timing-function>),常用的有可以用來做逐幀動畫的steps(number_of_steps, direction),作用為設定output效果為步進函數(step function),number_of_steps代表切割動畫(動畫指瀏覽器算出來的漸進動畫output效果)為幾幀,direction代表步進的不連續點為每幀的start處還是end處(參見<timing-function>CSS3 timing-function: steps() 詳解小tip: CSS3 animation漸進實現點點點等待提示效果)。
  8. 動畫結束後發出animationend事件,可用JavaScript的addEventListener('animationend',callbackFunction)或jQuery的bind('animationend',callbackFuncion)來獲取事件,但是如果動畫class被移除或animation-play-state被設成paused並不會引發animationend事件。

JavaScript, jQuery 的Event機制 (Capturing, Bubbling)

在Javascript的W3C標準中,當Evnet(事件)發生時,例如click事件、animationEnd事件、hover事件等,Event會以由外到內(Capturing)、再由內到外(Bubbling)的方式傳遞。

我們可以用JavaScript的方法,addEventListener(eventName , callbackFunction , capturOrBubble)
來設置Evnet的監聽器,其中

第一個參數eventName為一個String,表示事件名稱,跟jQuery的bind()及one()不同,不可用空白分隔多個事件。

第二個參數是一個callback方法,可以選擇傳入event參數來得到event資訊,例如可以用event.target或event.srcElement (IE適用) 來得到Event作用的JavaScript對像。

我們也可以用Jquery的方法,bind(eventName,callbackFunction),來做到一樣的事情,不過要要注意bind()的callbackFunction傳入的event不是JavaScript的Event物件,而是被包裹成jQuery版的Event物件,雖然一樣有event.target,但是確沒有event.srcElement,如果要拿到JavaScript版的Event物件,可以使用event.origianlEvent來得到。

現在用下面這個例子來說明:

html:

<div id='1'>
  <div id='2'>
    <div id='3' class='animClass'>Watch me move</div>
  </div>
</div>
<div id='text'></div>


CSS :
.animClass {
  animation-name: myAnim;
  animation-duration: 1s;
}



@keyframes myAnim {
  0% {
    padding-left: 100%;
  }
  100% {
    padding-left: 0%;
  }
}

JavaScript (使用addEventListener) :

document.getElementById('1').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#1 catched animation end from #' + event.target.id + '! (Capturing)</br>');
}, true);

document.getElementById('2').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#2 catched animation end from #' + event.target.id + '! (Capturing) </br>');
}, true);

document.getElementById('3').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#3 catched animation end from #' + event.target.id + '! (Capturing) </br>');
}, true);

document.getElementById('1').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#1 catched animation end from #' + event.target.id + '! (Bubbling)</br>');
}, false);

document.getElementById('2').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#2 catched animation end from #' + event.target.id + '! (Bubbling) </br>');
}, false);

document.getElementById('3').addEventListener('animationend', function (event) {
  $('#text').html($('#text').html() + '#3 catched animation end from #' + event.target.id + '! (Bubbling) </br>');
}, false);

其結果是:

2015年10月24日 星期六

自製jQuery的PlugIn(twinkle演示範例)

JQuery提供外掛(Plug-In)能力,可以讓使用者設計自己所需的功能,跟自己做一個客製化類別(Class)不一樣,外掛就相當於是jQuery物件能夠使用的功能,能夠用selector的方式直接得到jQuery物件然然後使用,就像顯示物件是 $("selector").show();一樣,我們也可以自己寫一個外掛來用,例如$("selector").myPlugInFun();

以下為在寫JQuery的Plug-In時,常使用的其中一種寫法(只是舉例,寫法因人而異):

(function($){

    //這裡可設制function或變數

    $.fn.extend({

           //這裡可設制function或變數

           myPlugInFun1 : function(options){

                                         //這裡可設制function或變數,跟Java或C很不一樣的是,

                                         //JavaScript的function也算一種Object,所以可以在function裡設

                                         //function屬性

                                         var defaultOptions1 = {

                                               option1 : "option1" ,

                                               option2 : "option2"

                                         };

                                          return this.each(function(){

                                              $.extend(defaultOptions,options);

                                              //..........Do something

                                          });

                                      } ,

           myPlugInFun2 : function(options){

                                            ...以此類推

                                      }

    });

})(jQuery);


下面我們來寫一個簡單的jQuery Plug-In,叫做twinkle(),其作用是讓選取的jQuery物件可以依自訂(當然也可以預設)的次數及間隔時間閃爍,且也可以設定執行完後的callback function及擁有串接的特性,相關的說明註解都在程式碼中,直接看程式碼:

2015年9月2日 星期三

好用 jQuery 外掛

 各種jQuery外掛庫的搜尋網站:

  1. jQuery plugins

覺得好用的jQuery外掛:

  1. jQuery custom content scroller :為區塊加上滑動的側邊卷軸。
  2. fullPage.js:為網站增加上下滑動(section)及同section左右滑動(slide)的效果。

2015年6月27日 星期六

Ajax、JQuery與Servlet的簡單範例

在這邊要紀錄的是一個簡單的Ajax、JQuery、Servlet聯合應用的簡單範例。
此例client端利用JQuery的Ajax語法來將表單中的參數傳給以Servlet實作的Server端,經由Server端處理過後,以JSON的格式回傳給client端, client端再將JSON格式的回傳值解析後做相應的動作,此例的動作為兩個:跳出視窗顯示回傳值、在html裡的filedset中顯示回傳值。

  1. 首先是作為client端的html內容,index.html:
  2. <!DOCTYPE html>
    
    <html>
    
        <head>
    
            <title>AjaxTest</title>
    
            <meta charset="UTF-8">
    
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
            <!-- 加載Ajax -->
    
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
    
            <!-- Ajax的測試Script -->
    
             <script>
    
                 //在網頁加載後,對id=doAjaxBtn的Button設定click的function
    
                $(document).ready(function(){
    
                   $("#doAjaxBtn").click(function(){
    
                       $.ajax({
    
                            type:"POST",                    //指定http參數傳輸格式為POST
    
                            url: "doAjaxServlet.do",        //請求目標的url,可在url內加上GET參數,如 www.xxxx.com?xx=yy&xxx=yyy
    
                            data: $("#formId").serialize(), //要傳給目標的data為id=formId的Form其序列化(serialize)為的值,之
    
                                                            //內含有name的物件value
    
                            dataType: "json",               //目標url處理完後回傳的值之type,此列為一個JSON Object
    
                            //Ajax成功後執行的function,response為回傳的值
    
                            //此範列回傳的JSON Object的內容格式如右所示: {userName:XXX,uswerInterest:[y1,y2,y3,...]}
    
                            success : function(response){
    
                                //在id=ajaxResponse的fieldset中顯示Ajax的回傳值
    
                                $("#ajaxResponse").html("您的大名:"+response.userName+"</br>");
    
                                $("#ajaxResponse").append("您的興趣:</br>");
    
                                var userInterestString = "";
    
                                for(var i = 0 ; i<response.userInterest.length ; i++){
    
                                    $("#ajaxResponse").append(response.userInterest[i]+"</br>");
    
                                    userInterestString += "\n"+response.userInterest[i];
    
                                }
    
                                //用彈出視窗顯示Ajax的回傳值
    
                                alert("UserName:"+response.userName+"\nInterest: "+userInterestString);
    
                            },
    
                            //Ajax失敗後要執行的function,此例為印出錯誤訊息
    
                            error:function(xhr, ajaxOptions, thrownError){
    
                                alert(xhr.status+"\n"+thrownError);
                            }
    
                        });
    
                   });
    
                });  
    
            </script>
    
        </head>
    
        <body>
    
            <div>AjaxTest</div>
    
            <form id="formId">
    
                大名:<input type="text" name="userName">
    
                <input type="checkbox" name="userInterest" value="看書" />看書
    
                <input type="checkbox" name="userInterest" value="遊戲" />遊戲
    
                <input type="checkbox" name="userInterest" value="電影" />電影
    
                <input type="button" id="doAjaxBtn" value="啟動Ajax" />           
    
            </form>
    
            <div id="anotherSection">
    
                <!-- 用來顯示Ajax回傳值的fieldset -->
    
             <fieldset>
    
                 <legend>Response from jQuery Ajax Request</legend>
    
                 <div id="ajaxResponse"></div>
    
             </fieldset>
    
            </div> 
    
        </body>  
    
    </html>
  3. 再來是Server端的Servlet,doAjaxServlet.java:

2014年12月28日 星期日

JQuery的小日曆

在撰寫html的日期選擇時,常會要求使用者使鍵入正確的日期格式,這樣才會方便後續的處理,例如進行MySQL的查詢,這時如果只是使用html中單純的text是難以規範使用者的輸入內容的,所以通常我們都會採取不讓使用者直接輸入而是用選擇的方式輸入日期資料,通常有下例幾種方式:
  1. 直接把年、月、日拆開來,分別用不同的下拉式選單讓使用者選取正確的數字,不過這種方式使用者能選擇的值是有限的,你沒有提供的數字就無法選擇。
  2. 以直觀的方式使用日曆來讓使用者選擇日期,使用者會看到日曆並直接在上面選擇。
因為JQuery十分方便,只要引用進JSP中就可以直接使用,而且日曆外觀也不錯,所以在這裡就以JQuery的小日曆為例子,來說明如實現第2種的日曆方式:
  1. 首先要在html的head中把要使用的CSS樣式、JQuery和JQuery UI引用進來,如以下的前三行程式碼,第四行程式碼是中文化的功能,不加入這行時日曆是呈現英文的界面,加入後就能顯示中文介面的日曆:
  2. <!-- 使用 jQuery -->
    <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js"></script>
    
    <!-- 使用 jQuery Datepicker的中文化-->
    <script type="text/javascript" src="../jquery/jquery.ui.datepicker-zh-TW.js"></script>