Creating Dynamic, User-Editable WordPress Widgets

WordPress Custom Widget

Here we’ll cover creating a dynamic widget: one that accepts, formats, and displays user input.

Today we’re continuing David’s discussion of widgets from a few weeks ago. In his post, David covered how to make a static widget: one that displays, say, a predefined block of images and text, or today’s date, but without input or customization by the user.

Here we’ll be covering creating a user-editable widget, which takes user input, formats it, and displays it. This would let you create, for example, a “social media widget” that lets the user choose some number of social media buttons to display, and to provide the relevant URLs for each button to link to. More generally, just about any truly useful widget is going to need some amount of user input.

To follow along, it’ll help if you’ve read David’s earlier piece. More generally, you’ll need basic knowledge of PHP functions, and of the principles of object-oriented programming (OOP) such as classes, objects, and inheritance. Let’s get started!

Our Widget Should Go in a Plugin

I recommend you register your custom widgets in one or more separate plugins. Otherwise, when you switch themes, you’ll lose your widgets.

First things first: Where’s the code for this widget going to go? Two obvious candidates exist: a custom plugin, and the functions.php file of the active theme.

I recommend you register your custom widgets in one or more separate plugins. Otherwise, when you switch themes, you’ll lose your widgets—which you definitely don’t want if they’re doing important stuff like serving a custom nav menu, a bunch of “buy now” buttons, etc. As we’ve argued recently, functions.php should confine itself to presentational (meaning, broadly, “visual”) functions that relate to the particular theme they’re part of. To do otherwise smells of theme creep.

The Goal: A “Now Listening” Widget With a Working Audio Player

Let’s say we’re running a music blog, and we’d like to add a “Now Listening” widget that gives the title of our current favorite song, plus a working HTML5 audio player to play the song itself, right from the widget.

Dynamic Data We’ll Need

We’ll need to make the widget accept two pieces of dynamic data:

  1. A text field describing the song (artist, song title, date, etc.).
  2. A link to an MP3 file of the song, which we’ll use for both the audio player and a download link.

How the Finished Product Should Look

The widget should take the two pieces of dynamic data listed above:

favorite-song-widget

And it should display them on the front end with a working HTML5 audio player and a download link:

Favorite Song Widget Front-End

Our Starting Point: A Static Widget

So we’ve figured out that we’re writing a plugin, and we’ve followed David’s advice on basic widget setup. That gets us something like this:

<?php

/*
Plugin Name: WPShout Test Widget
Description: A plugin to make a test tutorial widget
Version: 1.0
Author: WPShout
Author URI: https://wpshout.com/
*/

function wpshout_register_widgets() {
	register_widget( 'WPShout_Favorite_Song_Widget');
}
add_action( 'widgets_init', 'wpshout_register_widgets' );

class WPShout_Favorite_Song_Widget extends WP_Widget {

	function WPShout_Favorite_Song_Widget() {
		// Instantiate the parent object
		parent::__construct(
	            'wpshout_favorite_song_widget', // Base ID
        	    __('WPShout Favorite Song Widget', 'text_domain'), // Name
 	           array( 'description' => __( 'Widget for playable favorite song', 'text_domain' ), ) // Args
		);
	}

	function widget( $args, $instance ) {
		echo 'I am some static text...';
	}
}

Creating the Plugin

If the “creating a plugin” side of this process is confusing to you, please check out David’s tutorial on creating WordPress plugins.

Briefly, here’s the process:

Widget plugin location in WP filesystem

  1. Create an appropriately named plugin folder, say wpshout-test-widget.
  2. Save the code above as a PHP file inside that folder, again with an appropriate filename, say wpshout-test-widget.php.
  3. Upload that folder to your plugins directory inside wp-content, and the new plugin will show up in your list of installed plugins.
  4. Activate the new plugin.

You should now have a new widget (with name “WPShout Favorite Song Widget” and description “Widget for playable favorite song”) in the list of widgets in “Appearance -> Widgets.” You can place it anywhere inside a widget area and it’ll output the following: “I am some static text…”

Let’s Make it Dynamic

form(): Where the Magic Happens

We make our widget dynamic by adding user-editable form fields.

WordPress widgets allow for user input in the usual way: with HTML form elements like inputs and textareas. So to make our widget dynamic, we basically need to make it contain form fields that allow the user to input his or her audio link and text description.

There’s a specific way to do this inside the WP_Widget class. It’s a method (OOP-speak for “function”) called form(), and in our case it looks like this:

// Widget form creation
function form($instance) {
 	$link = '';
	$songinfo = '';

	// Check values
	if( $instance) {
		$link = esc_attr($instance['link']);
		$songinfo = esc_textarea($instance['songinfo']);
	} ?>
		 
	<p>
		<label for="<?php echo $this->get_field_id('link'); ?>"><?php _e('Link', 'wp_widget_plugin'); ?></label>
		<input class="widefat" id="<?php echo $this->get_field_id('link'); ?>" name="<?php echo $this->get_field_name('link'); ?>" type="text" value="<?php echo $link; ?>" />
	</p>
		 
	<p>
		<label for="<?php echo $this->get_field_id('songinfo'); ?>"><?php _e('Song Info:', 'wp_widget_plugin'); ?></label>
		<input class="widefat" id="<?php echo $this->get_field_id('songinfo'); ?>" name="<?php echo $this->get_field_name('songinfo'); ?>" type="text" value="<?php echo $songinfo; ?>" />
	</p>
		
<?php }

Our Variables: $link, $songinfo, and $instance

That looks complicated, but there are really two main things we’re interested in: a $songinfo variable, and an HTML input element to let the user set it; and a $link variable, and an input to let the user set it.

You’ll also notice an $instance variable, passed into the form() method as its only argument. $instance basically means “What stuff is stored”—it’s an array of the current values (for things like “link” and “songinfo”) that the widget has, and which we’ll be using our inputs to both display and update.

So what we’re doing here is, for starters, setting $link and $songinfo to their initial values—that is, their values in the current widget instance that’s been passed in as $instance. That’s what the code up through the section labeled “Check values” does for us.

The Form Itself

Next comes the section that’s written largely in plain HTML. Here, we’re creating elements that should be familiar from HTML forms: input elements to both display and change form values, and label elements to say what each form field is for.

The most important pieces of the code above are the name and value properties of each input element. As with any HTML form, the name property says which variable we’re changing, and the value property captures both what the variable’s value is at present and what we’ll be changing it to.

We’re setting both these names and values to PHP variables. For songinfo, for example, we set name to a value we retrieve with: <?php echo $this->get_field_name('songinfo'); ?>, and we set value to the $songinfo variable itself.

Some Tips for Understanding form()

The code probably looks pretty ugly, especially if you don’t know what the _e() function does. To help you traverse the code, here are two tips:

  1. If you simply name the get_field_id and get_field_name calls consistently with the PHP variable name in that part of the form—for example, get_field_id('songinfo') and value="<?php echo $songinfo; ?>"—then you don’t have to think too deeply about the relationships there. It becomes boilerplate.
  2. The _e() function just lets you substitute text in different namespaces, like if you wanted to make a Polish version of the plugin. If you’re not sure about it, simply edit the first argument and don’t worry too much about the second. And if you’d prefer more simplicity (and are certain that the plugin won’t ever be internationalized), you can simply replace the whole function with a string value: <?php _e(‘Link’, ‘wp_widget_plugin’); ?> would just become “Link”.

form() is the most complicated piece of the exercise, so take a bit of time to fiddle with it if you’re not yet seeing how it fits together.

update(): Some Necessary Boilerplate

Because of the way the widget API is written, we’ll also need a method to actually tell the widget to update itself—or, to use the language we introduced above, to change its instance data.

That code looks like this:

function update($new_instance, $old_instance) {
	$instance = $old_instance;
	// Fields
	$instance['link'] = strip_tags($new_instance['link']);
	$instance['songinfo'] = strip_tags($new_instance['songinfo']);
	return $instance;
}

This method takes two parameters: $new_instance, which will be loaded with the new values from the form() method described above, and $old_instance, which still contains the previous values, before our most recent form save.

All we’re doing here is setting each element of the $instance array to its new value (while doing a bit of simple form validation with the strip_tags() function, just in case the user uploaded anything dangerous).

In practical terms, you just need to remember to create one line like the two lines under “// Fields” above for every dynamic value you have in your form. So if you’ve added a songyear variable in form(), you’d add this line in update():

$instance['songyear'] = strip_tags($new_instance['songyear']);

Once we’re done defining new values for the $instance array, we just return the updated array, and the WP_Widget code does the rest of the updating process on its own.

widget(): The Fun Part

Now that we’ve defined the values of our link and songinfo variables, we’re ready to actually use those variables to display dynamic content on our site. As you know from the static widget we built above, we write widgets to the page with the widget() method. That method now looks like this:

function widget( $args, $instance ) {
	echo $args['before_widget']; 
	echo '<h2>Now Listening:</h2>';
	echo $instance['songinfo'];
	echo 
	'<p>
		<audio controls>
			<source src="' . $instance['link'] . '" type="audio/mpeg">
			Your browser does not support the audio element.
		</audio>
		<a href="' . $instance['link'] . '">Download it</a>
	</p>';
	echo $args['after_widget'];
}

As you see, widget() continues to take two arguments: $args and $instance. The most important is $instance, which is still the array that contains our dynamic values.

What we’re doing in the code above is printing out, using PHP’s echo, the contents of the widget. We’re including the properties of our $instance variable where they belong:

  • $instance['songinfo'] is included in text just after the widget’s title.
  • $instance['link'] is included in two places: as the URL of the <audio> element that will play the audio file in the widget, and as the URL of the link element that lets you download the MP3 itself.

We’re also printing out both $args['before_widget'] and $args['after_widget']. WordPress uses these to wrap each of a widget area’s widgets in consistent formatting—for example <li id="wpshout_favorite_song-2" class="widget widget_wpshout_favorite_song"> ... </li>. This part is optional, but you’ll want it to make widgets contained and easy to consistently style.

How the Whole Thing Looks

Okay, ready?

<?php 

/*
Plugin Name: WPShout Test Widget
Description: A plugin to make a test tutorial widget
Version: 1.0
Author: WPShout
Author URI: https://wpshout.com/
*/

function wpshout_register_widgets() {
	register_widget( 'WPShout_Favorite_Song_Widget');
}
add_action( 'widgets_init', 'wpshout_register_widgets' );

class WPShout_Favorite_Song_Widget extends WP_Widget {
	function WPShout_Favorite_Song_Widget() {
		// Instantiate the parent object
		parent::__construct(
	            'wpshout_favorite_song_widget', // Base ID
        	    __('WPShout Favorite Song Widget', 'text_domain'), // Name
 	           array( 'description' => __( 'Widget for playable favorite song', 'text_domain' ), ) // Args
		);
	}

	function widget( $args, $instance ) {
		echo $args['before_widget']; 
		echo '<h2>Now Listening:</h2>';
		echo $instance['songinfo'];
		echo 
		'<p>
			<audio controls>
				<source src="' . $instance['link'] . '" type="audio/mpeg">
				Your browser does not support the audio element.
			</audio>
			<a href="' . $instance['link'] . '">Download it</a>
		</p>';
		echo $args['after_widget'];
	}

	function update($new_instance, $old_instance) {
		$instance = $old_instance;
		// Fields
		$instance['link'] = strip_tags($new_instance['link']);
		$instance['songinfo'] = strip_tags($new_instance['songinfo']);
		return $instance;
	}

	// Widget form creation
	function form($instance) {
	 	$link = '';
		$songinfo = '';

		// Check values
		if( $instance) {
			$link = esc_attr($instance['link']);
			$songinfo = esc_textarea($instance['songinfo']);
		} ?>
		 
		<p>
			<label for="<?php echo $this->get_field_id('link'); ?>"><?php _e('Link', 'wp_widget_plugin'); ?></label>
			<input class="widefat" id="<?php echo $this->get_field_id('link'); ?>" name="<?php echo $this->get_field_name('link'); ?>" type="text" value="<?php echo $link; ?>" />
		</p>
		 
		<p>
			<label for="<?php echo $this->get_field_id('songinfo'); ?>"><?php _e('Song Info:', 'wp_widget_plugin'); ?></label>
			<input class="widefat" id="<?php echo $this->get_field_id('songinfo'); ?>" name="<?php echo $this->get_field_name('songinfo'); ?>" type="text" value="<?php echo $songinfo; ?>" />
		</p>
		
	<?php }
}
And viola

And viola

And viola! We’ve got a plugin that instantiates a new WP_Widget subclass, and defines its widget(), update(), and form() methods in the way we need for our “Now Listening” widget object to work.

In Conclusion…

For a complete plugin, you’d likely want to give the widget some styling (using a CSS file, also inside the plugin, that you enqueued in the main PHP file).

And, of course, there’s lots you could do to make this plugin better if you intended to release it to the public at large. For example, how about being able to choose your audio file by browsing for it in the WordPress Media Uploader? (Curious how you’d do that? Here’s a great tutorial.)

But I hope this gives you a general sense of how the form() and update() methods work, and sparks your imagination about how to make widgets do what you need.

Thanks for reading, and if you have any questions or comments we’d love to hear them!

Image credit: Yandle, Ctd 2005


3 Responses

Comments

Pingbacks