1. Code
  2. PHP

Writing Maintainable WordPress Widgets: Part 1 of 2

Scroll to top
15 min read

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:

1
2
<?php
3
/*

4
Plugin Name: TODO

5
Plugin URI: TODO

6
Description: TODO

7
Version: 1.0

8
Author: TODO

9
Author URI: TODO

10
Author Email: TODO

11
License:

12


13
  Copyright 2011 TODO (email@domain.com)

14


15
  This program is free software; you can redistribute it and/or modify

16
  it under the terms of the GNU General Public License, version 2, as 

17
  published by the Free Software Foundation.

18


19
  This program is distributed in the hope that it will be useful,

20
  but WITHOUT ANY WARRANTY; without even the implied warranty of

21
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

22
  GNU General Public License for more details.

23


24
  You should have received a copy of the GNU General Public License

25
  along with this program; if not, write to the Free Software

26
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

27
*/
28
29
// TODO: change 'Plugin_Name' to the name of your actual plugin

30
class Plugin_Name extends WP_Widget {
31
32
	/*--------------------------------------------------*/
33
	/* Constructor

34
	/*--------------------------------------------------*/
35
	
36
	/**

37
	 * The widget constructor. Specifies the classname and description, instantiates

38
	 * the widget, loads localization files, and includes necessary scripts and

39
	 * styles.

40
	 */
41
  // TODO: This should match the title given in the class definition above.

42
	function Plugin_Name() {
43
44
    // Define constnats used throughout the plugin

45
    $this->init_plugin_constants();
46
  
47
    // TODO: update classname and description

48
		$widget_opts = array (
49
			'classname' => PLUGIN_NAME, 
50
			'description' => __('Short description of the plugin goes here.', PLUGIN_LOCALE)
51
		);	
52
		
53
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
54
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
55
		
56
    // Load JavaScript and stylesheets

57
    $this->register_scripts_and_styles();
58
		
59
	} // end constructor

60
61
	/*--------------------------------------------------*/
62
	/* API Functions

63
	/*--------------------------------------------------*/
64
	
65
	/**

66
	 * Outputs the content of the widget.

67
	 *

68
	 * @args			The array of form elements

69
	 * @instance

70
	 */
71
	function widget($args, $instance) {
72
	
73
		extract($args, EXTR_SKIP);
74
		
75
		echo $before_widget;
76
		
77
    // TODO: This is where you retrieve the widget values

78
    
79
		// Display the widget

80
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
81
		
82
		echo $after_widget;
83
		
84
	} // end widget

85
	
86
	/**

87
	 * Processes the widget's options to be saved.

88
	 *

89
	 * @new_instance	The previous instance of values before the update.

90
	 * @old_instance	The new instance of values to be generated via the update.

91
	 */
92
	function update($new_instance, $old_instance) {
93
		
94
		$instance = $old_instance;
95
		
96
    // TODO Update the widget with the new values

97
    
98
		return $instance;
99
		
100
	} // end widget

101
	
102
	/**

103
	 * Generates the administration form for the widget.

104
	 *

105
	 * @instance	The array of keys and values for the widget.

106
	 */
107
	function form($instance) {
108
	
109
    // TODO define default values for your variables

110
		$instance = wp_parse_args(
111
			(array)$instance,
112
			array(
113
				'' => ''
114
			)
115
		);
116
	
117
    // TODO store the values of widget in a variable

118
		
119
		// Display the admin form

120
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
121
		
122
	} // end form

123
	
124
	/*--------------------------------------------------*/
125
	/* Private Functions

126
	/*--------------------------------------------------*/
127
	
128
  /**

129
   * Initializes constants used for convenience throughout 

130
   * the plugin.

131
   */
132
  private function init_plugin_constants() {
133
    
134
    /* TODO

135
     * 

136
     * This provides the unique identifier for your plugin used in

137
     * localizing the strings used throughout.

138
     * 

139
     * For example: wordpress-widget-boilerplate-locale.

140
     */
141
    if(!defined('PLUGIN_LOCALE')) {
142
      define('PLUGIN_LOCALE', 'plugin-name-locale');
143
    } // end if

144
    
145
    /* TODO

146
     * 

147
     * Define this as the name of your plugin. This is what shows

148
     * in the Widgets area of WordPress.

149
     * 

150
     * For example: WordPress Widget Boilerplate.

151
     */
152
    if(!defined('PLUGIN_NAME')) {
153
      define('PLUGIN_NAME', 'Plugin Name');
154
    } // end if

155
    
156
    /* TODO

157
     * 

158
     * this is the slug of your plugin used in initializing it with

159
     * the WordPress API.

160
     

161
     * This should also be the

162
     * directory in which your plugin resides. Use hyphens.

163
     * 

164
     * For example: wordpress-widget-boilerplate

165
     */
166
    if(!defined('PLUGIN_SLUG')) {
167
      define('PLUGIN_SLUG', 'plugin-name-slug');
168
    } // end if

169
  
170
  } // end init_plugin_constants

171
  
172
	/**

173
	 * Registers and enqueues stylesheets for the administration panel and the

174
	 * public facing site.

175
	 */
176
	private function register_scripts_and_styles() {
177
		if(is_admin()) {
178
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true);
179
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css');
180
		} else { 
181
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true);
182
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css');
183
		} // end if/else

184
	} // end register_scripts_and_styles

185
186
	/**

187
	 * Helper function for registering and enqueueing scripts and styles.

188
	 *

189
	 * @name	The 	ID to register with WordPress

190
	 * @file_path		The path to the actual file

191
	 * @is_script		Optional argument for if the incoming file_path is a JavaScript source file.

192
	 */
193
	private function load_file($name, $file_path, $is_script = false) {
194
		
195
    $url = WP_PLUGIN_URL . $file_path;
196
		$file = WP_PLUGIN_DIR . $file_path;
197
    
198
		if(file_exists($file)) {
199
			if($is_script) {
200
				wp_register_script($name, $url);
201
				wp_enqueue_script($name);
202
			} else {
203
				wp_register_style($name, $url);
204
				wp_enqueue_style($name);
205
			} // end if

206
		} // end if

207
    
208
	} // end load_file

209
	
210
} // end class

211
add_action('widgets_init', create_function('', 'register_widget("Plugin_Name");')); // TODO remember to change this to match the class definition above

212
?>

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:

1
2
jQuery(function($) {
3
  // Place your administration-specific code here

4
});

widget.js:

1
2
jQuery(function($) {
3
  // Place your public facing JavaScript here

4
});

Style Sheets:

admin.css:

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

widget.css:

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

Views:

1
2
<!-- This file is used to markup the administration form of the widget. -->
3
<!-- This file is used to markup the public facing widget. -->

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:

1
2
  private function init_plugin_constants() {
3
4
  if(!defined('PLUGIN_LOCALE')) {
5
    define('PLUGIN_LOCALE', 'my-social-network-locale');
6
  } // end if

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

11
12
  if(!defined('PLUGIN_SLUG')) {
13
    define('PLUGIN_SLUG', 'My-Social-Networks');
14
  } // end if

15
  
16
} // end init_plugin_constants

After that, we need to prepare the constructor:

1
2
	function My_Social_Network() {
3
4
    // Define constants used throughout the plugin

5
    $this->init_plugin_constants();
6
  
7
		$widget_opts = array (
8
			'classname' => PLUGIN_NAME, 
9
			'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE)
10
		);	
11
		
12
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
13
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
14
		
15
    // Load JavaScript and stylesheets

16
    $this->register_scripts_and_styles();
17
		
18
	} // end constructor

And stub out the API functions:

1
2
	function widget($args, $instance) {
3
	
4
		extract($args, EXTR_SKIP);
5
		
6
		echo $before_widget;
7
		
8
    $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']);
9
		$facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']);
10
		$google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']);
11
    
12
		// Display the widget

13
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
14
		
15
		echo $after_widget;
16
		
17
	} // end widget

18
19
	function update($new_instance, $old_instance) {
20
		
21
		$instance = $old_instance;
22
		
23
    $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username']));
24
    $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username']));
25
    $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id']));
26
    
27
		return $instance;
28
		
29
	} // end widget

30
	
31
	function form($instance) {
32
	
33
		$instance = wp_parse_args(
34
			(array)$instance,
35
			array(
36
				'twitter_username' => '',
37
        'facebook_username' => '',
38
        'google_plus_id' => ''
39
			)
40
		);
41
    
42
    $twitter_username = strip_tags(stripslashes($new_instance['twitter_username']));
43
    $facebook_username = strip_tags(stripslashes($new_instance['facebook_username']));
44
    $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id']));
45
46
		// Display the admin form

47
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
48
		
49
	} // end form

The final version of the plugin should look like this:

1
2
 <?php
3
/*

4
Plugin Name: My Social Network

5
Plugin URI: http://github.com/tommcfarlin/My-Social-Network

6
Description: A simple WordPress widget for sharing a few of your social networks.

7
Version: 1.0

8
Author: Tom McFarlin

9
Author URI: http://tommcfarlin.com

10
Author Email: tom@tommcfarlin.com

11
License:

12


13
  Copyright 2011 My Social Network (tom@tommcfarlin.com)

14


15
  This program is free software; you can redistribute it and/or modify

16
  it under the terms of the GNU General Public License, version 2, as 

17
  published by the Free Software Foundation.

18


19
  This program is distributed in the hope that it will be useful,

20
  but WITHOUT ANY WARRANTY; without even the implied warranty of

21
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

22
  GNU General Public License for more details.

23


24
  You should have received a copy of the GNU General Public License

25
  along with this program; if not, write to the Free Software

26
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

27
*/
28
29
30
class My_Social_Network extends WP_Widget {
31
32
	/*--------------------------------------------------*/
33
	/* Constructor

34
	/*--------------------------------------------------*/
35
	
36
	/**

37
	 * The widget constructor. Specifies the classname and description, instantiates

38
	 * the widget, loads localization files, and includes necessary scripts and

39
	 * styles.

40
	 */
41
	function My_Social_Network() {
42
43
    // Define constants used throughout the plugin

44
    $this->init_plugin_constants();
45
  
46
		$widget_opts = array (
47
			'classname' => PLUGIN_NAME, 
48
			'description' => __('A simple WordPress widget for sharing a few of your social networks.', PLUGIN_LOCALE)
49
		);	
50
		
51
		$this->WP_Widget(PLUGIN_SLUG, __(PLUGIN_NAME, PLUGIN_LOCALE), $widget_opts);
52
		load_plugin_textdomain(PLUGIN_LOCALE, false, dirname(plugin_basename( __FILE__ ) ) . '/lang/' );
53
		
54
    // Load JavaScript and stylesheets

55
    $this->register_scripts_and_styles();
56
		
57
	} // end constructor

58
59
	/*--------------------------------------------------*/
60
	/* API Functions

61
	/*--------------------------------------------------*/
62
	
63
	/**

64
	 * Outputs the content of the widget.

65
	 *

66
	 * @args			The array of form elements

67
	 * @instance

68
	 */
69
	function widget($args, $instance) {
70
	
71
		extract($args, EXTR_SKIP);
72
		
73
		echo $before_widget;
74
		
75
    $twitter_username = empty($instance['twitter_username']) ? '' : apply_filters('twitter_username', $instance['twitter_username']);
76
		$facebook_username = empty($instance['facebook_username']) ? '' : apply_filters('facebook_username', $instance['facebook_username']);
77
		$google_plus_id = empty($instance['google_plus_id']) ? '' : apply_filters('google_plus_id', $instance['google_plus_id']);
78
    
79
		// Display the widget

80
		include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/widget.php');
81
		
82
		echo $after_widget;
83
		
84
	} // end widget

85
	
86
	/**

87
	 * Processes the widget's options to be saved.

88
	 *

89
	 * @new_instance	The previous instance of values before the update.

90
	 * @old_instance	The new instance of values to be generated via the update.

91
	 */
92
	function update($new_instance, $old_instance) {
93
		
94
		$instance = $old_instance;
95
		
96
    $instance['twitter_username'] = strip_tags(stripslashes($new_instance['twitter_username']));
97
    $instance['facebook_username'] = strip_tags(stripslashes($new_instance['facebook_username']));
98
    $instance['google_plus_id'] = strip_tags(stripslashes($new_instance['google_plus_id']));
99
    
100
		return $instance;
101
		
102
	} // end widget

103
	
104
	/**

105
	 * Generates the administration form for the widget.

106
	 *

107
	 * @instance	The array of keys and values for the widget.

108
	 */
109
	function form($instance) {
110
	
111
		$instance = wp_parse_args(
112
			(array)$instance,
113
			array(
114
				'twitter_username' => '',
115
        'facebook_username' => '',
116
        'google_plus_id' => ''
117
			)
118
		);
119
    
120
    $twitter_username = strip_tags(stripslashes($new_instance['twitter_username']));
121
    $facebook_username = strip_tags(stripslashes($new_instance['facebook_username']));
122
    $google_plus_id = strip_tags(stripslashes($new_instance['google_plus_id']));
123
124
		// Display the admin form

125
    include(WP_PLUGIN_DIR . '/' . PLUGIN_SLUG . '/views/admin.php');
126
		
127
	} // end form

128
	
129
	/*--------------------------------------------------*/
130
	/* Private Functions

131
	/*--------------------------------------------------*/
132
	
133
  /**

134
   * Initializes constants used for convenience throughout 

135
   * the plugin.

136
   */
137
  private function init_plugin_constants() {
138
139
    if(!defined('PLUGIN_LOCALE')) {
140
      define('PLUGIN_LOCALE', 'my-social-network-locale');
141
    } // end if

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

146
147
    if(!defined('PLUGIN_SLUG')) {
148
      define('PLUGIN_SLUG', 'My-Social-Networks');
149
    } // end if

150
  
151
  } // end init_plugin_constants

152
  
153
	/**

154
	 * Registers and enqueues stylesheets for the administration panel and the

155
	 * public facing site.

156
	 */
157
	private function register_scripts_and_styles() {
158
		if(is_admin()) {
159
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.js', true);
160
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/admin.css');
161
		} else { 
162
      $this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/js/admin.css', true);
163
			$this->load_file(PLUGIN_NAME, '/' . PLUGIN_SLUG . '/css/widget.css');
164
		} // end if/else

165
	} // end register_scripts_and_styles

166
167
	/**

168
	 * Helper function for registering and enqueueing scripts and styles.

169
	 *

170
	 * @name	The 	ID to register with WordPress

171
	 * @file_path		The path to the actual file

172
	 * @is_script		Optional argument for if the incoming file_path is a JavaScript source file.

173
	 */
174
	private function load_file($name, $file_path, $is_script = false) {
175
		
176
    $url = WP_PLUGIN_URL . $file_path;
177
		$file = WP_PLUGIN_DIR . $file_path;
178
    
179
		if(file_exists($file)) {
180
			if($is_script) {
181
				wp_register_script($name, $url);
182
				wp_enqueue_script($name);
183
			} else {
184
				wp_register_style($name, $url);
185
				wp_enqueue_style($name);
186
			} // end if

187
		} // end if

188
    
189
	} // end load_file

190
	
191
} // end class

192
add_action('widgets_init', create_function('', 'register_widget("My_Social_Network");'));
193
?>

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

1
2
.wrapper fieldset { border: 1px solid #ddd; width: 90%; padding: 5%; }

3
.option { margin: 12px 0 12px 0; }
4
.option input { width: 100%; }

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

1
2
<div class="wrapper">
3
  <fieldset>
4
    <legend>
5
      <?php _e('My Social Networks', PLUGIN_LOCALE); ?>
6
    </legend>
7
    
8
    <div class="option">
9
      <label for="twitter">
10
        <?php _e('Twitter Username', PLUGIN_LOCALE); ?>
11
      </label>
12
      <input type="text" id="<?php echo $this->get_field_id('twitter_username'); ?>" name="<?php echo $this->get_field_name('twitter_username'); ?>" value="<?php echo $instance['twitter_username']; ?>" class="" />
13
    </div>
14
    
15
    <div class="option">
16
      <label for="facebook">
17
        <?php _e('Facebook Username', PLUGIN_LOCALE); ?>
18
      </label>
19
      <input type="text" id="<?php echo $this->get_field_id('facebook_username'); ?>" name="<?php echo $this->get_field_name('facebook_username'); ?>" value="<?php echo $instance['facebook_username']; ?>" class="" />
20
    </div>
21
    
22
    <div class="option">
23
      <label for="google_plus">
24
        <?php _e('Google+ ID', PLUGIN_LOCALE); ?>
25
      </label>
26
      <input type="text" id="<?php echo $this->get_field_id('google_plus_id'); ?>" name="<?php echo $this->get_field_name('google_plus_id'); ?>" value="<?php echo $instance['google_plus_id']; ?>" class="" />
27
    </div>
28
    
29
  </fieldset>
30
</div><!-- /wrapper -->

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

1
2
<h3>
3
  <?php _e('My Social Networks', PLUGIN_LOCALE); ?>
4
</h3>
5
<ul class="my-social-networks">
6
7
  <?php if(strlen(trim($twitter_username)) > 0) { ?>
8
    <li>
9
      <a href="http://twitter.com/<?php echo $twitter_username; ?>">
10
        <?php _e('Twitter', PLUGIN_LOCALE); ?>
11
      </a>
12
    </li>
13
  <?php } // end if ?>

14
  
15
  <?php if(strlen(trim($facebook_username)) > 0) { ?>
16
    <li>
17
      <a href="http://facebook.com/<?php echo $facebook_username; ?>">
18
        <?php _e('Facebook', PLUGIN_LOCALE); ?>
19
      </a>
20
    </li>
21
  <?php } // end if ?>

22
  
23
  <?php if(strlen(trim($google_plus_id)) > 0) { ?>
24
    <li>
25
      <a href="http://plus.google.com/<?php echo $google_plus_id; ?>">
26
        <?php _e('Google+', PLUGIN_LOCALE); ?>
27
      </a>
28
    </li>
29
  <?php } // end if ?>

30
  
31
</ul><!-- /my-social-networks -->

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?

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.