กลับมาอีกครั้งสำหรับ jQuery plugin ภาค 2 ( สองๆๆๆๆๆๆๆ ) ถ้าใครยังไม่ได้ดูว่าตอนแรกทำอย่างไร ไปดูได้เลยจ้า jQuery plgin ทำไง ภาค 2 นี้ยังคงเป็นการสอนทำ plugin ที่เราต้องการให้มันใช้งานที่ง่ายขึ้นและมีความซับซ้อนขึ้น ( อ้าวแล้วเราจะทำให้มันซับซ้อนขึ้นทำไม ? ) คือ เมื่อเราทำ plugin มาแล้วก็ควรจะทำให้มันยืดหยุ่นสามารถปรับเปลี่ยนตัวแปรได้อิสระ คืออาจจะปรับได้เล็กน้อยไปถึงปรับได้มากโดยภาคนี้เราจะได้เรียนรู้ดังนี้
jQuery plugins รายละเอียด
- Defaults and Options
- Namespacing
- Plugin Methods
- Events
- Data
Defaults and Options
สำหรับการกำหนดตัวแปรให้ค่อนข้างยืดหยุ่น ตัวอย่างที่ดีนั้นเราจะใช้ฟังก์ชั่น $.extend() เราจะไม่ส่งตัวแปรมาเยอะเยะ แต่เราจะทำการส่งตัวแปรแบบ object json เข้ามาทีเดียว กำหนดได้หมดว่าจะทำอะไร อย่างไรบ้าง
(function( $ ){
$.fn.tooltip = function( options ) {
// สร้างตัวแปรมารับค่าเพื่อเราจะเอาค่าจากใน settings มาใช้ใน plugin ของเรา
var settings = $.extend( {
'location' : 'top',
'background-color' : 'blue'
}, options);
return this.each(function() {
// Tooltip plugin code here
});
};
})( jQuery );
ตอนเราจะส่งค่าเข้าไปก็ทำอย่างนี้เลยจ้า
$('div').tooltip({
'location' : 'left'
});
จากตัวอย่างด้านบนนั้นเมื่อเราทำการเรียก jQuery plugins tooltip แล้วส่งค่าเข้าไปยังตัวแปร options ค่า location ในตัวแปร options จะทำการ override หรือว่าทับตัวที่อยู่ในตัวแปร settings ในฟังก์ั่ชั่น $.extend เพราะฉะนั้นค่าที่เราจะได้จากการเรียกแบบนี้ตามตัวอย่างคือ
{
'location' : 'left',
'background-color' : 'blue'
}
แถมให้อีกนิดคือการเข้าถึงค่าในตัวแปรถ้าสมมติคุณต้องการ location ในตัวแปร settings ก็ให้เขียนอย่างนี้ครับ settings.location ก็จะได้ค่าจากตัวแปร location แล้วทำนองเดียวกันถ้าคุณต้องการค่า background-color
Namespacing
การตั้งชื่อ jQuery plugins นั้นก็สำคัญเป็นส่วนหนึ่งของการพัฒนาโปรแกรม ต้องมั่นใจว่าชื่อที่เราจะต้องไม่ซ้ำหรือมีโอกาสน้อยมากที่จะซ้ำในหน้าเดียวกัน หรือโดยทับโดยชื่อของ jQuery plugins ตัวอื่น ซึ่งมันจะอำนวยความสะดวกสำหรับการที่เราจะ track ( ตามหา ) event , data , method
jQuery Plugins Method
ถ้าหากว่าเรามีหลาย ฟังก์ชั่นที่ถูกเรียกใช้ภายใน plugin เดียวกันอย่างตัวอย่าง
(function( $ ){
$.fn.tooltip = function( options ) {
// THIS
};
$.fn.tooltipShow = function( ) {
// IS
};
$.fn.tooltipHide = function( ) {
// BAD
};
$.fn.tooltipUpdate = function( content ) {
// !!!
};
})( jQuery );
การเรียกใช้นัั้นก็จะมีความลำบากนิดหน่อยเช่นถ้าเราต้องการเรียก tooltipShow ก็ต้องเป็น $(‘element’).tooltipShow() แล้วเราก็ต้องมานั่งจำชื่ออีก แต่ถ้าเราสามารถจัดการให้มันอยู่ในกลุ่มเดียวกันได้โดยการทำเป็น object literal หรือการส่งแบบ JSON นั่นเอง ซึ่งจะง่ายมากๆและเขียนได้สั้นน้อยลงตามด้านล่างเลยครับ
(function( $ ){
var methods = {
init : function( options ) {
// THIS
},
show : function( ) {
// IS
},
hide : function( ) {
// GOOD
},
update : function( content ) {
// !!!
}
};
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
// calls the init method
$('div').tooltip();
// calls the init method
$('div').tooltip({
foo : 'bar'
});
// calls the hide method
$('div').tooltip('hide');
// calls the update method
$('div').tooltip('update', 'This is the new tooltip content!');
หลายๆคนคงสับสนว่ามันทำงานอย่างไร จะอธิบายให้ฟังครับ ในการเรียก plugin ตัวนี้เราได้แบ่งชื่อฟังก์ชั่นตามที่เห็นด้านบนและ เวลาเราเรียกนั้นเราก็สามารถใส่ namespacing ต่างๆได้ โดยเมื่อเราทำการเรียก plugin แล้วใส่ค่า hide ไปตัว plugin จะทำการเรียกไปที่บรรทัดนี้ก่อน
$.fn.tooltip = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
โดยค่า hide ที่เราใส่เข้ามาคือตัว argument ชื่อ method นั่นเอง แล้วก็เข้าสู่ if โดยมีการเช็คว่า methods ( สังเกตุนะครับว่ามี s ) มี index หรือค่า key นั้นชื่อว่า hide หรือไม่ซึ่งถ้ามีก็เข้าสู่ if ( methods[ method] ) และทำงานในนี้ทันที โดยบรรทัด return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); เป็นการส่งค่า argument ที่ใส่มาด้วยให้กับฟังก์ชั่นนั้นๆ เช่น ฟังก์ชั่น update จะเห็นว่าเราใส่ค่าเพิ่มมาด้วยก็จะโอนค่านั้นส่งต่อไปให้ฟังก์ชั่น update และค่านั้นจะถูกใช้ในฟังก์ชั่น update ที่มีชื่อว่า content นั่นเอง ส่วนการเรียกที่เราไม่ใส่ค่าอะไรเลยหรือว่าใส่ค่าเป็นแบบ JSON มันรู้ได้อย่างไรว่าเราทำการเรียกฟังก์ชั่น init ตัวอย่างเราทำการเรียก
// calls the init method
$('div').tooltip();
// calls the init method
$('div').tooltip({
foo : 'bar'
});
จะเห็นว่าใน if ที่ผมอธิบายเมื่อตะกี้จะมี else if ต่อมาคือ else if ( typeof method === ‘object’ || ! method ) ซึ่งหมายถึงว่า ถ้ากรณีที่ไม่ใส่ค่าอะไรมาเลยหรือใส่ค่ามาเป็น object จะให้ทำคำสั่งนี้ โดยจะทำการเรียกฟังก์ชั่น init ให้อัตโนมัติพร้อมกับส่งค่า argument ไปให้ฟังก์ชั่น init อีกด้วย แต่ถ้าหากเราเรียกอะไรแปลกๆซึ่งไม่มีในสิ่งที่เรากำหนดไว้ละก็ มันจะทำการทำคำสั่งสุดท้ายของ else นั่นคือการแจ้งเตือน error ใน console.log
จะเห็นการเขียนแบบนี้นั้นจะเป็นการเอาข้อดีของ oop คือ encapsulate มาใช้โดยเราสามารถสร้าง method ที่เราต้องการและสามารถส่งค่าต่างๆให้กับ method ของเราได้และการสร้าง plugin แบบนี้คือหลักสากล ของ jQuery plugin ที่ใช้กันภายใน community และสามารถเรียก plugin ตัวอื่นหรือแม้แต่ widgets ต่างๆได้อีกด้วย
Events
อย่างที่เราทราบกันว่า method bind นั้นเป็นการ detect event ของ namespacing ( ใครไม่เข้าใจจะอธิบายทีหลังน้าสำหรับ method bind ) อธิบายสั้นๆว่า ถ้าคุณทำการ bind event ใดๆมันจะติดอยู่กับ element นั้นๆเช่น
$('#id_element').bind('click', function() {
//do something
});
ถ้า jQuery plugins ของคุณมีการ bind event แล้ว แล้วคุณต้องการจะทำการ unbind ( อันนี้อีกตัวจะบอกทีหลังน้าฮ่าๆ ) ก็เหมือนการถอด event นั้นออกจาก element ที่เรากำหนด ซึ่งจากตัวอย่างนั้นคือ การ bind element ธรรมดาถ้าสมมติว่าเราอยากทำให้ jQuery plugins ของเรามีการใช้ bind event ใดๆใน event ของเราแล้วเราต้องการจะทำการ unbind เราต้องทำทุก event เลยหรือเปล่า ? คำตอบคือเรามาสามารถ unbind ทุก event ของเราได้โดยการพิมพ์ unbind(‘.<namespacing>’) ซึ่งคำใน namespacing นั้นคือชื่อ plugin ของเรา แล้วถามว่าทำไมเราต้อง unbind event ของเราด้วยยกตัวอย่างง่ายๆ คือ ถ้าสมมติ plugin ของคุณมีการใช้ bind click แล้วมี element ตัวอื่นทำการเรียกเรียก plugin ของคุณอีก อาจจะทำให้มีการชนกันของ event หรืออยากจะทำงานซับซ้อนเราจึงควรจะมีการ unbind event ใน plugin ของเราก่อนครับ ตัวอย่าง
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
$(window).bind('resize.tooltip', methods.reposition);
});
},
destroy : function( ) {
return this.each(function(){
$(window).unbind('.tooltip');
})
},
reposition : function( ) {
// ...
},
show : function( ) {
// ...
},
hide : function( ) {
// ...
},
update : function( content ) {
// ...
}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
$('#fun').tooltip();
// Some time later...
$('#fun').tooltip('destroy');
$('#bar').tooltip();
ในตัวอย่างนั้นเมื่อ jQuery plugins tooltip มีการเรียก init method ซึ่งจะมีการเรียกให้ method reposition ซึ่ง method นี้ทำการ resize ซึ่ง event ที่ถูก bind นั้นจะอยู่กับ window ภายใต้ชื่อในการเรียกใช้ว่า ‘tooltip’ งงไหม ? หมายถึงว่าตอนนี้ window มี event ชื่อว่า tooltip ติดอยู่ด้วยนั่นเอง แล้วทีนี้ถ้าสมมติว่าคุณต้องการจะทำลาย event นี้ เราสามารถ unbind event นี้ด้วยการส่งค่าผ่าน jQuery plugins ด้วย namespacing บางอย่าง ( ในตัวอย่าง namespacing สำหรับ unbind คือ destroy ) ในกรณีนี้สำหรับ tooltip สำหรับ unbind event method การที่เราทำการ $().tooltip(‘destroy’) โดยอย่าลืมว่า event มันเกาะกับ element เพราะฉะนั้นคุณต้องเลือก element นั้นๆเพื่อทำการ destroy หรือ unbind ออกนะครับ ซึ่งวิธีนี้จะทำให้เราสามารถ unbind ได้ปลอดภัยโดยจะไม่พบการชนกันของ event ที่อาจจะออกไปนอก plugin
Data
บ่อยครั้งที่นักพัฒนา plugin นั้นต้องการเช็คสถานะต่างๆหรือว่ามีการเช็คว่า plugin ของเราทำงานกับ element นั้นๆหรือยัง ให้ใช้ .data เป็น method ที่ดีมากสำหรับการติดตาม ( debug หรือ tracking ) ตัวแปรต่างๆ อย่างไรก็ตามแทนที่เราต้องมานั่งดูว่าแต่ละตัวแปรชื่ออะไรบ้าง ( ถ้าสมมติว่าเราเก็บไว้หลายตัว ) มันจะดีกว่าถ้าเราสามารถส่งตัวแปรที่เป็น json หรือ object เข้าไปในตัวแปรเดียวเพื่อจะเป็นเสมือนบ้านเก็บตัวแปรของคุณเลยไม่ต้องมานั่งหา เพราะว่าเวลาเช็คคุณก็เรียกออกมาทั้งหมดเลยและข้อดีอีกอย่างคือคุณสามารถเข้าถึงตัวแปรด้วยชื่อเดียวตัวอย่าง
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
destroy : function( ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip');
// Namespacing FTW
$(window).unbind('.tooltip');
data.tooltip.remove();
$this.removeData('tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
การใช้ method .data นั้นสามารถทำให้เรา tracking ตัวแปรโดยสามารถเรียกได้จาก method อื่นใน plugin ได้ด้วย ซึ่งจะทำให้คุณสามารถเรียกดูเมื่อไหร่ก็ได้และจะสะดวกอีกทางคือเมื่อคุณต้องการจะลบค่าของมัน
Summary and Best Practices
การเขียน jQuery plugin นั้นอนุญาตให้เราใช้ความสามารถของฟังก์ชั่นต่างๆและการนำกลับมาใช้ใหม่ซึ่งเหล่านี้จะช่วยลดเวลาในการเขียนโค้ดและการพัฒนาต่ออย่างมีประสิทธิภาพ ต่อไปจะเป็นสรุปว่าทุกครั้งที่เราจะสร้าง plugin ซักตัวเราจะต้องทำอะไรบ้าง
- ทุกครั้งอย่าลืมว่า plugin ต้องอยู่ภายใต้ วงเล็บ ( closure )
- อย่าใช้ this เกินขอบเขตของ function อย่างที่อธิบายข้างบนแล้วว่ามันต่างกันอย่างไร
- ให้คุณ return this จาก plugin plugin เพื่อให้สามารถทำ chainability ( การที่คุณเรียก method ได้ติดต่อกันครับ )
- พยายามเขียนตัวแปรที่ส่งเข้าไปใน plugin ของเราทำเป็นแบบ setting ได้ โดยใช้ฟังก์ชั่น extend
- อย่าใช้ jQuery.fn แล้วตามด้วย namespacing เยอะหรือจะอธิบายง่ายๆว่าอย่าทำแบบนี้ เช่น jQuery.fn.update , jQuery.fn.delete , jQuery.fn.create คือให้ทำแบบที่สอนด้านบนมีการเรียกใช้ผ่านชื่อ method ที่ตั้ง
- ตั้งชื่อ method, event, data เสมอ
Credit
https://www.tutorialspoint.com/jquery/jquery-plugins.htm