Writing a custom WordPress multi-instance widget

While working on a soon-to-be finished WordPress theme for a client, I ran into a situation where it would be useful to create a custom widget that they could use to organize content on their site. Basically, they wanted to be able to select a random post or page and display some associated meta data. Essentially a custom image and content teaser.

They were going to be doing this several times throughout the site, but in slightly different configurations. A post here, a page there. It seemed silly to hard-code these features. Using a widget would allow them to swap them out for a Twitter stream, or an RSS news feed in the future.

Making multi-instance widgets in WordPress 2.8+ couldn’t be easier. Here is a good example to start with from the WordPress codex.

  1. <?php
  2. class My_Widget extends WP_Widget {
  3. 	function My_Widget() {
  4. 		// widget actual processes
  5. 	}
  6.  
  7. 	function widget($args, $instance) {
  8. 		// outputs the content of the widget
  9. 	}
  10.  
  11. 	function update($new_instance, $old_instance) {
  12. 		// processes widget options to be saved
  13. 	}
  14.  
  15. 	function form($instance) {
  16. 		// outputs the options form on admin
  17. 	}
  18. }
  19. register_widget('My_Widget');
  20. ?>

And here is my finished widget (evolved from the above example).

  1. <?php
  2. /*
  3.  * Custom mini-post widget
  4.  */
  5. class FGX_MiniPost_Widget extends WP_Widget {
  6. 	function FGX_MiniPost_Widget() {
  7. 		// widget actual processes
  8. 		parent::WP_Widget(false, $name = 'Floragenex MiniPost', array(
  9. 			'description' => 'Display a teaser for a post or a page.'
  10. 		));
  11. 	}
  12.  
  13. 	function widget($args, $instance) {
  14. 		// outputs the content of the widget
  15. 			global $post;
  16.  
  17. 			extract( $args );
  18. 			$type = $instance['type'];
  19. 			$include = (!empty($instance['include']) ? explode(',', $instance['include']) : '');
  20. 			$category = (is_numeric($instance['category']) ? (int)$instance['category'] : '');
  21.  
  22.                         // Set up query for posts with the provided filters
  23. 			query_posts(array(
  24. 				'post_type' => $type,
  25. 				'post__in' => $include,
  26. 				'post__not_in' => array($post->ID),
  27. 				'cat' => $category,
  28. 				'post_status' => 'publish',
  29. 				'meta_key' => 'teaser_value',
  30. 				'meta_value' => '',
  31. 				'meta_compare' => '!=',
  32. 				'orderby' => 'rand',
  33. 				'posts_per_page' => '1'
  34. 			));
  35.  
  36. 			echo $before_widget;
  37.  
  38.                         // Output widget, if a post exists that matches our query
  39. 			if ( have_posts() ) :
  40. 				while ( have_posts() ) : the_post();
  41. 					$post_meta = get_post_custom($post->ID);
  42. 					echo (!empty($post_meta['image_value'][0]) ? '<a href="' . get_page_link() . '">' .
  43. 							 '<img alt="image" src="' . get_bloginfo('template_url') . '/scripts/timthumb.php?src=' . htmlentities($post_meta['image_value'][0]) . '&h=62&w=180&zc=1" />' .
  44. 							 '</a>' : '') .
  45. 							 '<h3><a href="' . get_page_link() . '">' . get_the_title() . '</a></h3>' .
  46. 							 '<p>' . htmlentities($post_meta['teaser_value'][0]) . '</p>' .
  47. 							 '<p><a class="learn-more" href="' . get_page_link() . '">learn more</a></p>';
  48. 				endwhile;
  49. 			else:
  50. 				echo '<p>No posts found.</p>';
  51. 			endif;
  52.  
  53.                         // Very important to reset the query here.
  54. 			wp_reset_query();
  55.  
  56. 			echo $after_widget;
  57. 	}
  58.  
  59. 	function update($new_instance, $old_instance) {
  60. 		// processes widget options to be saved
  61. 		return $new_instance;
  62. 	}
  63.  
  64. 	function form($instance) {
  65. 		// outputs the options form on admin
  66. 		$type = esc_attr($instance['type']);
  67. 		$include = esc_attr($instance['include']);
  68. 		$category = esc_attr($instance['category']);
  69.  
  70.                 // Get the existing categories and build a simple select dropdown for the user.
  71. 		$categories = get_categories();
  72.  
  73. 		$cat_options = array();
  74. 		$cat_options[] = '<option value="BLANK">Select one...</option>';
  75.  
  76. 		foreach ($categories as $cat) {
  77. 			$selected = $category === $cat->cat_ID ? ' selected="selected"' : '';
  78. 			$cat_options[] = '<option value="' . $cat->cat_ID .'"' . $selected . '>' . $cat->name . '</option>';
  79. 		}
  80.  
  81. 		?>
  82. 			<p>
  83. 				<label for="<?php echo $this->get_field_id('type'); ?>">
  84. 					<?php _e('Content type:'); ?>
  85. 				</label>
  86. 				<select id="<?php echo $this->get_field_id('type'); ?>" class="widefat" name="<?php echo $this->get_field_name('type'); ?>">
  87. 					<option value="post"<?php echo ($type === 'post' ? ' selected="selected"' : ''); ?>>Post</option>
  88. 					<option value="page"<?php echo ($type === 'page' ? ' selected="selected"' : ''); ?>>Page</option>
  89. 				</select>
  90. 			</p>
  91. 			<p>
  92. 				<label for="<?php echo $this->get_field_id('include'); ?>">
  93. 					<?php _e('Include post IDs (optional):'); ?>
  94. 				</label>
  95. 				<input id="<?php echo $this->get_field_id('include'); ?>" class="widefat" type="text" name="<?php echo $this->get_field_name('include'); ?>" value="<?php echo $include; ?>" />
  96. 			</p>
  97. 			<p>
  98. 				<label for="<?php echo $this->get_field_id('category'); ?>">
  99. 					<?php _e('Include category (optional):'); ?>
  100. 				</label>
  101. 				<select id="<?php echo $this->get_field_id('category'); ?>" class="widefat" name="<?php echo $this->get_field_name('category'); ?>">
  102. 					<?php echo implode('', $cat_options); ?>
  103. 				</select>
  104. 			</p>
  105. 		<?php
  106. 	}
  107. }
  108.  
  109. // register widget
  110. register_widget('FGX_MiniPost_Widget');
  111. ?>

Just paste this code in your theme’s functions.php file and the widget should appear under your available widgets.

This was a quick braindump post, so please feel free to post with any specific questions.

2 thoughts on “Writing a custom WordPress multi-instance widget

  1. Hi, I just found this blog – thank you for the good work. I wanted to inform you that it’s not showing up properly on the BlackBerry Browser (I have a Pearl). Either way, I’m now subscribed to the RSS feed on my PC, so thanks again!

    • Hey Jack, glad my blog has been useful to you. I tried accessing my blog on a BlackBerry Pearl and it looks like the browser appends a “www.” to the front of the URL. This results in an incorrect URL of “www.blog.twaddington.com” instead of “blog.twaddington.com.” This might have been the issue you ran into.

      Let me know if you have any requests for post topics!

Comments are closed.