การเขียน Widget สำหรับ WordPress
Widget โดยนิยามแล้วมันคือกล่องเล็กๆ ที่ไว้สำหรับแสดงข้อมูลเพิ่มเติมบน Sidebar (เช่นโพสต์ล่าสุด, กล่องค้นหา, รายการแท็ก, ฯลฯ) ซึ่งตัว WordPress เองก็ได้เตรียมวิดเจ็ตเอาไว้ให้เราจำนวนหนึ่ง และปลั๊กอินหลายๆ ตัวก็มาพร้อมกับวิดเจ็ตด้วยเช่นกัน แต่โดยปกติแล้ววิดเจ็ตเหล่านี้มักจะปรับแต่งอะไรได้ไม่มากนายนัก ทำให้บางครั้งก็ค่อนข้างลำบากในการเอามาใส่ให้เข้ากับเว็บที่ทำอยู่
เนื่องจากงานสองสามงานล่าสุดที่ผมทำนั้นมีการต้องเข้าไปเขียน Widget เพิ่มเติมด้วย (เว็บ TechSauce) โดยเหตุหลักๆ คือวิดเจ็ตที่มากับ WordPress เองนั้นมันหน้าตาไม่เข้ากับ Design นั่นแหละ ดังนั้นไหนๆ ก็ไหนๆ เอามาลงบล็อกเลยแล้วกัน
คลาสขยาย WP_Widget
Widget ใน WordPress นั้นจะเขียนขึ้นมาในลักษณะ OOP โดยขยายจากคลาส WP_Widget อีกต่อครับ อ้ออย่าเพิ่งกลัวกับ OOP ครับ ไม่ยากหรอก เขียน Widget นี่มันแค่เขียนฟังก์ชันหน้าตา ฟังก์ชันกล่องตั้งค่า ฟังก์ชันเซฟค่า แล้วก็ครอบมันด้วยคลาสโง่ๆ อันนึงแค่นั้นเอง (จริงๆ มันเขียนเป็น Procedural ได้ … แต่ผมไม่เคยเขียนวิดเจ็ตเป็น Procedural ครับ :v ) ไปลองดูโครงสร้างกัน
class mn_widget_hello extends WP_Widget { function __construct() { parent::__construct( 'mn_widget_hello', // Base ID 'Hello World', // Name array( 'description' => "Just another WordPress Widget", ) // Args ); } function widget($args, $instance){ ... } function update($new_instance, $old_instance){ ... return $instance; } function form($instance){ ... } }
ในการสร้างวิดเจ็ต เราจะใส่เพียงแค่ 4 เมธ็อดนี้เท่านั้นครับ ซึ่งแต่ละเมธ็อดจะมีหน้าที่ต่างกันไปดังนี้
- WP_Widget::__constructor() สำหรับกำหนดค่าพื้นฐานข้อวิดเจ็ต
- WP_Widget::widget() สำหรับแสดงผลวิดเจ็ตบนหน้าเว็บ
- WP_Widget::update() สำหรับอัพเดทการตั้งค่าในเมนู Widget (หลังบ้าน)
- WP_Widget::form() สำหรับแสดงผลฟอร์มตั้งค่าในเมนู Widget
กำหนดค่าพื้นฐานด้วย WP_Widget::__constructor()
เมธ็อดนี้ไม่มีอะไรมากครับ แค่ให้เรากำหนดค่าพื้นฐานของวิดเจ็ตของเราเท่านั้น ในที่นี้เรากำหนด 3 อย่างคือ
- Base ID เอาไว้สำหรับเป็น ID อ้างอิงต่างๆ (เช่น id ใน HTML หรือกล่องตั้งค่า)
- Name ชื่อวิดเจ็ต ซึ่งหลักๆ มันจะแสดงอยู่ในเมนู Widget นั่นแหละ
- Arguments ในที่นี้ผมกำหนดลงไปแค่ Description อย่างเดียว (คำอธิบายว่าอันนี้เป็นวิดเจ็ตอะไร) จริงๆ ยังสามารถกำหนด classname จากตรงนี้ได้อีกด้วย
ตัวอย่าง
function __construct() { parent::__construct( 'mn_widget_hello', // Base ID 'Hello World', // Name array( 'description' => "Just another WordPress Widget", ) // Args ); }
สร้างฟอร์มการตั้งค่าด้วย WP_Widget::form($instance)
เมธ็อด form นี้หน้าที่หลักของมันมีเพียงแค่ใช้แสดงผลฟอร์มตั้งค่าครับ (การบันทึกค่าจะเป็นหน้าที่ของเมธ็อด update) โดยมีการรับพารามิเตอร์เพียงตัวเดียวเท่านั้น คือ
- $instance ซึ่งจะเป็นการตั้งค่าต่างๆ ที่เคยบันทึกเอาไว้
สำหรับโค๊ดส่วนอื่นๆ จะเป็นโค๊ดสำหรับแสดงผล Form Element ต่างๆ ตามปกติ (ไม่ต้องครอบแท็ก form)
ข้อแนะนำอย่างหนึ่งคือในการกำหนด id หรือ name ให้กับฟิลด์ต่างๆ ควรทำผ่านเมธ็อด get_field_id($name) และ get_field_name($name) ตามลำดับ เพื่อป้องกันปัญหาชื่อฟิลด์หรือไอดีของ element นั้นๆ ซ้ำกับบน element อื่นๆ ที่มีอยู่แล้ว
เช่นในโค๊ดตัวอย่างนี้จะเป็นการสร้างฟอร์มสำหรับรับค่า title ขึ้นมา (ซึ่งเราจะนำไปแสดงเป็นหัวข้อของวิดเจ็ตเมื่อแสดงผล)
function form($instance){ if ( isset( $instance[ 'title' ] ) ) { $title = $instance[ 'title' ]; } else { $title = __( 'Links', 'mn_textdomain' ); } echo "<p>"; echo "<label for=\"".$this->get_field_id( 'title' )."\">".__( 'Title:' )."</label>"; echo "<input class=\"widefat\" id=\"".$this->get_field_id( 'title' )."\" name=\"".$this->get_field_name( 'title' )."\" type=\"text\" value=\"".esc_attr( $title )."\">"; echo "</p>"; }
อัพเดทการตั้งค่าจากฟอร์มด้วย WP_Widget::update($new_instance, $old_instance)
เมธ็อดสำหรับทำหน้าที่บันทึกการตั้งค่าวิดเจ็ตในเมนู Widget ครับ รับพารามิเตอร์ 2 ตัวเช่นกัน คือ
- $new_instance คือการตั้งค่าชุดใหม่ที่ฟอร์มตั้งค่าส่งมาให้
- $old_instance คือการตั้งค่าเดิมที่ใช้อยู่ก่อนหน้านี้
การทำงานของมันนั้นง่ายมากๆ ครับ เราไม่ต้องไปยุ่มย่ามกับการอัพเดทใดๆ เพียงแค่อัพเดทการตั้งค่าลงอาร์เรย์ตัวหนึ่งแล้ว return ค่ากลับออกไปเพียงเท่านั้น ซึ่งโดยปกติก็จะเช็คว่ามีการส่งการตั้งค่าใหม่เข้ามาหรือไม่ (ผ่าน $new_instance[$key]) หากมีก็จะใช้การตั้งค่าใหม่ ($instance = $new_instance[$key]) หากไม่มีก็จะนำค่าของ instance เก่ามาใช้ ($instance = $old_instance[$key]) เมื่อตรวจค่าครบทุกตัวแล้วจึงค่อย return $instance กลับออกไป
function update($new_instance, $old_instance){ $instance = array(); $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : ''; return $instance; }
แสดงผล Widget ด้วย WP_Widget::widget($args, $instance)
เมธ็อดนี้มีหน้าที่แสดงผลวิดเจ็ตบนไซด์บาร์ครับ มันจะรับพารามิเตอร์ 2 ตัว คือ
- $args เป็นการรับอาร์กิวเมนต์สำหรับแสดงผลจาก Sidebar ครับ (เป็นอาร์กิวเมนต์ที่ระบุเอาไว้ตอน register_sidebar() ครับ) ที่มีมาให้ก็คือ before_title, after_title, before_widget, และafter_widget
- $instance เป็นการรับการตั้งค่าของวิดเจ็ตเข้ามาครับ (ตรงนี้คือการตั้งค่าในเมนู Widget) โดยจะรับค่าที่ตั้งไว้เข้ามาเป็น Array
เมธ็อดนี้จะไม่ return ค่าใดๆ ครับ แต่จะแสดงผลออกมาตรงๆ เลย ซึ่งโดยปกติแล้วเราก็ควรผลจาก $args ที่รับมาด้วย ประมาณนี้
function widget($args, $instance){ echo $args['before_widget']; echo $args['before_title'].$instance['title'].$args['after_title']; echo '<div class="widget-content">'; echo '... Widget Content Goes Here ...'; echo '</div>'; echo $args['after_widget']; }
ในตัวอย่างก็จะเป็นการแสดงผลวิดเจ็ตเปล่าๆ ขึ้นมาครับ มีการเอาค่า title ที่ตั้งค่าไว้มาแสดงเป็นหัวข้อของวิดเจ็ต
การใช้งานวิดเจ็ต
ตัวโค๊ดของวิดเจ็ตนี้ใครจะเก็บยังไง เรียกยังไง ก็เอาแล้วแต่สะดวกครับ บางท่านอาจจะใส่ลงใน functions.php ตรงๆ บางท่านอาจจะใส่ไฟล์แยกแล้ว include เข้ามา หรือบางท่านอาจจะใส่ไฟล์แยกแล้วใช้ฟีเจอร์ autoloader ในการโหลดไฟล์เข้ามา ก็แล้วแต่สะดวก
แต่ก่อนที่เราจะใช้งานวิดเจ็ตนี้ได้ เราจำเป็นต้อง register มันเข้ามาในระบบก่อนครับ โดยปกติแล้วผมจะ register มันเข้ามาในขั้นตอนเดียวกับตอน register_sidebar() ครับ โดยเขียนโค๊ดการ register ทั้ง sidebar และ widget แยกเอาไว้ในฟังก์ชันหนึ่ง แล้วฮุคเข้ามาในแอคชัน widget_init
function mn_reg_sidebar(){ ... โคํด register_sidebar() ... register_widget('mn_widget_hello'); } add_action('widgets_init', 'mn_reg_sidebar');
โดยในฟังก์ชัน register_widget(); เราจะส่งชื่อคลาสที่เราเขียนเอาไว้เข้าไป จากนั้นมันก็จะมาแสดงอยู่ในรายการวิดเจ็ตของเราในเมนู Appearance > Widgets ครับ
เราสามารถลากมาใส่ใน Widget Area ได้ตามปกติ
และนี่เป็นการแสดงผลครับ
สรุปส่งท้าย
โครงสร้างการเขียนวิดเจ็ตคร่าวๆ ก็เป็นตามนี้ครับ ในขั้นตอนการใช้งานจริงๆ คุณสามารถเอาไปพลิกแพลงได้ตามแต่ที่ต้องการเลย เช่นที่ผมเขียนวิดเจ็ต Latest Posts ใหม่ ผมเขียน WP_Widget::form() ให้รับค่าว่าจะแสดงกี่โพสต์ แล้วใน WP_Widget::widget() ก็เอาค่าที่ตั้งไว้มาเขียน WP_Query ดึงเอาโพสต์ล่าสุดจำนวนเท่านั้นขึ้นมาแสดงในโครง HTML ของผมเอง
และนี่เป็นโค๊ดทั้งหมดครับ
class mn_widget_hello extends WP_Widget { function __construct() { parent::__construct( 'mn_widget_hello', // Base ID 'Hello World', // Name array( 'description' => "Just another WordPress Widget", ) // Args ); } function widget($args, $instance){ echo $args['before_widget']; echo $args['before_title'].$instance['title'].$args['after_title']; echo '<div class="widget-content">'; echo '... Widget Content Goes Here ...'; echo '</div>'; echo $args['after_widget']; } function update($new_instance, $old_instance){ $instance = array(); $instance['title'] = ( ! empty( $new_instance['title'] ) ) ? strip_tags( $new_instance['title'] ) : ''; return $instance; } function form($instance){ if ( isset( $instance[ 'title' ] ) ) { $title = $instance[ 'title' ]; } else { $title = __( 'Links', 'mn_textdomain' ); } echo "<p>"; echo "<label for=\"".$this->get_field_id( 'title' )."\">".__( 'Title:' )."</label>"; echo "<input class=\"widefat\" id=\"".$this->get_field_id( 'title' )."\" name=\"".$this->get_field_name( 'title' )."\" type=\"text\" value=\"".esc_attr( $title )."\">"; echo "</p>"; } }
include("widget-hello-world.php"); function mn_register_sidebar(){ /* โค๊ดสำหรับ register_sidebar(); */ register_widget('mn_widget_hello'); } add_action('widgets_init', 'mn_register_sidebar');