Image uploads on wordpress admin screens using jQuery and new Plupload

Since the release of version 3.3, WordPress is using a brand new media uploading engine – Plupload. Plupload is an awesome uploader created by the developers of TinyMce. Depending on the device, it can use HTML5, Gears, Silverlight, Flash, BrowserPlus, or normal forms to provide features such as upload progress, auto image resizing, and asynchronous uploads.

Media Uploader Image

Media Uploader

I am sure you have used “Upload Media” button at the top of wysiwyg editor in wordpress admin (see the image on the right hand side). WordPress core team used Plupload to make that media uploader. And, in this tutorial, we will find out how we, theme and plugin developers, can use it to handle image uploads in our admin screens. In this article, we will create a custom image uploader that uploads behind the scene (ajax), allows multiple images upload in one go, displays upload progress to the user, handles image resizing automatically, and works in metaboxes too. Yes, it’s one heck of an uploader.

Let’s start by writing down a list of features our uploader must have.

  • It allows both multiple and single image upload.
  • It uploads images asynchronously.
  • It displays upload progress.
  • It works in metaboxes.
  • We must be able to place more than one uploader in a page.
  • It handles image resizing automatically.
  • It displays thumbnails of images user has uploaded.
  • And optionally, it allows user to drag the thumbnails to change order of the images.

Long list? I know. But don’t worry, most of the needed functionality is provided by Pluploaded out of the box.

Before we start, here are the demos of final uploader.

Demo 1 shows pluploader in theme/plugin admin page.

Demo 2 shows pluploader in a metabox

Step 1: Include necessary javascripts and css styles.

Code

<?php
function plu_admin_enqueue() {
    if(!($condition_to_check_your_page))// adjust this if-condition according to your theme/plugin
       return;
    wp_enqueue_script('plupload-all');
 
    wp_register_script('myplupload', '/adjust-this-url/myplupload.js', array('jquery'));
    wp_enqueue_script('myplupload');
 
    wp_register_style('myplupload', '/adjust-this-url/myplupload.css');
    wp_enqueue_style('myplupload');
}
add_action( 'admin_enqueue_scripts', 'plu_admin_enqueue' );
?>

Explanation

We use the “admin_enqueue_scripts” hook to include our scripts and styles. Please don’t include these files where you don’t need them. You can use an if-condition to test if this is the page you want to add this uploader to. We add Plupload scripts bundled with wordpress core. We also add the script and style we used to make this uploader: myplupload.js and myplupload.css

Step 2: Use the admin_head hook to print some script.

Code

<?php
function plupload_admin_head() {
// place js config array for plupload
    $plupload_init = array(
        'runtimes' => 'html5,silverlight,flash,html4',
        'browse_button' => 'plupload-browse-button', // will be adjusted per uploader
        'container' => 'plupload-upload-ui', // will be adjusted per uploader
        'drop_element' => 'drag-drop-area', // will be adjusted per uploader
        'file_data_name' => 'async-upload', // will be adjusted per uploader
        'multiple_queues' => true,
        'max_file_size' => wp_max_upload_size() . 'b',
        'url' => admin_url('admin-ajax.php'),
        'flash_swf_url' => includes_url('js/plupload/plupload.flash.swf'),
        'silverlight_xap_url' => includes_url('js/plupload/plupload.silverlight.xap'),
        'filters' => array(array('title' => __('Allowed Files'), 'extensions' => '*')),
        'multipart' => true,
        'urlstream_upload' => true,
        'multi_selection' => false, // will be added per uploader
         // additional post data to send to our ajax hook
        'multipart_params' => array(
            '_ajax_nonce' => "", // will be added per uploader
            'action' => 'plupload_action', // the ajax action name
            'imgid' => 0 // will be added per uploader
        )
    );
?>
<script type="text/javascript">
    var base_plupload_config=<?php echo json_encode($plupload_init); ?>;
</script>
<?php
}
add_action("admin_head", "plupload_admin_head");
?>

Explanation

The only reason we are not placing this script in an external file is that we need php and wordpress environment to output some data. In short, this is a base configuration array which will be used to configure uploaders.

Step 3: Placing html in your forms

Code

<?php
// adjust values here
$id = "img1"; // this will be the name of form field. Image url(s) will be submitted in $_POST using this key. So if $id == “img1” then $_POST[“img1”] will have all the image urls
 
$svalue = ""; // this will be initial value of the above form field. Image urls.
 
$multiple = true; // allow multiple files upload
 
$width = null; // If you want to automatically resize all uploaded images then provide width here (in pixels)
 
$height = null; // If you want to automatically resize all uploaded images then provide height here (in pixels)
?>
 
<label>Upload Images</label>
<input type="hidden" name="<?php echo $id; ?>" id="<?php echo $id; ?>" value="<?php echo $svalue; ?>" />
<div class="plupload-upload-uic hide-if-no-js <?php if ($multiple): ?>plupload-upload-uic-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-upload-ui">
    <input id="<?php echo $id; ?>plupload-browse-button" type="button" value="<?php esc_attr_e('Select Files'); ?>" class="button" />
    <span class="ajaxnonceplu" id="ajaxnonceplu<?php echo wp_create_nonce($id . 'pluploadan'); ?>"></span>
    <?php if ($width && $height): ?>
            <span class="plupload-resize"></span><span class="plupload-width" id="plupload-width<?php echo $width; ?>"></span>
            <span class="plupload-height" id="plupload-height<?php echo $height; ?>"></span>
    <?php endif; ?>
    <div class="filelist"></div>
</div>
<div class="plupload-thumbs <?php if ($multiple): ?>plupload-thumbs-multiple<?php endif; ?>" id="<?php echo $id; ?>plupload-thumbs">
</div>
<div class="clear"></div>

Explanation

We begin this code by adjusting some values for this specific uploader unit. We can request multiple file uploads and image resizing using those values.

We are using a hidden field to store all the image urls. This hidden field will be submitted in $_POST. Additionally, you must provide existing saved value (image urls) in $svalue variable. We added information like ajax nonce, multiple file upload, resize width and height in html tags (in classes, and in empty span tags – a bit unorthodox, but it will work perfectly. You can use data attributes here if you want to.)

Step 4: Setup ajax handler to handle asynchronous file upload.

Code

<?php
function g_plupload_action() {
 
    // check ajax noonce
    $imgid = $_POST["imgid"];
    check_ajax_referer($imgid . 'pluploadan');
 
    // handle file upload
    $status = wp_handle_upload($_FILES[$imgid . 'async-upload'], array('test_form' => true, 'action' => 'plupload_action'));
 
    // send the uploaded file url in response
    echo $status['url'];
    exit;
}
add_action('wp_ajax_plupload_action', "g_plupload_action");
?>

Explanation

We check the ajax nonce, handle file upload, and send the url of uploaded file as response.

Myplupload.js

Code

jQuery.fn.exists = function () {
    return jQuery(this).length > 0;
}
jQuery(document).ready(function($) {
 
    if($(".plupload-upload-uic").exists()) {
        var pconfig=false;
        $(".plupload-upload-uic").each(function() {
            var $this=$(this);
            var id1=$this.attr("id");
            var imgId=id1.replace("plupload-upload-ui", "");
 
            plu_show_thumbs(imgId);
 
            pconfig=JSON.parse(JSON.stringify(base_plupload_config));
 
            pconfig["browse_button"] = imgId + pconfig["browse_button"];
            pconfig["container"] = imgId + pconfig["container"];
            pconfig["drop_element"] = imgId + pconfig["drop_element"];
            pconfig["file_data_name"] = imgId + pconfig["file_data_name"];
            pconfig["multipart_params"]["imgid"] = imgId;
            pconfig["multipart_params"]["_ajax_nonce"] = $this.find(".ajaxnonceplu").attr("id").replace("ajaxnonceplu", "");
 
            if($this.hasClass("plupload-upload-uic-multiple")) {
                pconfig["multi_selection"]=true;
            }
 
            if($this.find(".plupload-resize").exists()) {
                var w=parseInt($this.find(".plupload-width").attr("id").replace("plupload-width", ""));
                var h=parseInt($this.find(".plupload-height").attr("id").replace("plupload-height", ""));
                pconfig["resize"]={
                    width : w,
                    height : h,
                    quality : 90
                };
            }
 
            var uploader = new plupload.Uploader(pconfig);
 
            uploader.bind('Init', function(up){
 
                });
 
            uploader.init();
 
            // a file was added in the queue
            uploader.bind('FilesAdded', function(up, files){
                $.each(files, function(i, file) {
                    $this.find('.filelist').append(
                        '<div class="file" id="' + file.id + '"><b>' +
 
                        file.name + '</b> (<span>' + plupload.formatSize(0) + '</span>/' + plupload.formatSize(file.size) + ') ' +
                        '<div class="fileprogress"></div></div>');
                });
 
                up.refresh();
                up.start();
            });
 
            uploader.bind('UploadProgress', function(up, file) {
 
                $('#' + file.id + " .fileprogress").width(file.percent + "%");
                $('#' + file.id + " span").html(plupload.formatSize(parseInt(file.size * file.percent / 100)));
            });
 
            // a file was uploaded
            uploader.bind('FileUploaded', function(up, file, response) {
 
 
                $('#' + file.id).fadeOut();
                response=response["response"]
                // add url to the hidden field
                if($this.hasClass("plupload-upload-uic-multiple")) {
                    // multiple
                    var v1=$.trim($("#" + imgId).val());
                    if(v1) {
                        v1 = v1 + "," + response;
                    }
                    else {
                        v1 = response;
                    }
                    $("#" + imgId).val(v1);
                }
                else {
                    // single
                    $("#" + imgId).val(response + "");
                }
 
                // show thumbs 
                plu_show_thumbs(imgId);
            });
 
 
 
        });
    }
});
 
function plu_show_thumbs(imgId) {
    var $=jQuery;
    var thumbsC=$("#" + imgId + "plupload-thumbs");
    thumbsC.html("");
    // get urls
    var imagesS=$("#"+imgId).val();
    var images=imagesS.split(",");
    for(var i=0; i<images.length; i++) {
        if(images[i]) {
            var thumb=$('<div class="thumb" id="thumb' + imgId +  i + '"><img src="' + images[i] + '" alt="" /><div class="thumbi"><a id="thumbremovelink' + imgId + i + '" href="#">Remove</a></div> <div class="clear"></div></div>');
            thumbsC.append(thumb);
            thumb.find("a").click(function() {
                var ki=$(this).attr("id").replace("thumbremovelink" + imgId , "");
                ki=parseInt(ki);
                var kimages=[];
                imagesS=$("#"+imgId).val();
                images=imagesS.split(",");
                for(var j=0; j<images.length; j++) {
                    if(j != ki) {
                        kimages[kimages.length] = images[j];
                    }
                }
                $("#"+imgId).val(kimages.join());
                plu_show_thumbs(imgId);
                return false;
            });
        }
    }
    if(images.length > 1) {
        thumbsC.sortable({
            update: function(event, ui) {
                var kimages=[];
                thumbsC.find("img").each(function() {
                    kimages[kimages.length]=$(this).attr("src");
                    $("#"+imgId).val(kimages.join());
                    plu_show_thumbs(imgId);
                });
            }
        });
        thumbsC.disableSelection();
    }
}

Explanation

For each uploader unit, we create a configuration array using the base configuration array, and pass it to the plupload.Uploader constructor. We then attach the following events:

1. Init:
This gets fired on the initiation of uploader object. This currently doesn’t do anything in our code.

2. FilesAdded:
This gets fired when user adds files using “Select Files” button. We use this event to show file unit containing filename and filesize. This unit has a small progress bar at the bottom. We also automatically start uploading selected files asynchronously.

3. UploadProgress:
This gets fired when we receive upload progress. We use it to display upload progress (both textual and graphic).

4. FileUploaded:
This gets fired when we receive upload complete notification from the server. We use it to fadeOut the file unit. In Step 4, we sent the url of uploaded file as response. We receive that url in this event. Depending on whether if we are using multiple files upload, we store this url in the hidden field. We then refresh the image thumbs below the select button area.

5. Image thumbs at the bottom:
We display thumbnails of the images which user has provided below the uploader. We also place a link to remove image below images. We use jQuery sortable ui to allow user to change order of the images by dragging thumbnails.

Myplupload.css

Code

.filelist {
    width: 60%;
}
.filelist .file {
    padding: 5px;
    background: #ececec;
    border: solid 1px #ccc;
    margin-bottom: 4px;
}
.filelist .fileprogress {
    width: 0%;
    background: #B7C53D;
    height: 5px;
}
.plupload-thumbs {
 
}
.plupload-thumbs .thumb {
    width: 50px;
    padding-right: 5px;
    float: left;
}
.plupload-thumbs .thumb img {
    width: 50px;
    height: 50px;
}
.ui-sortable  .thumb img {
    cursor: pointer;
}

Explanation

Css to style the uploader.

Handling Form Upload:

You will receive image urls in $_POST[“img1”] (see Step 3 to adjust “img1”) in csv format. For example: imgurl1,imgurl2,imgurl3

Conclusion:

In this article we have seen what an awesome addition Plupload is to the wordpress core. We can use it in our themes and plugins. We can even use it in metaboxes, which is awesome since normal file uploads don’t work in metaboxes. Now that you know how to use Plupload, you have the knowledge to create custom media uploaders for wordpress.

Do you like this article? Please share.

About Krishna

Hi there! My name is Krishna Kant Sharma and I am a freelance web developer. I am 27 years old and I live in Bhilai, India. Wordpress theme and plugin development is my primary focus.

I am passionate about coding, literature and music. I can’t stay away from my pc for more than 2 hours at a stretch. I am a dog person. My favorite books are Harry Potter, Dan Brown’s, Michael Crichton’s, The God of Small Things and many more. I like TV soaps; FRIENDS, Dexter, The Big Bang Theory, How I met your mother, Prison Break, and Lost. But my biggest passion is coding!

View all posts by this author
  1. check here

    Thank you for another fantastic post. The place else may anyone get that kind of info in such a perfect method of writing?
    I have a presentation next week, and I am at the search for such
    info.

  2. Eric de La Goublaye de Menorval

    It works perfect,

    I only added ‘jquery-ui-sortable’ to enable drag and drop.

    Thanks a lot!!

    wp_register_script(‘myplupload’, ‘/adjust-this-url/myplupload.js’, array(‘jquery’, ‘jquery-ui-sortable’));

  3. Sam Velte

    I should have added to my previous comment, that I can upload the sames files via the WordPress Media “Add New” without any problem.

    So, I’m thinking that it’s something that it’s something I have configured incorrectly when trying to implement your code.

    • Krishna

      Hey Sam,

      Since you are receiving 0 from the ajax request, we can conclude that the ajax request being sent to wordpress is not being received by our code (see step 4 in this tutorial).

      Check these things:
      1) In step 2, we wrote: ‘action’ => ‘plupload_action’, // the ajax action name
      In step 4, we wrote: add_action(‘wp_ajax_plupload_action’, “g_plupload_action”);
      The action names must match. (ie ‘plupload_action’ must match plupload_action part in wp_ajax_plupload_action)
      2) The add_action(‘wp_ajax_plupload_action’, “g_plupload_action”); code in step 4 must be placed where it can be approached on ajax request.
      Ajax request doesn’t run hooks like “admin_enqueue_scripts” and “admin_head” etc. So keep that line outside of any such hooks.

      Do let me know how it goes.

      • Sam Velte

        Many thanks for the fast response to my question.

        I have it working now.

        Your response made me think about about the placement of

        add_action(‘wp_ajax_plupload_action’, “g_plupload_action”);

        and I realised that I had placed it where it was not getting triggered.

        Once I repositioned that in my code everything started working fine.

        Again, many thanks for the great tutorial – you’ve helped me a lot.

  4. Sam Velte

    Many thanks for the great tutorial.

    I implemented it in a metabox, and everything looks like it is working but the uploads are failing.

    I can select the files, and I see the files being uploaded and the percentages increase to 100%, but then the IDs return zero, and the files have not been uploaded.

    Is there anything you could suggest that I check to try and find the reason for the file upload error?

    Thanks for any help you can provide.

  5. Nick Powers

    Thanks so much Krishna!

    This is going to work great in a plugin that I am updating.

    Nick Powers

  6. archird

    You sir is my hero. Thanks for this and continue the excellent work..

  7. Brian C

    Krishna,

    This is an amazing article – well written, great illustration and movie, very helpful; please write articles more often, the WordPress community needs them!

    Will be in touch!

    Cheers, Brian

  8. Niklas Högefjord

    Hi Krishna,
    Thanks for a great article! I’ve modified your script so that the uploaded images will be added as attachements. Now I’m trying to remove the images from being attached when clicking the Remove-link. Just so that I understand it right: does the image being removed in the call:

    thumb.find("a").click(function()

    Or where does the action take place?

    Kind regards /NIklas

    • Krishna

      Hi,

      Yes, you are right. Image is being removed in that call.


      thumb.find("a").click(function() {
      var ki=$(this).attr("id").replace("thumbremovelink" + imgId , "");
      ki=parseInt(ki);
      var kimages=[];
      imagesS=$("#"+imgId).val();
      images=imagesS.split(",");
      for(var j=0; j if(j != ki) {
      kimages[kimages.length] = images[j];
      }
      }
      $("#"+imgId).val(kimages.join());
      plu_show_thumbs(imgId);
      return false;
      });

      Basically, image urls are stored in csv format in a hidden field [$("#"+imgId)]. In that code we are just removing the url of that image from the hidden field's csv. This doesn't delete the actual physical file on your server. If you want to delete those files (or remove it from attachments) then you can add another hidden field containing list of urls (or attachment id), when user submits the form you can read values from this hidden field and delete/remove the files/attachments.

      -Krishna

  9. Yakir Sitbon

    Good work Krishna Kant Sharma. I really like this one trick :)

  10. Павел Голубь

    Hi, great job!  I’m a newbie to WP, so please allow me one stupid question. How can I allow users to upload files from the WP page? I mean I want to create page template with upload functionality and then create several pages for different kinds of data. I know this must be simple, but I’m stuck.
    Thanks in advance!

  11. Kristopherk1

    Thanks for the tutorial, but i can’t get it to do anything.  I guess I’m stuck at where I would place the html section.  Does that go in a metabox?

  12. Gaurav Mehra

    gr8 going man. it was a great help!

  13. Kathy Darling

    krishna, this looks brilliant.  i am going to try to implement it into my metabox.  had a question before i get too involved, b/c at first glance I couldn’t tell: is it possible for the user to select a file from the media library if it exists?

    • Krishna

       Hi Kathy,

      I am glad you like it.

      No, it currently doesn’t allow user to select a file from the media library.

      • Kathy Darling

        ok, so i finally got around to trying to put this in a metabox.  got it saving so that’s a good start.  also i removed your plupload_admin_head()function in favor of creating that object with wp_localize_script().  Now: to try to get the true ‘drag and drop’ magic of plupload.  

  14. bage

    I couldn’t follow the step3. I am suppose to add it to a metabox. I have created a metabox and assigned step3 as the class to the metabox. 

    But nothing seems to happen. Any ideas please?

  15. Ryan

    Hey, great tutorial. The only thing is that sometimes my user’s won’t be admins but they will be allowed to upload images and I’d love to use this. I know I can update the AJAX to use the nopriv one but would I need to do something different in Step #2?

    • Krishna

       Hi Ryan,

      Thanks, I am glad you like it.

      To use it in frontend you will have to modify step1 and 2 too. You will need to use frontend hooks in place of admin hooks (“admin_enqueue_scripts” and “admin_head”).

  16. Jason Bahl

    How do you save the data? I have this meta box working and it’s AWESOME, but when I update the post, the data is not saved. 

    Also, how would I retrieve the data?

    Thanks! 

What others are saying about this article:

  1. http://www.qandasystem.info/wordpress/plupload-in-metabox-ajax-action-not-working-in-class/
  2. http://halfelf.org/2012/plugin-danger-zone/

Leave a Reply

Your email address will be not be published. You will not be spammed. Gentleman's promise!

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>