Calling WordPress Gallery Uploader/Selector From Metabox

Andy Mercer picture Andy Mercer · Feb 18, 2014 · Viewed 7.7k times · Source

When I click the Add Media button on a Post/Page, I have the option to Add Media. After selecting media, I click Insert Into Post, and the images are inserted. However, there is another option, which is on the left sidebar. I can click Create Gallery. The image selecting process is the same, but when I click Create New Gallery, it goes to a new frame which allows me to edit the order of the images.

This second window is what I am after. I am calling the frame from a metabox, and I have gotten it successfully to allow me to grab single or multiple images and save the ID's as a string, as well as insert thumbnails live into a preview box. I cannot find anything about calling the Gallery frame.

My current code is as follows:

jQuery('#fg_select').on('click', function(event){

    event.preventDefault();

    // If the media frame already exists, reopen it.
    if ( file_frame ) {
        file_frame.open();
        return;
    }

    // Create the media frame.
    file_frame = wp.media.frame = wp.media({
        title: "Select Images For Gallery",
        button: {text: "Select",},
        library : { type : 'image'},
        multiple: true // Set to true to allow multiple files to be selected
    });

    file_frame.on('open', function() {
        var selection = file_frame.state().get('selection');
        ids = jQuery('#fg_metadata').val().split(',');
        ids.forEach(function(id) {
            attachment = wp.media.attachment(id);
            attachment.fetch();
            selection.add( attachment ? [ attachment ] : [] );
        });
    });

    file_frame.on('ready', function() {
        // Here we can add a custom class to our media modal.
        // .media-modal doesn't exists before the frame is
        // completly initialised.
        $( '.media-modal' ).addClass( 'no-sidebar' );
    });

    // When an image is selected, run a callback.
    file_frame.on('select', function() {
        var imageIDArray = [];
        var imageHTML = '';
        var metadataString = '';
        images = file_frame.state().get('selection');
        images.each(function(image) {
            imageIDArray.push(image.attributes.id);
            imageHTML += '<li><button></button><img id="'+image.attributes.id+'" src="'+image.attributes.url+'"></li>';
        });
        metadataString = imageIDArray.join(",");
        if(metadataString){
            jQuery("#fg_metadata").val(metadataString);
            jQuery("#featuredgallerydiv ul").html(imageHTML);
            jQuery('#fg_select').text('Edit Selection');
            jQuery('#fg_removeall').addClass('visible');
        }
    });

    // Finally, open the modal
    file_frame.open();

});

Any ideas?

EDIT:

I've gotten it to the point where it calls the gallery directly, without any sidebars, etc. However, it now ignores the on('select') call. I guess galleries send a different call when selecting the image?

jQuery(document).ready(function($){

// Uploading files
var file_frame;

jQuery('#fg_select').on('click', function(event){

    event.preventDefault();

    // If the media frame already exists, reopen it.
    if ( file_frame ) {
        file_frame.open();
        return;
    }

    // Create the media frame.
    file_frame = wp.media.frame = wp.media({
        frame: "post",
        state: "featured-gallery",
        library : { type : 'image'},
        button: {text: "Edit Image Order"},
        multiple: true
    });

    file_frame.states.add([
        new wp.media.controller.Library({
            id:         'featured-gallery',
            title:      'Select Images for Gallery',
            priority:   20,
            toolbar:    'main-gallery',
            filterable: 'uploaded',
            library:    wp.media.query( file_frame.options.library ),
            multiple:   file_frame.options.multiple ? 'reset' : false,
            editable:   true,
            allowLocalEdits: true,
            displaySettings: true,
            displayUserSettings: true
        }),
    ]);

    file_frame.on('open', function() {
        var selection = file_frame.state().get('selection');
        ids = jQuery('#fg_metadata').val().split(',');
        if (!empty(ids)) {
            ids.forEach(function(id) {
                attachment = wp.media.attachment(id);
                attachment.fetch();
                selection.add( attachment ? [ attachment ] : [] );
            });
        }
    });

    file_frame.on('ready', function() {
        // Here we can add a custom class to our media modal.
        // .media-modal doesn't exists before the frame is
        // completly initialised.
        $( '.media-modal' ).addClass( 'no-sidebar' );
    });

    file_frame.on('change', function() {
        // Here we can add a custom class to our media modal.
        // .media-modal doesn't exists before the frame is
        // completly initialised.
        setTimeout(function(){
            $('.media-menu a:first-child').text('← Edit Selection').addClass('button').addClass('button-large').addClass('button-primary');
        },0);
    });

    // When an image is selected, run a callback.
    file_frame.on('set', function() {
        alert('test');
    });

    // Finally, open the modal
    file_frame.open();

});

EDIT 2:

Okay, so I've gotten everything to fire correctly. But I can't decipher the outputted gallery code.

        // When an image is selected, run a callback.
    file_frame.on('update', function() {
        var imageIDArray = [];
        var imageHTML = '';
        var metadataString = '';
        images = file_frame.state().get('selection');
        images.each(function(image) {
            imageIDArray.push(image.attributes.id);
            imageHTML += '<li><button></button><img id="'+image.attributes.id+'" src="'+image.attributes.url+'"></li>';
        });
        metadataString = imageIDArray.join(",");
        if (metadataString) {
            jQuery("#fg_metadata").val(metadataString);
            jQuery("#featuredgallerydiv ul").html(imageHTML);
            jQuery('#fg_select').text('Edit Selection');
            jQuery('#fg_removeall').addClass('visible');
        }
    });

Nothing is coming out for $imageArray, or $imageHTML. $image is something, it's an [object object].

EDIT 3: As mentioned below in comment, the main problem with the code from Edit 2 is that when using Gallery, you have to call 'library' instead of 'selection'.

    // Uploading files
var file_frame;

jQuery('#fg_select').on('click', function(event){

    event.preventDefault();

    // If the media frame already exists, reopen it.
    if ( file_frame ) {
        file_frame.open();
        return;
    }

    // Create the media frame.
    file_frame = wp.media.frame = wp.media({
        frame: "post",
        state: "gallery",
        library : { type : 'image'},
        button: {text: "Edit Image Order"},
        multiple: true
    });

    file_frame.on('open', function() {
        var selection = file_frame.state().get('selection');
        var ids = jQuery('#fg_metadata').val();
        if (ids) {
            idsArray = ids.split(',');
            idsArray.forEach(function(id) {
                attachment = wp.media.attachment(id);
                attachment.fetch();
                selection.add( attachment ? [ attachment ] : [] );
            });
        }
    });

    // When an image is selected, run a callback.
    file_frame.on('update', function() {
        var imageIDArray = [];
        var imageHTML = '';
        var metadataString = '';
        images = file_frame.state().get('library');
        images.each(function(attachment) {
            imageIDArray.push(attachment.attributes.id);
            imageHTML += '<li><button></button><img id="'+attachment.attributes.id+'" src="'+attachment.attributes.url+'"></li>';
        });
        metadataString = imageIDArray.join(",");
        if (metadataString) {
            jQuery("#fg_metadata").val(metadataString);
            jQuery("#featuredgallerydiv ul").html(imageHTML);
            jQuery('#fg_select').text('Edit Selection');
            jQuery('#fg_removeall').addClass('visible');
        }
    });

    // Finally, open the modal
    file_frame.open();

});

The main thing here I'm having difficulty with now is that I can't get it to open to gallery-edit with a selection. I can get it to open there, but there are no images selected. I'm looking into that. I'm also looking into re-opening instead of creating a new view and sending a pre-selection. If I go to the selection window, then the order window, but click the X to close, I can re-open to the order window. So there should be a way.

EDIT 4

As per code from answer below, I've changed the pre-selection code to:

    file_frame.on('open', function() {
        var library = file_frame.state().get('library');
        var ids = jQuery('#fg_perm_metadata').val();
        if (ids) {
            idsArray = ids.split(',');
            idsArray.forEach(function(id) {
                attachment = wp.media.attachment(id);
                attachment.fetch();
                library.add( attachment ? [ attachment ] : [] );
            });
        }
    });

This allows me to re-open directly to the gallery-edit state and have images pre-selected. However, when I open directly to this state, I cannot click Cancel Gallery (return to image selection state). Clicking that button/link just closes the frame. I tried pre-filling both the library and the selection, but that doesn't work either. The following is from media-views.js, and seems to be what controls that button. Instead of changing the state to a specific state, it changes it to the previous state. Since we are opening directly to gallery-edit, there is no past state. I'm wondering if it's possible to open to gallery, and then on open, change to gallery-edit. Do it instantly so that the user doesn't see, but so that it gets the past state into the system.

    galleryMenu: function( view ) {
    var lastState = this.lastState(),
        previous = lastState && lastState.id,
        frame = this;

EDIT 5:

Finally figured it all out. I couldn't get the above to work at all, I'm not sure why. So, there may be a better way to do this, involving that code. If so, I'd love to know.

    file_frame.on('open', function() {
        var selection = file_frame.state().get('selection');
        var library = file_frame.state('gallery-edit').get('library');
        var ids = jQuery('#fg_perm_metadata').val();
        if (ids) {
            idsArray = ids.split(',');
            idsArray.forEach(function(id) {
                attachment = wp.media.attachment(id);
                attachment.fetch();
                selection.add( attachment ? [ attachment ] : [] );
            });
            file_frame.setState('gallery-edit');
            idsArray.forEach(function(id) {
                attachment = wp.media.attachment(id);
                attachment.fetch();
                library.add( attachment ? [ attachment ] : [] );
            });
        }
    });

FINAL EDIT

My code is now working entirely, and I appreciate the help! If you'd like to see it in action, check out http://wordpress.org/plugins/featured-galleries/

Answer

Kan Nguyen picture Kan Nguyen · Feb 22, 2014

I'm relatively new to WP. In fact, I'm building my first WP theme and I'm stuck on the same question as you. Thank to your code, I can get to the Gallery page. And luckily, I've got the images saved. Here's my code:

// when click Insert Gallery, run callback
wp_media_frame.on('update', function(){
    var library = wp_media_frame.state().get('library');
    var images  = [];
    var image_ids = [];
    thumb_wraper.html('');

    library.map( function( image ) {
        image = image.toJSON();
        images.push(image.url);
        image_ids.push(image.id);
        thumb_wraper.append('<img src="' + image.url + '" alt="" />');
    });
});

What I have found is you should get 'library' instead of get 'selection'.

Edit: I've figured out how to go back to gallery-edit. Here is my full code:

    $( '#btn_upload' ).on( 'click', function( event ) {
    event.preventDefault();

    var images = $( '#image_ids' ).val();
    var gallery_state = images ? 'gallery-edit' : 'gallery-library';

    // create new media frame
    // You have to create new frame every time to control the Library state as well as selected images
    var wp_media_frame = wp.media.frames.wp_media_frame = wp.media( {
        title: 'My Gallery', // it has no effect but I really want to change the title
        frame: "post",
        toolbar: 'main-gallery',
        state: gallery_state,
        library: {
            type: 'image'
        },
        multiple: true
    } );

    // when open media frame, add the selected image to Gallery
    wp_media_frame.on( 'open', function() {
        var images = $( '#image_ids' ).val();
        if ( !images )
            return;

        var image_ids = images.split( ',' );
        var library = wp_media_frame.state().get( 'library' );
        image_ids.forEach( function( id ) {
            attachment = wp.media.attachment( id );
            attachment.fetch();
            library.add( attachment ? [ attachment ] : [] );
        } );
    } );

    // when click Insert Gallery, run callback
    wp_media_frame.on( 'update', function() {

        var thumb_wrapper = $( '#thumb-wrapper' );
        thumb_wraper.html( '' );
        var image_urls = [];
        var image_ids = [];

        var library = wp_media_frame.state().get( 'library' );

        library.map( function( image ) {
            image = image.toJSON();
            image_urls.push( image.url );
            image_ids.push( image.id );
            thumb_wrapper.append( '<img src="' + image.url + '" alt="" />' );
        } );
    } );
} );

I figured that if you re-open the existed frame, it'll always keep the initial state, in your case it's 'gallery'. You'll have to create new frame every time and check if there's images to open 'gallery-edit' Also, I prefer 'gallery-library' than 'gallery' because I want user to focus on my gallery.