How to replicate Media Library “Add New” on Plugin Settings Page

WordPress is using Backbone JS for the Media Library. At this time, there doesn’t seem to be much in terms of official documentation. I dug through some source code and this is how I got it working…

Disclaimer:

  1. This is my first time hacking at BackboneJS, so this could all be totally wrong.
  2. Class names matter for both WP theming and functionality. Edit with caution.
  3. Obviously this is streamlined. You’ll need to add your own form validation, nonce, script enqueuing, etc.

PHP File

You need this to load all the Backbone templates for the Media Library.

wp_enqueue_media();

If it worked properly then you’ll find these when you inspect…

enter image description here

This is the Add New button.

<button class="page-title-action add-new-image">
  <?php echo esc_html_x( 'Add New', 'file' ); ?>
</button>

This is where Backbone will load the templates.

<div class="media-frame mode-grid"></div>

This is the form that will handle the file uploads.

<form method="POST" enctype="multipart/form-data">

  <!--
    * the "enctype" is important
    * remove "multiple" attribute to limit to a single file
    * the brackets in the "name" attribute make it an array for multiple files
    * auto submits when the user selects a file
  -->

  <input id="uploaded_files"
         multiple="multiple"
         name="uploaded_files[]"
         onchange="form.submit()"
         type="file" >
  </input>

  ...

</form>

Do whatever you do to validate the form, then something like this to handle the file uploads…

if (empty($_FILES)) {
  echo '<div class="error"><p>No files were uploaded.</p></div>';
} else if (isset($_FILES['uploaded_files'])) {
  $files = $_FILES["uploaded_files"];

  // written for multiple files
  foreach ($files['name'] as $key => $value) {
    if ($files['name'][$key]) {
      $file = array(
        'name' => $files['name'][$key],
        'type' => $files['type'][$key],
        'tmp_name' => $files['tmp_name'][$key],
        'error' => $files['error'][$key],
        'size' => $files['size'][$key]
      );

      if ($file['error'] !== UPLOAD_ERR_OK) {
        echo '<div class="error"><p>Upload Error: ('. $file['error'] .') for <strong>'. $file['name'] .'</strong></p></div>';
        return;
      }

      $_FILES = array("uploaded_files" => $file);

      // 0 = post ID = none = unattached
      // if you want to attach it to a post then replace the 0 with the post ID
      $uploaded = media_handle_upload('uploaded_files', 0);

      if (is_wp_error($uploaded)) {
        echo '<div class="error"><p>'. $uploaded->get_error_message() . '</p></div>';
      } else {
        echo '<div class="updated"><p>File uploaded: <strong>'. $file['name'] .'</strong></p></div>';
      }
    }
  }
}

JS File

Many of the selectors you see in this JS file come from elements in WordPress templates loaded by Backbone, so don’t change them. You won’t find them in your files, but they are required.

// File Uploader (aka: uploaderInline)
// This loads the HTML markup for the dashed box and its contents
var uploaderInline = new wp.media.view.UploaderInline({
  controller: {
    state: function() {
      return {
        get: function(x) {
          switch (x) {
            case "suggestedHeight":
              return null;
            case "suggestedWidth":
              return null;
          }
        }
      };
    }
  },
  options: {
    browser: $(".add-new-image"),
    postId: 0 // 0 = unattached
  },
  canClose: true
}).render();

uploaderInline.ready();

$(".media-frame").append(uploaderInline.el);

$(".uploader-inline .drop-instructions").css({ display: "block" });

uploaderInline.hide();

// DropZone (aka: uploaderWindow)
// This enables the drag & drop area
var uploaderWindow = new wp.media.view.UploaderWindow({
  controller: {
    trigger: function() {},
    on: function() {}
  },
  uploader: {
    dropzone: $(".uploader-inline"),
    container: $(".uploader-inline")
  }
}).render();

uploaderWindow.ready();

$(".uploader-inline").append(uploaderWindow.el);

// This will show/hide the File Uploader
$(".add-new-image").on("click", function(e) {
  e.preventDefault();

  var isHidden = $(".uploader-inline").is(":hidden");

  if (isHidden) {
    uploaderInline.show();
  } else {
    uploaderInline.hide();
  }
});

// This triggers the file selector when the user
// clicks the "Select Files" button in the dashed box
$("button.browser").on("click", function() {
  $("#uploaded_files").click();
});

Useful Resources

File Uploader (aka: uploaderInline)

DropZone (aka: uploaderWindow)