Meta box controls – image and gallery upload controls

Image with blue gradient and a gallery icon

In the previous article, we went through adding the slider and date picker controls. This time I’ll show you how to add image upload and gallery upload controls. For this, we’ll use the built-in wp.media handler. To be honest, this was supposed to be covered last time (at least according to my own to-do paper), but there was a request for slider control, so I decided to cover that first. We’ll continue in the same file as before, basically adding to the existing controls.

Notice: This article was written in 2016. WordPress has changed a lot since then, so take this article with a grain of salt…

PHP part

Inside our mytheme_metabox_controls( $post ) function, we’ll add two variables that will check the metadata of the post for the image and galleries.

$mytheme_featured_image = ( isset( $meta['mytheme_featured_image'][0] ) ) ? $meta['mytheme_featured_image'][0] : '';
$mytheme_gallery = ( isset( $meta['mytheme_gallery'][0] ) ) ? $meta['mytheme_gallery'][0] : '';Code language: PHP (php)

While you may think that we are essentially doing the same thing (gallery is just a bunch of images sorted together), the difference is quite big. In the case of the ‘featured image’ we’re storing the image URL, while in the case of the galleries we’ll store the attachment ID’s, so the way we display them and use them is quite different.

Inside our .right_part div, we’ll add

<p>
	<label for="mytheme_featured_image"><?php esc_html_e( 'Featured Image', 'mytheme' ); ?></label>
	<span class="uploaded_image">
	<?php if ( '' !== $mytheme_featured_image ) : ?>
		<img src="<?php echo esc_url( $mytheme_featured_image ); ?>" />
	<?php endif; ?>
	</span>
	<input type="text" name="mytheme_featured_image" value="<?php echo esc_url( $mytheme_featured_image ); ?>" class="featured_image_upload">
	<input type="button" name="image_upload" value="<?php esc_html_e( 'Upload Image', 'mytheme' ); ?>" class="button upload_image_button">
	<input type="button" name="remove_image_upload" value="<?php esc_html_e( 'Remove Image', 'mytheme' ); ?>" class="button remove_image_button">
</p>Code language: PHP (php)

Also, for a better appearance, we’ll add the styles

.uploaded_image{display: block; width: 200px;}
.uploaded_image img{width: 200px;}
.featured_image_upload{display: block; margin-bottom: 10px;}Code language: CSS (css)

in our inline styles at the top of the custom fields. We want to limit the size of the image container, since you can place a 2000px wide image, and you don’t want to display a 2000px wide image inside the meta box (that would just break the layout and overflow to the page). Also notice that in our code, we added a check to see if the image is present, so that we show the image, only if it’s saved in our meta data. No need to display an empty HTML.

This on its own won’t do much. We need to add some JavaScript to it. We’ve enqueued admin.js file in the previous article to control the slider, and date picker. In the same file we can add

$(document).on( 'click', '.upload_image_button', upload_image_button )
		   .on( 'click', '.remove_image_button', remove_image_button );

function upload_image_button(e) {
	e.preventDefault();
	var $this = $( e.currentTarget );
	var $input_field = $this.prev();
	var $image = $this.parent().find( '.uploaded_image' );
	var custom_uploader = wp.media.frames.file_frame = wp.media({
		title: 'Add Image',
		button: {
			text: 'Add Image'
		},
		multiple: false
	});
	custom_uploader.on('select', function() {
		var attachment = custom_uploader.state().get( 'selection' ).first().toJSON();
		$input_field.val( attachment.url );
		$image.html( '<img src="' + attachment.url + '" />' );
	});
	custom_uploader.open();
}

function remove_image_button(e) {
	e.preventDefault();
	var $this = $( e.currentTarget );
	var $input_field = $this.parent().find( '.featured_image_upload' );
	var $image = $this.parent().find( '.uploaded_image' );

	$input_field.val( '' );
	$image.html( '' );
}Code language: PHP (php)

We need two functions – one to trigger the upload, and one for the removal of the image. Notice that we used var custom_uploader = wp.media.frames.file_frame = wp.media(); which is the call to the native WordPress media handle. Here we don’t want to allow multiple images upload, that’s why we added multiple: false, and we are fetching the url of the selected attachment, and adding it to the input field, as well as attaching the <img /> element to the .uploaded_image element. On the remove button click, we just want to find the image, and the value in the input field and we want to remove those (empty them).

Saving the image

Now all we need to do is to save it. Inside the mytheme_save_metaboxes( $post_id ) function, add

if ( isset( $_POST['mytheme_featured_image'] ) ) { // Input var okay.
	update_post_meta( $post_id, 'mytheme_featured_image', sanitize_text_field( wp_unslash( $_POST['mytheme_featured_image'] ) ) ); // Input var okay.
}Code language: PHP (php)

Gallery upload control

Next, we want to add the gallery upload control. This one is a bit different, although there are similarities with the image upload. First, we add the HTML and PHP part in like with the image control

<p>
	<label for="mytheme_gallery"><?php esc_html_e( 'Project Gallery', 'mytheme' ); ?></label>
	<div class="separator gallery_images">
		<?php
		$img_array = ( isset( $mytheme_gallery ) && '' !== $mytheme_gallery ) ? explode( ',', $mytheme_gallery ) : '';
		if ( '' !== $img_array ) {
			foreach ( $img_array as $img ) {
				echo '<div class="gallery-item" data-id="' . esc_attr( $img ) . '"><div class="remove">x</div>' . wp_get_attachment_image( $img ) . '</div>';
			}
		}
		?>
	</div>
	<p class="separator gallery_buttons">
		<input id="mytheme_gallery_input" type="hidden" name="mytheme_gallery" value="<?php echo esc_attr( $mytheme_gallery ); ?>" />
		<input id="manage_gallery" title="<?php esc_html_e( 'Manage gallery', 'mytheme' ); ?>" type="button" class="button" value="<?php esc_html_e( 'Manage gallery', 'mytheme' ); ?>" />
		<input id="empty_gallery" title="<?php esc_html_e( 'Empty gallery', 'mytheme' ); ?>" type="button" class="button" value="<?php esc_html_e( 'Empty gallery', 'mytheme' ); ?>" />
	</p>
</p>Code language: HTML, XML (xml)

Notice that we are converting our saved list of image IDs into an $img_array. That way we can control the output easier using the for each loop. The last part is almost the same as the image input, but the input field for the image IDs is hidden, since it’s just a list of numbers. To save it we’ll add in our save function that gets called on the save_post action

if ( isset( $_POST['mytheme_gallery'] ) ) { // Input var okay.
	update_post_meta( $post_id, 'mytheme_gallery', sanitize_text_field( wp_unslash( $_POST['mytheme_gallery'] ) ) ); // Input var okay.
}Code language: PHP (php)

The important part here is the JavaScript. In our admin.js add

$( document ).on( 'click', '#manage_gallery', upload_gallery_button )
			 .on( 'click', '#empty_gallery', empty_gallery_button )
			 .on( 'click', '.gallery-item .remove', empty_single_image );

function upload_gallery_button(e) {
	var $this = $( e.currentTarget );
	e.preventDefault();
	if ( ! $this.data( 'lockedAt' ) || + new Date() - $this.data( 'lockedAt' ) > 300) { // Doubleclick prevent.
		var $input_field = $( '#mytheme_gallery_input' );
		var ids = $input_field.val();
		var gallerysc = '[[gallery ids="' + ids + '"]]';
		wp.media.gallery.edit( gallerysc ).on('update', function(g) {
			var id_array = [];
			var url_array = [];
			$.each(g.models, function(id, img){
				url_array.push( img.attributes.url );
				id_array.push( img.id );
			});
			var ids = id_array.join( "," );
			ids = ids.replace( /,\s*$/, "" );
			var urls = url_array.join( "," );
			urls = urls.replace( /,\s*$/, "" );
			$input_field.val( ids );
			var html = '';
			for (var i = 0 ; i < url_array.length; i++) {
				html += '<div class="gallery-item" data-id="' + id_array[i] + '"><div class="remove">x</div><img src="' + url_array[i] + '"></div>';
			}

			$( '.gallery_images' ).html( '' ).append( html );
		});
	}
	$this.data( 'lockedAt', + new Date() );
}

function empty_gallery_button(e){
	e.preventDefault();
	var $input_field = $( '#mytheme_gallery_input' );
	$input_field.val( '' );
	$( '.gallery_images' ).html( '' );
}


Array.prototype.remove = function() {
	var what, a = arguments, L = a.length, ax;
	while (L && this.length) {
		what = a[--L];
		while ((ax = this.indexOf( what )) !== -1) {
			this.splice( ax, 1 );
		}
	}
	return this;
};

function empty_single_image(e){
	e.preventDefault();
	var $this = $( this );
	var value = $this.parent().data( 'id' );
	var $this_image = $this.parent().find( 'img' );
	var $this_image_url = $this_image.attr( 'src' );
	var $input_field = $( '#mytheme_gallery_input' );
	var existing_values_arr = $input_field.val().split( ',' );
	var new_arr = existing_values_arr.remove( value.toString() );
	var replace_str = new_arr.join();
	$input_field.val( '' ).val( replace_str );
	$this.parent().remove();
}Code language: JavaScript (javascript)

Now there is a small issue with my syntax highlighter and WordPress automatically detecting the shortcodes in the code above so in the line where it says

var gallerysc = '[[gallery ids="' + ids + '"]]';Code language: JavaScript (javascript)

The code should  contain just one bracket on each side not two. So just be sure to change this small issue (don’t just copy paste the code :D ).

Notice the addition of the

if ( ! $this.data( 'lockedAt' ) || + new Date() - $this.data( 'lockedAt' ) > 300) { // Double click prevent.
}
$this.data( 'lockedAt', + new Date() );Code language: JavaScript (javascript)

code. This is a gem I found on StackOverflow that will prevent any double click issues that happened when you’d click on the ‘Manage gallery’ button. This time we need to call the wp.media.gallery and not just the frames (single image). The code is a bit longer, but you are basically manipulating the gallery shortcode to show your images. The rest of the code just places images in the .gallery_images element, and the IDs in the hidden input field, so that we can save them. There is also empty gallery code, which just removes everything from the input field and the images element, and the single remove function that will just remove the single image one at the time.

There is also the helper function that is used to remove item from an array by its value (we need it to remove single image from the gallery and the input field).

After this the gallery should work when clicked on the ‘Manage Gallery’ button.

The neat thing about the built in WordPress gallery is that you can add images to it, remove it, reorder the images in it and so on. On the meta box side I’ve added a little x on each image, so that you can remove individual image from the list. You’ll need to add some small inline css for it to be nicely styled:

.post_meta_extras .gallery_buttons input{position: relative; display: inline-block; vertical-align: top; width: auto;}
.post_meta_extras .title{font-size: 14px; padding: 8px 12px 8px 0; margin: 0; line-height: 1.4; font-weight: 600;}
.post_meta_extras .gallery-item{position: relative; display: inline-block; vertical-align: top; margin-right: 10px; margin-bottom: 10px;}
.post_meta_extras .gallery-item img{width: 200px; display: inline-block; vertical-align: top;}
.post_meta_extras .gallery-item .remove{position: absolute; top: 5px; right: 5px; width: 20px; height: 20px; background: #000; border-radius: 50%; border: 1px solid #fff; color: #fff; text-align: center; font-weight: 600;
line-height: 18px; cursor: pointer; display: none;}
.post_meta_extras .gallery-item:hover .remove{display: block;}Code language: CSS (css)

The end result looks like this:

Image upload meta box without images
Image upload meta box with no images
Image upload meta box with images
Image upload meta box with images

Usage

It’s all nice to have the meta box controls, but how do we use them? For the single image just use the meta url in your page template:

$featured_image = get_post_meta( get_the_ID(), 'mytheme_featured_image', true );Code language: PHP (php)

For the gallery, it’s a bit different. Since we have attachment IDs we need to fetch them first

$gallery = ( isset( $custom['mytheme_gallery'][0] ) && '' !== $custom['mytheme_gallery'][0] ) ? explode( ',', $custom['mytheme_gallery'][0] ) : '';Code language: PHP (php)

And then use for each loop to get the image urls. Then you can even set the background as an inline style and use it as such

foreach ( $gallery as $key => $value ) {
	$image_url = wp_get_attachment_url( $value );
	$background = ( isset( $image_url ) && '' !== $image_url ) ? 'style="background:url( ' . esc_url( $image_url ) . ' ); -webkit-background-size: cover; background-size: cover; background-repeat: no-repeat; ' . $bg_pos . '"' : '';
}Code language: PHP (php)

I hope you’ll find this tutorial interesting and useful. If you have any questions feel free to post them below, and as always, happy coding :)

8 responses

  1. how i can upload images in metabox from frontend by submitting form?

    1. Hi!

      You would need to create a form, and handle the upload logic yourself from that form. In that logic, you would store the uploaded image to the media library, and then attach it to a specific post mytheme_featured_image meta field. That should work.

  2. […] post text about them, and then some more images. I used the info from the last tutorial about including images in your posts in a meta box, so that the client could arrange images to his liking, and also some ‘slides’ had 2 images […]

  3. sonia maklouf Avatar
    sonia maklouf

    Hi denis
    I was thinking about a tutorial about media library tutorial
    To be more precise when you edit a gallery. You have some select controls like columns, size, link to.
    It would be interesting to add an another select with masonry, grid etc option to shape the gallery.
    https://uploads.disquscdn.com/images/c158bbcd06d9306ef27f15925995e26800be6e102eefdd49fa27a61b74d3310d.png
    What do you think ?
    Would it be interesting or not ?

    1. Great, I’ll add this to the list of to do articles. I actually did something similar for images in gallery (image post meta fields). Currently I have some personal projects I’m working on, but as soon as I find the time I’ll write a tutorial about this :)

      1. michel lompret Avatar
        michel lompret

        Hi Denis First I wanted to thank you for your precious and useful tutorial :-D they help me to understand a lot of things but there is one thing that stay obscure to me and it’s the page builder mechanism. I used page builder plugin for a long time . First visual composer and now elementor and I always ask myself how they get work. How visually edit a page and resize some module. I find it fascinating. I guess there’s lot to say about that but I think that if you do a series of tutorials about that it’ll have a great success because a lot of people use page builder without having any idea about how they work and I think there isn’t any tutorial explaining that :-)

        1. Hi!

          Page builders are a different sort of beasts :D

          Especially visual based ones. You are basically loading everything in a separate container, and you need to keep track of every change. Different builders use different methods. Some use AJAX to communicate, some use ReactJS. It’s not a trivial thing to cover, and they are usually projects that take over a year to get in production (colleague on my job is working on such builder right now).

          Interesting topic, sure, but not trivial definitely :)

          1. michel lompret Avatar
            michel lompret

            ah I underestimated their complexity. This probably explain why there’s no tutorial about that

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.