Advertisement
Widgets

Writing Maintainable WordPress Widgets: Part 1 of 2

by

Best of Wptuts 2011: Every week through January, we're going to revisit some of our favorite posts from 2011. Plugin development can often feel like the wild west if you're creating something from scratch without a boilerplate or a similar plugin to work from - Tom's 2 part series on Maintainable WordPress Widgets/Plugins offers some practical guidelines that should keep you on the tracks!

When it comes to software development, frameworks and libraries are popular because they're helpful, right? They provide a consistent way to write and organize code in hopes of making development and maintenance as easy as possible.


The thing is, these very same principles that apply to larger, enterprise level systems are just as applicable to smaller projects - such as WordPress plugins - developed by a small teams. And just as larger systems are full of moving parts, such is the case with WordPress plugins.

For example: you've got the core code that is responsible for communicating with WordPress (via filters, actions, and hooks), administration dashboards, client-side views, JavaScript files, style sheets, localization files, and so on all of which are achieved using at least four different programming languages.

During my time spent in WordPress Development, I've created a few boilerplates that I use to begin each of my projects. This tutorial will take a look at my WordPress Widget Boilerplate code, how to leverage it in new projects, and an example application in hopes of helping you get your next WordPress project off to a solid start.


A Widget Boilerplate

Organization

When it comes to development, I typically try to keep things as simple as possible by planning only for the necessary features; however, this is one case in which I aim to be exhaustive. It's almost always easier to begin planning a boilerplate when you know all of the components that may go into the system.

A plugin can ultimately consist of the following:

  • Core plugin code
  • Style sheets
  • Java Scripts
  • Localization files
  • Markup
  • Images

Taking all of the above into consideration, the widget boilerplate directory is laid out like this:

We'll take a look at each directory in detail later in the article.

The Skeleton

In addition to file organization, I also like to stub out the code used to drive the widget. The WordPress Codex[1] has a detailed explanation of the Widget API[2] and because there is a suggest way to craft them, I try to follow it.

Additionally, I'm a fan of writing my code in an object-oriented manner along with code comments to help explain what's going on in each area of the code. As such, the initial widget code looks like this:

init_plugin_constants();
  
    // TODO: update classname and description
		$widget_opts = array (
			'classname' => PLUGIN_NAME, 
			'description' => __('Short description of the plugin goes here.', PLUGIN_LOCALE)
		);	
		
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
		
    // Load JavaScript and stylesheets
    $this->register_scripts_and_styles();
		
	} // end constructor

	/*--------------------------------------------------*/
	/* API Functions
	/*--------------------------------------------------*/
	
	/**
	 * Outputs the content of the widget.
	 *
	 * @args			The array of form elements
	 * @instance
	 */
	function widget($args, $instance) {
	
		extract($args, EXTR_SKIP);
		
		echo $before_widget;
		
    // TODO: This is where you retrieve the widget values
    
		// Display the widget
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
		
		echo $after_widget;
		
	} // end widget
	
	/**
	 * Processes the widget's options to be saved.
	 *
	 * @new_instance	The previous instance of values before the update.
	 * @old_instance	The new instance of values to be generated via the update.
	 */
	function update($new_instance, $old_instance) {
		
		$instance = $old_instance;
		
    // TODO Update the widget with the new values
    
		return $instance;
		
	} // end widget
	
	/**
	 * Generates the administration form for the widget.
	 *
	 * @instance	The array of keys and values for the widget.
	 */
	function form($instance) {
	
    // TODO define default values for your variables
		$instance = wp_parse_args(
			(array)$instance,
			array(
				'' => ''
			)
		);
	
    // TODO store the values of widget in a variable
		
		// Display the admin form
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
		
	} // end form
	
	/*--------------------------------------------------*/
	/* Private Functions
	/*--------------------------------------------------*/
	
  /**
   * Initializes constants used for convenience throughout 
   * the plugin.
   */
  private function init_plugin_constants() {
    
    /* TODO
     * 
     * This provides the unique identifier for your plugin used in
     * localizing the strings used throughout.
     * 
     * For example: wordpress-widget-boilerplate-locale.
     */
    if(!defined('PLUGIN_LOCALE')) {
      define('PLUGIN_LOCALE', 'plugin-name-locale');
    } // end if
    
    /* TODO
     * 
     * Define this as the name of your plugin. This is what shows
     * in the Widgets area of WordPress.
     * 
     * For example: WordPress Widget Boilerplate.
     */
    if(!defined('PLUGIN_NAME')) {
      define('PLUGIN_NAME', 'Plugin Name');
    } // end if
    
    /* TODO
     * 
     * this is the slug of your plugin used in initializing it with
     * the WordPress API.
     
     * This should also be the
     * directory in which your plugin resides. Use hyphens.
     * 
     * For example: wordpress-widget-boilerplate
     */
    if(!defined('PLUGIN_SLUG')) {
      define('PLUGIN_SLUG', 'plugin-name-slug');
    } // end if
  
  } // end init_plugin_constants
  
	/**
	 * Registers and enqueues stylesheets for the administration panel and the
	 * public facing site.
	 */
	private function register_scripts_and_styles() {
		if(is_admin()) {
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true);
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css');
		} else { 
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true);
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css');
		} // end if/else
	} // end register_scripts_and_styles

	/**
	 * Helper function for registering and enqueueing scripts and styles.
	 *
	 * @name	The 	ID to register with WordPress
	 * @file_path		The path to the actual file
	 * @is_script		Optional argument for if the incoming file_path is a JavaScript source file.
	 */
	private function load_file($name, $file_path, $is_script = false) {
		
    $url = WP_PLUGIN_URL . $file_path;
		$file = WP_PLUGIN_DIR . $file_path;
    
		if(file_exists($file)) {
			if($is_script) {
				wp_register_script($name, $url);
				wp_enqueue_script($name);
			} else {
				wp_register_style($name, $url);
				wp_enqueue_style($name);
			} // end if
		} // end if
    
	} // end load_file
	
} // end class
add_action('widgets_init', create_function('', 'register_widget("Plugin_Name");')); // TODO remember to change this to match the class definition above
?>

Notice there are a number of TODO's throughout the code. These are useful especially in the context of writing your code on top of the boilerplate.

Note that there are three primary sections of code, as well:

  1. Constructor. This function is responsible for initializing the widget, importing localizing files, and including JavaScript sources and style sheets.
  2. API Functions. These functions are the three functions required for administering, displaying, and updating the widget.
  3. Helper Functions. These are private functions that I use to help with often repetitive or required tasks.

The three most important functions above, the API functions are required for developing your plugin.

  1. widget() extracts the stored values and rendering the public view
  2. update() is responsible for updating the previously saved values with the values provided by the user
  3. form() renders the administration form and provides functionality necessary for storing new values.

Because plugins are often divided between the administration functionality and the client-facing functionality, I divide my JavaScript source, style sheets, and HTML accordingly. I name these files accordingly and stub them out appropriately:

JavaScript Sources:

admin.js:

jQuery(function($) {
  // Place your administration-specific code here
});

widget.js:

jQuery(function($) {
  // Place your public facing JavaScript here
});

Style Sheets:

admin.css:

/* This style sheet is used to style the admin option form of the widget. */

widget.css:

/* This style sheet is used to style the public view of the widget. */

Views:


Easy, right? You can view (and fork!) this entire boilerplate including the localization files and the README on GitHub.

There's now a place for everything and when it comes time to ship, you just exclude certain files from the final build..


A Working Example With Your Social Networks

When it comes to programming, practice helps in learning a new language or tip so here's a quick example of how to use the above boilerplate to create a simple widget for making it easy to share your Twitter, Facebook, and Google+ links.

First, we'll list out the requirements:

  • An administration view for entering values. This includes markup and styles.
  • A public facing view for displaying links to social networks. This also includes markup and styles.
  • Options for storing a Twitter username, Facebook username, and Google+ ID

Secondly, let's open up the boilerplate and begin stubbing out the necessary parts.

First, we define out plugin name, slug, and locale values. These are used repeatedly throughout the code so it's nice to store them as constants for easily retrieving them. Locate the init_plugin_constants() function and make sure that your code looks like this:

  private function init_plugin_constants() {

  if(!defined('PLUGIN_LOCALE')) {
    define('PLUGIN_LOCALE', 'my-social-network-locale');
  } // end if

  if(!defined('PLUGIN_NAME')) {
    define('PLUGIN_NAME', 'My Social Networks');
  } // end if

  if(!defined('PLUGIN_SLUG')) {
    define('PLUGIN_SLUG', 'My-Social-Networks');
  } // end if
  
} // end init_plugin_constants

After that, we need to prepare the constructor:

	function My_Social_Network() {

    // Define constants used throughout the plugin
    $this->init_plugin_constants();
  
		$widget_opts = array (
			'classname' => PLUGIN_NAME, 
			'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE)
		);	
		
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
		
    // Load JavaScript and stylesheets
    $this->register_scripts_and_styles();
		
	} // end constructor

And stub out the API functions:

	function widget($args, $instance) {
	
		extract($args, EXTR_SKIP);
		
		echo $before_widget;
		
    $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']);
		$facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']);
		$google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']);
    
		// Display the widget
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
		
		echo $after_widget;
		
	} // end widget

	function update($new_instance, $old_instance) {
		
		$instance = $old_instance;
		
    $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username']));
    $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username']));
    $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id']));
    
		return $instance;
		
	} // end widget
	
	function form($instance) {
	
		$instance = wp_parse_args(
			(array)$instance,
			array(
				'twitter_username' => '',
        'facebook_username' => '',
        'google_plus_id' => ''
			)
		);
    
    $twitter_username = strip_tags(stripslashes($new_instance['twitter_username']));
    $facebook_username = strip_tags(stripslashes($new_instance['facebook_username']));
    $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id']));

		// Display the admin form
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
		
	} // end form

The final version of the plugin should look like this:

 init_plugin_constants();
  
		$widget_opts = array (
			'classname' => PLUGIN_NAME, 
			'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE)
		);	
		
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
		
    // Load JavaScript and stylesheets
    $this->register_scripts_and_styles();
		
	} // end constructor

	/*--------------------------------------------------*/
	/* API Functions
	/*--------------------------------------------------*/
	
	/**
	 * Outputs the content of the widget.
	 *
	 * @args			The array of form elements
	 * @instance
	 */
	function widget($args, $instance) {
	
		extract($args, EXTR_SKIP);
		
		echo $before_widget;
		
    $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']);
		$facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']);
		$google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']);
    
		// Display the widget
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
		
		echo $after_widget;
		
	} // end widget
	
	/**
	 * Processes the widget's options to be saved.
	 *
	 * @new_instance	The previous instance of values before the update.
	 * @old_instance	The new instance of values to be generated via the update.
	 */
	function update($new_instance, $old_instance) {
		
		$instance = $old_instance;
		
    $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username']));
    $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username']));
    $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id']));
    
		return $instance;
		
	} // end widget
	
	/**
	 * Generates the administration form for the widget.
	 *
	 * @instance	The array of keys and values for the widget.
	 */
	function form($instance) {
	
		$instance = wp_parse_args(
			(array)$instance,
			array(
				'twitter_username' => '',
        'facebook_username' => '',
        'google_plus_id' => ''
			)
		);
    
    $twitter_username = strip_tags(stripslashes($new_instance['twitter_username']));
    $facebook_username = strip_tags(stripslashes($new_instance['facebook_username']));
    $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id']));

		// Display the admin form
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
		
	} // end form
	
	/*--------------------------------------------------*/
	/* Private Functions
	/*--------------------------------------------------*/
	
  /**
   * Initializes constants used for convenience throughout 
   * the plugin.
   */
  private function init_plugin_constants() {

    if(!defined('PLUGIN_LOCALE')) {
      define('PLUGIN_LOCALE', 'my-social-network-locale');
    } // end if

    if(!defined('PLUGIN_NAME')) {
      define('PLUGIN_NAME', 'My Social Networks');
    } // end if

    if(!defined('PLUGIN_SLUG')) {
      define('PLUGIN_SLUG', 'My-Social-Networks');
    } // end if
  
  } // end init_plugin_constants
  
	/**
	 * Registers and enqueues stylesheets for the administration panel and the
	 * public facing site.
	 */
	private function register_scripts_and_styles() {
		if(is_admin()) {
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true);
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css');
		} else { 
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true);
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css');
		} // end if/else
	} // end register_scripts_and_styles

	/**
	 * Helper function for registering and enqueueing scripts and styles.
	 *
	 * @name	The 	ID to register with WordPress
	 * @file_path		The path to the actual file
	 * @is_script		Optional argument for if the incoming file_path is a JavaScript source file.
	 */
	private function load_file($name, $file_path, $is_script = false) {
		
    $url = WP_PLUGIN_URL . $file_path;
		$file = WP_PLUGIN_DIR . $file_path;
    
		if(file_exists($file)) {
			if($is_script) {
				wp_register_script($name, $url);
				wp_enqueue_script($name);
			} else {
				wp_register_style($name, $url);
				wp_enqueue_style($name);
			} // end if
		} // end if
    
	} // end load_file
	
} // end class
add_action('widgets_init', create_function('', 'register_widget("My_Social_Network");'));
?>

Next, let's add some styles to the administration form. Locate /css/admin.css and add the following code:

.wrapper fieldset { border: 1px solid #ddd; width: 90%; padding: 5%; }
.option { margin: 12px 0 12px 0; }
.option input { width: 100%; }

And let's write the markup that will render the view of the administration form:

Finally, we need to write some markup to render the public-facing view of the widget when it's live on the actual blog:

    0) { ?>
  • 0) { ?>
  • 0) { ?>

Done and done. Not bad, huh? A fair amount of work and functionality done relatively quickly.

You can download the working source code (including an associated README) for this widget on GitHub or right here at Wptuts.

Ultimately, maintaining a software projects amounts to trying to organize complexity. Although the above boilerplate is not *the* way the organize or manage code, it's an *effective* way to organize code and I've found is extremely helpful in many of my projects and hopefully it helps you with your future work.

Remember, you can grab a copy of both the boilerplate and the example project from their respective GitHub repositories. I also highly recommend bookmarking the WordPress Codex[1]. It's a tremendous resource for anyone seeking to do advanced WordPress development.

  1. http://codex.wordpress.org
  2. http://codex.wordpress.org/Widgets_API

Moving on to Part Two...

Check out the second part of this tutorial series where we'll be digging deeper into creating maintainable plugins! We'll be looking at how to use hooks within WordPress - and then we'll actually put our boilerplate to use to create another useful plugin. Ready for Part Two?

Related Posts
  • Code
    Theme Development
    Creating a WordPress Theme from Static HTML - Adding WidgetsCreating wordpress theme from html 400
    In this series, you've learned how to convert a static HTML file to a WordPress theme and edit the header file. So far you've: prepared your markup for WordPress converted your HTML to PHP and split your file into template files edited the stylesheet and uploaded your theme to WordPress added a loop to your index file added meta tags, the wp_head hook and the site title and description to your header file added a navigation menu. Read More…
  • Code
    Plugins
    A Better Forum List Widget for bbPressBbpress
    When bbPress was still a standalone installation, I had tried it out and wasn't really impressed. Things were clunky and it didn't always work the way it was supposed to. After languishing for a few years, Automattic decided to take bbpress and turn it into a plugin, improving the functionality leaps and bounds and making it a strong contender amongst other forum option for WordPress.Read More…
  • Code
    Widgets
    Building a Countdown Timer WidgetBuilding a countdown timer widget preview
    We all love a celebration, and now that Easter is over, we look forward to the next occasion. How far away is the next occasion you like to celebrate? Whatever it might be, let's build a widget plugin to add a countdown timer to our sidebar showing how long we have left to wait.Read More…
  • Code
    Creative Coding
    Developing Plugins With WordPress Boilerplates: Building a PluginDeveloping plugins with wordpress boilerplates building a plugin
    In the first article of this series, we looked at how a boilerplate can improve your development efforts by providing a foundation off of which your project can be built. Ideally, boilerplates should provide just enough of a framework to get started while letting you focus on the specific business logic, core need, or domain-specific code that you need to write. Specifically, we took a look at the WordPress Widget Boilerplates and the WordPress Plugin Boilerplate. In this post, we're going to take advantage of the Plugin Boilerplate to write our own plugin in order to see how Boilerplates both lay the foundation for writing good code, and how we can use it as a starting place for our future work.Read More…
  • Code
    Widgets
    Twitter Trends Widget for WordPressPreview
    In this tutorial we are going to create a WordPress widget that displays Twitter Trends by region. We will use Twitter's Trends API and update trends after a specific time duration using WordPress' Transients API.Read More…
  • Code
    Plugins
    Create a TwitterRSS Stats Widget for WordPressPreview
    In this tutorial, I’ll walk you through the steps of setting up a widget, its settings form, and displaying it on your site. At the end of the tutorial, you can download an example plugin to build from. Of course, you can apply this to your themes as well.Read More…