การเขียน 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 เมธ็อดนี้เท่านั้นครับ  ซึ่งแต่ละเมธ็อดจะมีหน้าที่ต่างกันไปดังนี้

  1. WP_Widget::__constructor() สำหรับกำหนดค่าพื้นฐานข้อวิดเจ็ต
  2. WP_Widget::widget() สำหรับแสดงผลวิดเจ็ตบนหน้าเว็บ
  3. WP_Widget::update() สำหรับอัพเดทการตั้งค่าในเมนู Widget (หลังบ้าน)
  4. WP_Widget::form() สำหรับแสดงผลฟอร์มตั้งค่าในเมนู Widget

กำหนดค่าพื้นฐานด้วย WP_Widget::__constructor()

เมธ็อดนี้ไม่มีอะไรมากครับ  แค่ให้เรากำหนดค่าพื้นฐานของวิดเจ็ตของเราเท่านั้น  ในที่นี้เรากำหนด 3 อย่างคือ

  1. Base ID เอาไว้สำหรับเป็น ID อ้างอิงต่างๆ (เช่น id ใน HTML หรือกล่องตั้งค่า)
  2. Name ชื่อวิดเจ็ต  ซึ่งหลักๆ มันจะแสดงอยู่ในเมนู Widget นั่นแหละ
  3. 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)  โดยมีการรับพารามิเตอร์เพียงตัวเดียวเท่านั้น คือ

  1. $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 ตัวเช่นกัน คือ

  1. $new_instance คือการตั้งค่าชุดใหม่ที่ฟอร์มตั้งค่าส่งมาให้
  2. $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 ตัว  คือ

  1. $args เป็นการรับอาร์กิวเมนต์สำหรับแสดงผลจาก Sidebar ครับ  (เป็นอาร์กิวเมนต์ที่ระบุเอาไว้ตอน register_sidebar() ครับ) ที่มีมาให้ก็คือ before_title, after_title, before_widget, และafter_widget
  2. $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');

Posted by Jirayu

WordPress Developer ที่พอมีประสบการณ์อยู่บ้าง วันไหนไม่ทำงานอยู่บ้านว่างๆ ก็นั่งเลี้ยงแมว

Comments