10/10/2018, 11:08

[Advanced] Viết plugin cho jQuery - Nâng cao

[NẾU CÓ DÙNG BÀI VIẾT, XIN BẠN TÔN TRỌNG QUYỀN TÁC GIẢ BẰNG CÁCH GIỮ NGUYÊN DẤU TÁC QUYỀN [x] BÊN DƯỚI BÀI VÀ DÒNG CHỮ NÀY, THÂN]

Ngồi rảnh không có gì làm nên ngồi viết cái này cho những bạn nào có kiến thức cơ bản về javascript và muốn nâng cao hơn đọc. Đánh giá mức độ của mình cho bài viết này là Khó.

Giả sử rằng các bạn đã nắm vững những hàm cơ bản của mảng, đối tượng,... Và nắm vững ở đây có nghĩa là nhìn vào là biết, ko phải lật sách hay google nó nhá.

Đầu tiên, như mọi ứng dụng jQuery khác, bạn phải nhập vào thư viện jQuery:

Code:
<script language="javascript" type="text/javascript" src="scripts/jQuery.js"></script>
<script language="javascript" type="text/javascript" src="scripts/jQueryUI.js"></script>
Khởi tạo 1 plug in jQuery? Việc này không khó và được xem như là cơ bản cần phải biết:

Code:
(function($)
	{
	//Code custom jQuery nằm ở đây.
	})(jQuery);
Giải thích: Đây là hàm không tên, có cấu trúc dạng (function(){})() được gọi và thực thi ngay sau khi viết [nhờ vào cái () ở phía cuối á], trong hàm này, nó đưa giá trị jQuery vào dưới dạng $ để có thể dùng được ký hiệu dollar quen thuộc trong jQuery.

Mục đích của bài viết này là custom 1 hàm đơn giản tên là fly() với thuộc tính và chức năng như sau:

Code:
//Hàm fly này sẽ làm cho đối tượng resize kích thước 600 x 600
//Và bay qua bên phải góc màn hình
//Trong thời gian 2000 mili giây
//Tuy nhiên trước khi thực hiện nó sẽ gán position: "absolute" để có thể bay
//Và sau khi bay xong, giảm kích thước width và height đi 400
//Rồi bay ngược về góc trái màn hình trong thời gian gấp đôi ban đầu
//Và xuất hiện hộp thông báo alert("Đã hoàn tất") sau khi hoàn tất bay.
$("#test").fly(
	{
	precss: {position: "absolute", background: "#000000"},
	css: {width: 600, height: 600, top: 0, right: 0},
	time: 2000,
	callback: function(settings)
		{
		var w=settings["css"]["width"]; var h=settings["css"]["height"];
		var t=settings["time"];
		this.fly(
			{
			css: {width: w-400, height: h-400, top: 0, left: 0},
			time: t*2,
			callback: function()
				{
				alert("Da hoan tat!");
				}
			});
		}
	});
Chà, nhức đầu chưa, nếu quan sát kĩ, thì đây chỉ là hàm bình thường, tuy nhiên sau khi bay xong, nó lại gọi 1 hàm khác để bay thêm 1 vòng nữa, và sau khi bay xong lần 2 nó thông báo hoàn tất. Chóng mặt thật xD!

Vậy thì qua ví dụ này, chắc chắn bạn sẽ nắm được:
  • Cách tạo hàm cơ bản
  • Gọi callback cho hàm
  • Nhận giá trị biến theo dạng mảng, và có thể cài mặc định
  • ...


Và đảm bảo với các bạn, trong quá trình làm, cả chục vấn đề phát sinh ra, cả chục kinh nghiệm lý thú của mình .

Ok, bắt tay vào việc:

Đầu tiên nói về đặt tên cho 1 hàm, như sau đây là một tối kiến KHÔNG NÊN LÀM:

Code:
(function($)
	{
	$.fn.fly=function(){};
	$.fn.flyAgain=function(){};
	$.fn.flyAgainAgain=function(){};
	})(jQuery);
Vì sao? Vì khi bạn đặt quá nhiều tên, khả năng xung đột giữa cái custom plugin khác và custom plug in của bạn tăng lên rất cao, có thể làm hỏng chương trình của người dùng! Vậy thì thay vì người dùng gọi:

Code:
$("#test").fly();
$("#test").flyAgain();
$("#test").flyAgainAgain();
Thì hãy để người dùng gọi theo cách này:

Code:
$("#test").fly();
$("#test").fly("again");
$("#test").fly("againAgain");
Để làm được cái đó, các bạn sẽ cần hàm sau [phân tích bên dưới]

Code:
(function($)
	{
	methods=//Khởi tạo mảng chứa các hàm
		{
		init: function()
			{
			//Code for $("selector").fly()
			},
		doSomething: function()
			{
			//Code for $("selector").fly("doSomething")
			}
		}
	$.fn.fly=function(m)//Khởi tạo hàm khi gọi $("selector").fly, nhận vào giá trị m
		{
		if(methods[m])//Nếu hàm methods[m] tồn tại (phía trên)
			{//Thực thi hàm cùng với tất cả các tham số
			return methods[m].apply(this,Array.prototype.slice.call(arguments,1));
			}
		else if(typeof m=="object"||!m)//Nếu như không có gì truyền vào
			{//Thực thi methods["init"]
			return methods.init.apply(this,arguments);
			}
		else//Khác
			{//Thông báo hàm không tồn tại
			alert("Method "+m+" does not exist!");
			}
		}
	})(jQuery);
Chắc chắn rằng bạn có thể hiểu được những điều trên trước khi đọc tiếp nha @@! Nếu hiểu sơ sơ cũng được, vì sau khi làm vài lần, bạn sẽ nhuyễn như ăn cháo thôi.

Chẹp, thế là xong phần cơ bản của khởi tạo 1 hàm rồi, bây giờ thử đổi hàm init xem sao:

Code:
methods=
	{
	init: function()
		{
		alert(this.id);
		}
	}
Gọi nó nào, ý quên, còn phải tạo 1 <div> để test chứ:
Code:
<div id="test"></div>
Giờ thì an tâm gọi rồi, nhớ gọi nói khi document ready, điều cơ bản nhất của jQuery:

Code:
$(document).ready(function()
	{
	$("#test").fly();
	}
Nếu thành công, bạn sẽ nhận được thông báo "test" [vì id của đối tượng này là test mà].

Tiếp theo hãy dùng phương pháp truyền thống để truyền tham số vào cho đối tượng thử nhé:

Code:
methods=
	{
	init: function(parameter)
		{
		alert(parameter);
		}
	}
Gọi nó nào:

Code:
$(document).ready(function()
	{
	$("#test").fly("HELLO");
	}
Kết quả là gì, thất bại. Hahaha, mình đưa ra ví dụ này để cho thấy rằng, nếu bạn truyền tham số "HELLO" vào hàm fly(), nó sẽ không nghĩ đây là tham số, mà sẽ tưởng nhầm là 1 method [phần trên], tức là nó sẽ gọi methods["HELLO"] và kết quả trả về là method ko tồn tại.

Vậy thì làm sao? Đây là nơi mình phải truyền tham số bằng cách dùng mảng rồi. Nói về cách dùng mảng, từ lúc này trở đi, bạn phải nhuyễn như ăn cháo cách viết:

Code:
var a={name: "WHATEVER", age: 17};
Thay vì cách thông thường:

Code:
var a=new Array();
a["name"]="WHATEVER";
a["age"]=17;
Cách mới ngắn hơn, và dễ quản lý hơn rất nhiều. Viết code theo mình là hướng đến tính đơn giản và đa chức năng. Vậy bây giờ hãy truyền tham số vào cho nó nhé:

Code:
methods=
	{
	init: function(parameters)
		{
		//Nhớ rằng parameters lúc này là 1 mảng
		alert(parameters["message"]+this.id);
		}
	}
Gọi nó nào:

Code:
$(document).ready(function()
	{
	$("#test").fly(
		{
		message: "My id is "
		});
	}
Kết quả trả về là "My is is test", đúng ko? Vậy là đã thành công rồi đó. Tuy nhiên đến lúc này vẫn chưa phải là phần khó lắm.

Nếu bạn có kiên nhẫn đọc được tới đây rồi thì chúc mừng bạn, bạn sắp học được một trong những kinh nghiệm của riêng mình rồi đó.

Ok, trở lại vấn đề, do mình gọi hàm fly bằng cách dùng id ("#test"):

Code:
$(document).ready(function()
	{
	$("#test").fly(
		{
		message: "My id is "
		});
	}
Nên chỉ có 1 đối tượng được gọi, vậy chuyện gì sẽ xảy ra nếu mình gọi theo class (".test"). Chà, có hàng chục đối tượng có cùng lớp như vậy, vậy nếu ở hàm init, nó chỉ thông báo 1 lần, thế giả mình muốn thực hiện với tất cả cách đối tượng khác thì sao, đến lúc này, bạn phải bố sung thêm 1 đoạn nhỏ vào cho code của mình:

Code:
methods=
	{
	init: function(parameters)
		{
		this.each(function()//Hàm này dùng để chạy lần lượt theo tất cả các phần tử được chọn
			{
			alert(parameters["message"]+this.id);
			});
		}
	}
Bây giờ thử tạo nhiều thẻ div với id khác nhau nhưng cùng chung class test và gọi xem, kết quả là gì nhá .


Phù. Vậy là phần sườn đã xong, bây giờ sẽ tới phần hơi phúc tạp 1 tí: Đặt mặc định cho tham số. Nói cho dễ hiểu, nhìn lại hàm trên, có phải nó sẽ thông báo hộp thoại alert(parameters["message"]+this.id) không? Vậy chuyện gì nếu parameters["message"] bị bỏ trống? Undefined? Trong ví dụ này, nó không có gì là quan trọng, tuy nhiên với những ứng dụng sau này, giả sử bạn cần màu sắc chẳng hạn, mà tham số đó ko có thì sao. Đặt mặc định chứ sao. Vậy cách làm là gì? Cách làm cơ bản nhất mà mình vẫn làm khi tự nghiên cứu:

Code:
var message=(!parameters["message"]) ? "Mặc định " : parameters["message"];
//Hay
if(!parameters["message"])
	{message="Mặc định ";}
else
	{message=parameters["message"];}
Cách làm trên thế nào. Đúng chứ sao, 100% đúng, nhưng chuyện gì xảy ra nếu như mục đính của bạn [cũng như mục đích trong bài tut này] là dùng tới tận 4 tham số [precss, css, time, callback]. Chuyện gì sẽ xảy ra nếu số lượng tham số lên tới cả chục, hoặc cả trăm @@! Chà, vấn đề đấy. Vào một ngày đẹp trời, mình lang thang lên mạng đọc về jQuery, và cuối cùng mình đã tìm ra được cách: dùng jQuery extend().

jQuery.extend() có rất nhiều cách dùng, mình sẽ không giới thiệu từng cách 1 [các bạn nên tự tìm hiểu], mình chỉ giới thiệu 1 cách dùng để đặt mặc định thôi. Hãy nhìn vào đoạn mã sau:

Code:
init: function(parameters)
	{
	//Nếu message không tồn tại, thì trộn nó với message mặc định, nếu tồn tại thì thôi.
	var defaults={message: "Mặc định "}; parameters=$.extend({}, defaults, parameters);

	this.each(function()
		{
		alert(parameters["message"]+this.id);
		});
	}
Sau khi extend, parameters vẫn sẽ trả về dưới dạng 1 mảng. NHỚ CHO KỸ CÁI NÀY NHA!
Vậy là an toàn rồi nha, bây giờ không sợ có tham số rỗng nữa rồi. Quên cái ví dụ alert() đơn giản đó đi, đến lúc ứng dụng nó vào plugin thực sự rồi. Bước tiếp theo là tạo cho nó mấy giá trị mặc định như đã nói [precss, css, time và callback]:
Code:
init: function(parameters)
	{
	var defaults={precss: {}, css: {}, time: 2000, callback: function(){}};
	parameters=$.extend({}, defaults, parameters);

	this.each(function()
		{
		//Code sẽ nằm ở đây
		});
	}
Như bạn nhìn thấy, precss là 1 mảng rỗng [mình dùng mảng để chứa được nhiều dữ liệu hơn], css cũng là 1 mảng rỗng, time có giá trị mặc định là 2000, callback là 1 hàm rỗng. Tất nhiên bạn có thể sửa nó tùy ý bạn.

Chuẩn bị cho khái niệm trừu tượng tiếp theo chưa? Đó là sự khác nhau giữa "this" và "$(this)", tuy chúng cùng chỉ về đối tượng được chọn. Hãy tạm gác tut lại một chút và nhìn vào ví dụ dưới đây:

Code:
init: function(parameters)
	{
	this.each(function()
		{
		alert(this.id);//Dùng this.id nha
		alert($(this).attr("id"));//Thế tại sao ở đây lại dùng $(this)????
		});
	}
Bạn dễ dàng thấy được khi cần id, thì mình dùng this.id, và khi muốn động chạm đến jQuery.attr() thì mình lại phải dùng $(this). Thực ra, sự khác nhau của this và $(this) là ở chỗ $(). $() là một hàm của jQuery dùng để biến đối tượng this thành 1 object, vì chỉ có object mới có thể dùng được .attr(). Do biến thành object, nên nó không còn thuộc tính .id như ban đầu nữa. Tóm gọn lại, để dùng những hàm của jQuery, phải biến nó thành object thông qua hàm $() hay jQuery(), object là một đối tượng mới, không có bất cứ thuộc tính nào. Còn nếu để nguyên this, nó chỉ về đối tượng và giữ nguyên tất cả các thuộc tính của nó [như src, id,...] và không dùng được trong các hàm của jQuery.

Quay lại tut, bây giờ vẫn đoạn code trên, nhưng mình sẽ thêm vài dòng chú thich vào để các bạn có thể theo dõi mình đang làm gì:

Code:
init: function(parameters)
	{
	var defaults={precss: {}, css: {}, time: 2000, callback: function(){}};
	parameters=$.extend({}, defaults, parameters);

	this.each(function()
		{
		//Đặt tên 1 biến để dễ tham chiếu nào
		//Đầu tiên đặt css cho đối tượng, lấy thông tin từ precss của parameters
		//Tiếp theo cho đối tượng bay nhảy gì gì đó tùy ý, dùng .animate() của jQuery, dùng css và time của parameters
		//Cuối cùng là gọi hàm callback sau khi animate() xong, dùng callback của parameters
		});
	}
Ok, điền code vào thôi, phần này khá đơn giản nên cũng không cần giải thích nhiều:

Code:
init: function(parameters)
	{
	var defaults={precss: {}, css: {}, time: 2000, callback: function(){}};
	parameters=$.extend({}, defaults, parameters);

	this.each(function()
		{
		//Đặt tên 1 biến để dễ tham chiếu nào
		var obj=$(this);//Nhớ rằng mình dùng hàm của jQuery, nên phải biến nó thành object
		//Đầu tiên đặt css cho đối tượng, lấy thông tin từ precss của parameters
		obj.css(parameters["precss"]);
		//Tiếp theo cho đối tượng bay nhảy gì gì đó tùy ý, dùng .animate() của jQuery, dùng css và time của parameters
		obj.animate(parameters["css"], parameters["time"]);
		//Cuối cùng là gọi hàm callback sau khi animate() xong, dùng callback của parameters
		});
	}
Phù, cơ bản là xong rồi, thế còn vụ callback thì sao? Làm cách nào để gọi hàm callback? Để gọi hàm callback, hãy vận dụng 1 cách gọi hàm khá cơ bản của javascript:

Code:
function saySomething(a)
	{
	alert(a);
	}
saySomething.call(this, "HELLO");
Chờ gì nữa, gom nó vào code, tuy nhiên, để an toàn, trước khi chạy callback function, phải chắc chắn rằng nó là 1 hàm nữa:

Code:
init: function(parameters)
	{
	var defaults={precss: {}, css: {}, time: 2000, callback: function(){}};
	parameters=$.extend({}, defaults, parameters);

	this.each(function()
		{
		//Đặt tên 1 biến để dễ tham chiếu nào
		var obj=$(this);//Nhớ rằng mình dùng hàm của jQuery, nên phải biến nó thành object
		//Đầu tiên đặt css cho đối tượng, lấy thông tin từ precss của parameters
		obj.css(parameters["precss"]);
		//Tiếp theo cho đối tượng bay nhảy gì gì đó tùy ý, dùng .animate() của jQuery, dùng css và time của parameters
		obj.animate(parameters["css"], parameters["time"], function(){
			//Cuối cùng là gọi hàm callback sau khi animate() xong, dùng callback của parameters
			if(typeof parameters["callback"]=="function")//Kiểm tra coi có phải là hàm ko
				{
				parameters["callback"].call(this, null);//Không có tham số nào được truyền vào.
				}
			});
		});
	}
Xong rồi đó, nhưng nhìn kỹ lại cái đoạn tut dự định làm của mình xem, bạn có thấy cái hàm trả về ko? Nó có mảng settings để chứa các thông tin hiện tại của đối tượng, vậy nó ở đâu ra?

Code:
$("#test").fly(
	{
	precss: {position: "absolute", background: "#000000"},
	css: {width: 600, height: 600, top: 0, right: 0},
	time: 2000,
	callback: function(settings)
		{
		var w=settings["css"]["width"]; var h=settings["css"]["height"];
		var t=settings["time"];
		this.fly(
			{
			css: {width: w-400, height: h-400, top: 0, left: 0},
			time: t*2,
			callback: function()
				{
				alert("Da hoan tat!");
				}
			});
		}
	});
Rất đơn giản, thay vì truyền tham số null khi gọi hàm callback:

Code:
parameters["callback"].call(this, null);
Thì chúng ta truyền thông tin hiện có của đối tượng trong mảng parameters xem:

Code:
parameters["callback"].call(this, parameters);
Vậy là xong, đây là đoạn code cơ bản nhất và có lẽ là đầy đủ nhất về những điều cần biết khi viết 1 plugin. Bạn đã có thể gọi hàm mà ko sợ bị trùng lặp, truyền biến thông qua mảng, gọi callback, trả về đối tượng thích hợp cho hàm callback,... Hy vọng rằng tut này sẽ giúp bạn 1 phần nào trong việc bắt đầu viết 1 plug in của mình. Mình sẽ không post 1 lúc full code ở đây, vì các bạn phải đọc từng phần cho nhuyễn. Chỉ cần nối lại là xong ấy mà.

Thân.
[x] - xx3004

Bonus: Ví dụ trên, nhưng được viết dưới dạng phức tạp hơn rất nhiều, ứng dụng nhuần nhuyễn hơn những điều đã nói ở trên, mức độ KHÓ, bạn không nên đọc tiếp nếu bạn chưa nhuyễn những điều ở trên:

Code:
(function($)
	{
	xm=
		{
		init: function(o, d)//Options, Defaults to merge
			{
			o=(o!="" && o!=null) ? o : {}; d=(d!=""&&d!=null) ? d : {};
			return $.extend({},d,o);
			},
		exec: function($_)//[Function and parameters callback]
			{
			var defaults={func: function(){}, parameters: null, callback: null}; $_=this._($_, defaults);
			f=$_["func"]; p=($_["parameters"]==undefined) ? null : $_["parameters"]; cb=$_["callback"];
			
			if(typeof f=="function"){f.call(this, p);}
			else{this._("error", {error: "Not a [function]"});}
			
			//Call back
			if(cb){this._("exec", {func: cb});}
			},
		error: function($_)//[Error]
			{
			var defaults={error: null}; $_=this._($_, defaults); e=$_["error"];
			if(e){this._.errors=(this._.errors!=null) ? this._.errors+"
 [AND] 
"+e : e;}else{return this._.errors;}
			},
		data: function($_)//[Name Data]
			{
			var defaults={name: null, data: null}; $_=this._($_, defaults);
			var d=$_["name"], v=$_["data"];
			this._.data=(this._.data==null) ? {} : this._.data;
			if(d && v){this._.data[d]=v;}else{return this._.data[d];}
			}
		};
	$.fn.x=$.fn._=function(m){if(xm[m]){this._.errors=(!this._.errors)?null:this._.errors;return xm[m].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof m=="object"||!m){this.errors=(!this.errors)?null:this.errors;return xm.init.apply(this,arguments)}else{this.errors=(!this.errors)?null:this.errors;this._("error", {error: "Method "+m+" does not exist"})}}
	})(jQuery);

(function($)
	{
	methods=
		{
		init: function(parameters)
			{
			var defaults={precss: {}, css: {}, time: 2000, callback: function(){}};
			parameters=this._(parameters, defaults);

			this.each(function()
				{
				var obj=$(this);

				obj.css(parameters["precss"]);
				obj.animate(parameters["css"], parameters["time"], function()
					{
					this._("exec", {func: parameters["callback"], parameters: parameters});
					});
				});
			}
		}
	$.fn.fly=function(m)
		{
		if(methods[m])
			{return methods[m].apply(this,Array.prototype.slice.call(arguments,1));}
		else if(typeof m=="object"||!m)
			{return methods.init.apply(this,arguments);}
		else
			{
			this._("error", {error: "Method "+m+" does not exist!"});
			alert(this._("error"));
			}
		}
	})(jQuery);
ENJOY
kenphan19 viết 13:10 ngày 10/10/2018
hehe hay lắm ... cố gắng phát huy khả năng viết thêm vài bài nữa nhé !!!
manlivo viết 13:13 ngày 10/10/2018
Nhìn dài quá! Nếu bác chia nhỏ ra đc thì hay.
xx3004 viết 13:14 ngày 10/10/2018
Được gửi bởi manlivo
Nhìn dài quá! Nếu bác chia nhỏ ra đc thì hay.
Hì, mình không có kinh nghiệm viết bài cho lắm. Nói thật là lúc mới làm mấy cái code này, lỗi lã tùm lum. Mình phải đúc kết rất nhiều thứ và nhiều lần thử mới làm được đoạn code hoàn chỉnh thế này xD! Nếu các bạn bắt đầu từ đầu thì chắc chắn sẽ gặp rất nhiều vấn đề bực mình , nên sẵn có kinh nghiệm, mình làm giúp luôn xD!
P/S: Để có gì lát về chia nhỏ nó lại!
[x]
chesterben viết 13:13 ngày 10/10/2018
Nice tut!
Để vậy là dễ đọc rồi đó chớ
xx3004 viết 13:20 ngày 10/10/2018
Được gửi bởi chesterben
Nice tut!
Để vậy là dễ đọc rồi đó chớ
Cám ơn bạn nhé xD!, mình hiểu cảm giác của mấy bạn kia, nhìn thấy dài quá là ngán tới tận cổ rồi xD! Mình cũng thế, thấy tut nào dài thôi thì...tự làm cho sướng. Hahaha.
[x]
ngoc_viet08 viết 13:21 ngày 10/10/2018
Nice tút , build it simple và giảm thiểu tối đa những thứ ko cần thiết nhé. cho newbie dễ tham khảo .
xx3004 viết 13:11 ngày 10/10/2018
Được gửi bởi ngoc_viet08
Nice tút , build it simple và giảm thiểu tối đa những thứ ko cần thiết nhé. cho newbie dễ tham khảo .
Cám ơn bạn! Mình đã lược gọn lắm rồi đó, dùng đúng 4 tham số để làm 4 ví dụ về tham số mảng, tham số dạng number, tham số dạng callback function(), mà newbie ở đây theo mình đề cập trước là newbie phải có kiến thức cơ bản về jQuery và vững về javascript trước, nếu ko thì bó tay hehe xD! Để lát ngồi lược ra 4 phần khác nhau để dễ xử vậy.

[x]
maicon viết 13:24 ngày 10/10/2018
công nhận công phu, nhưng mà dài thật ~~~~ nếu cắt ra từng đoạn thì hay quá
kenphan19 viết 13:11 ngày 10/10/2018
hi !!! repost lên blog mình đc
Bài liên quan
0