Add media uploader to WordPress widgets

I was working on a WordPress theme some time ago and came to a scenario where I needed to add wordpress’ default media uploader to a widget. It’s very useful when you try to create a widgetized page template. Here I am going to show you how to add media uploader to a widget in WordPress.

First we need to enqueue our wordpress media library scripts and our own script and style for admin panel. We need the script to access the media uploader and stylesheet to modify some of the outcomes.

function my_admin_scripts(){
    wp_enqueue_media();
    wp_enqueue_style('my-style', get_template_directory() . '/inc/widgets/my_script.css');
    wp_enqueue_script('my-script', get_template_directory() . '/inc/widgets/my_script.js', array('jquery'), '', true);
}
add_action('admin_enqueue_scripts', 'my_admin_scripts');

After that we will need to register our widget. Here is the class to implement our widget.

class MyWidget extends WP_Widget{
    function __construct()
    {
        parent::__construct(
        'my-widget',
        __('My Widget', 'text-domain'),
        array(
           'description' => __('This is description', 'text-domain'),
           'classname' => 'my-class'
        ));
    }

    function widget($args, $instance)
    {
        // This function generates the output of the widget
    }

    function form($instance)
    {
    // This function takes all the inputs in admin panel widgets page
        // This is where we are going to implement the
    }

    function update($new_instance, $old_instance)
    {
        // This function will save the inputs of the widget.
    }
}

I haven’t added all the codes to the class yet. But we will go there eventually. Now we need to register the plugin.

include_once get_template_directory() . '/inc/widgets/my_widget.php';

function my_widgets_init(){
    register_widget('MyWidget');
}
add_action( 'widgets_init', 'my_widgets_init' );

This will add the widget in the widget menu in the admin panel. But we have a long way to go still.

Now we will prepare the form for the widget in the admin panel. Let’s add a button first to invoke the media uploader. Then we will add a hidden input field to store the source url of the selected image and a img tag to display preview of the selected image. We will do it in a function named form in our MyWidget Class. Here is what it looks like after we are done.

function form($instance)
 {
     // This function takes all the inputs in admin panel widgets page
     // This is where we are going to implement the input form for the widget.
     $image_src = ! empty($instance['my-image']) ? $instance['my-image'] : '';
     ?>
     <p>
         <label class="widefat" for="image-selector-button">Choose an image</label>
         <button class="my-image-selector-button">Choose Image</button>
         <input class="my-image-input" value="<?php echo $image_src; ?>" type="hidden" name="<?php echo esc_attr($this->get_field_name('my-image')); ?>">
         <?php if($image_src != ''): ?>
             <img class="my-image-preview" src="<?php echo esc_url($image_src); ?>" alt="">
         <?php endif; ?>
     </p>
 <?php
 }
?>

Now we will start with some javascript. First we have to go back to our my_script.js file that we added at the beginning of the tutorial. There we will add the following code snippet inside the document ready function like this:

jQuery(document).ready(function ($) {
   function renderMediaUploader(img, input) {
       // media uploader codes will be here.
   }
}

In this renderMediaUploader function we will render the WordPress media uploader and enable your user to select and upload images, videos and other media files in the widget.

Now let’s add following code snippet into the renderMediaUploader function.

var file_frame;

if ( undefined !== file_frame ) {
     file_frame.open();
     return;
}

Here the variable file_frame will be used to invoke the default media uploader of the wordpress. Now we will initialize the variable and add a listener to it.

       file_frame = wp.media.frames.file_frame = wp.media({
            frame:    'post',
            state:    'insert',
            multiple: false
        });

        file_frame.on('insert', function () {
            var selectedImage = file_frame.state().get( 'selection' ).first().toJSON();
            if(input){
                input.val(selectedImage.url);
            }
            if(img){
                img.attr('src', selectedImage.url);
            }
            else {
                input.before('<img class="my-image-preview" src="' + selectedImage.url + '" alt="Alternative Text">');
            }
        });

Here we are initializing the file_frame variable with WordPress Media Uploader instance. Then we are adding a callback for insert event.

And finally we will add another line of code to finish up the renderMediaUploader function.

file_frame.open();

Now the whole renderMediaUploader function will look like this:

function renderMediaUploader(img, input) {
    var file_frame;
    if ( undefined !== file_frame ) {
        file_frame.open();
        return;
    }

    file_frame = wp.media.frames.file_frame = wp.media({
        frame:    'post',
        state:    'insert',
        multiple: false
    });

    file_frame.on('insert', function () {
        var selectedImage = file_frame.state().get( 'selection' ).first().toJSON();
        if(input){
            input.val(selectedImage.url);
        }
        if(img){
            img.attr('src', selectedImage.url);
        }
        else {
            input.before('<img class="my-image-preview" src="' + selectedImage.url + '" alt="Alternative Text">');
        }
    });

    file_frame.open();
}

At this point we will see how to invoke the function based on user action and let the user to use the media uploader. In order to do this, we will need the piece of code from below:

$(document).on('click', 'a.my-image-selector-button', function (e) {
    e.preventDefault();
    renderMediaUploader($(this).next('img'), 
    $(this).parent().find('input.my-image-input'));
})

This code snippet will invoke the renderMediaUploader function every time we click on the Choose Image button in the widget.

Now we will dive into the depth of this code. When someone clicks on the Choose Image Button in the widget, this jQuery code snippet will call renderMediaUploader function. There is a little thing left to explain here. As you might have noticed, we have added the click listener to document and asked it to track the button as a child element. The reason for this is that, every time you click save button in the widget, the widget refreshes itself and generates the elements within itself. For this reason, any listener bound to the button will get refreshed after each save. This will create some issues with the listener. To prevent this, we have added the listener with root document instead of the button.

References

Leave a Reply

Your email address will not be published. Required fields are marked *