Adding image to visual editor popup for shortcode with wp.media and wp.mce: changing image duplicates shortcode

tl;dr: Why does using media manager popup within a TinyMCE popup cause duplications of shortcodes? Is it my code or just the way life is? [Spoiler: It was my code]

I’ve written a small plugin to create a visual editor button and popup that inserts a shortcode representing a “media object”: image to right, text to left.

enter image description here

In the visual editor you click the button,

enter image description here

fill in the text in the popup,

enter image description here

choose the image

enter image description here

enter image description here

and hurrah:

enter image description here

The shortcode is correctly inserted into the editor, and the media view renders it nicely. (This is a reduced case for this question. The full popup has a heap of options.)

The only problem I have is if I want to edit the object and change the image.

If I edit the text, things are fine. If I change the image, then the whole thing inserts a second shortcode instead of replacing the first.

enter image description here

enter image description here

I suspect that clicking the Select Image button on the popup is changing the underlying insertion point or selection in the main editor, but I can’t for the life of me see how I can fix it.

See here, when I click to edit the object is highlighted:

enter image description here

Open the popup and the object looks selected still behind:

enter image description here

(Admin CSS tweaked to make this more visible)

But then clicking the Image button seems to make the selection go:

enter image description here

Here’s my editor view JavaScript, taken from all the work done on shortcode popups by DT Baker a few years ago and still almost the only source of info on the Internet for this.

/* global tinyMCE */
(function($){
    var media = wp.media, shortcode_string = 'media_object';
    wp.mce = wp.mce || {};
    wp.mce.media_object = {
        shortcode_data: {},
        template: media.template( 'editor-media-object' ),
        getContent: function() {
            var options = this.shortcode.attrs.named;
            options.innercontent = this.shortcode.content;
            return this.template(options);
        },
        View: { // before WP 4.2:
            template: media.template( 'editor-media-object' ),
            postID: $('#post_ID').val(),
            initialize: function( options ) {
                this.shortcode = options.shortcode;
                wp.mce.media_object.shortcode_data = this.shortcode;
            },
            getHtml: function() {
                var options = this.shortcode.attrs.named;
                options.innercontent = this.shortcode.content;
                return this.template(options);
            }
        },
        edit: function( data ) {
            var shortcode_data = wp.shortcode.next(shortcode_string, data);
            var values = shortcode_data.shortcode.attrs.named;
            // var StrippedString = shortcode_data.shortcode.content.replace(/(<([^>]+)>)/ig,"");
            var StrippedString = shortcode_data.shortcode.content.replace(/(<p[^>]+?>|<p>|<\/p>|<br>|<\/br>|<br\/>|<br \/>)/img, "");
            values.innercontent = StrippedString;
            // values.innercontent = shortcode_data.shortcode.content;
            values.imagehtml="<img src="" + values.image_url + '" class="wp-image-' + values.image_id + '" width=120 height=120 style="max-width: 120px; max-height: 120px;" />';
            wp.mce.media_object.popupwindow(tinyMCE.activeEditor, values);
        },
        // this is called from our tinymce plugin, also can call from our "edit" function above
        // wp.mce.boutique_banner.popupwindow(tinyMCE.activeEditor, "bird");
        popupwindow: function(editor, values, onsubmit_callback){
            values = values || [];
            if(typeof onsubmit_callback !== 'function'){
                onsubmit_callback = function( e ) {
                    // Insert content when the window form is submitted (this also replaces during edit, handy!)
                    var args = {
                            tag     : shortcode_string,
                            //type    : e.data.innercontent.length ? 'closed' : 'single',
                            content : e.data.innercontent,
                            type    : 'closed',
                            //type    : 'single',
                            //content : '',
                            attrs : {
                                image_id    : e.data.image_id,
                                image_url   : e.data.image_url,
                                
                                
                            }
                        };
                    editor.insertContent( wp.shortcode.string( args ) );
                };
            }
            editor.windowManager.open( {
                title: 'Media Object',
                    body: [
                    
                    {
                        type: 'textbox',
                        name: 'innercontent',
                        multiline: true,
                        minWidth: 500,
                        minHeight: 100,
                        label: 'Main Text',
                        value: values.innercontent
                    },
                    
                    
                    {
                        type: 'textbox',
                        name: 'image_url',
                        label: 'Image',
                        id: 'my-image-box',
                        value: values.image_url
                    },
                    {
                        type: 'textbox',
                        name: 'imagehtml',
                        label: '',
                        id: 'my-image-box-html',
                        hidden: true,
                        value: values.imagehtml
                    },
                    {
                        type: 'textbox',
                        name: 'image_id',
                        label: '',
                        id: 'my-image-box-id',
                        hidden: true,
                        value: values.image_id
                    },
                    
{
                    type   : 'container',
                    minWidth: 120,
                    minHeight: 120,
                    
                    name   : 'container',
                    label  : ' ',
                    id: 'my-image-container',
                    html   : values.imagehtml
                },
                
                    {
                        type: 'button',
                        name: 'selectimage',
                        text: 'Select Image',
                        onclick: function() {
                            window.mb = window.mb || {};

                            window.mb.frame = wp.media({
                                frame: 'post',
                                state: 'insert',
                                library : {
                                    type : 'image'
                                },
                                multiple: false
                            });

                            window.mb.frame.on('insert', function() {
                                var json = window.mb.frame.state().get('selection').first().toJSON();

                                if (0 > jQuery.trim(json.url.length)) {
                                    return;
                                }
console.log(json);
                                jQuery('#my-image-box-id').val(json.id);
                                jQuery('#my-image-box').val(json.sizes.medium.url);
                                //jQuery('#my-image-container').prepend('<img src="' + json.url + '" />');
                                jQuery('#my-image-container-body').empty().prepend('<img src="' + json.sizes.medium.url + '" width=120 height=120 style="max-width: 120px; max-height: 120px;" />');
                                imagehtml="<img src="" + json.sizes.medium.url + '" class="wp-image-' + json.id + '" />';
                                jQuery('#my-image-box-html').val(imagehtml);
                            });

                            window.mb.frame.open();
                        }
                    }],
                onsubmit: onsubmit_callback
            } );
        }
    };
    wp.mce.views.register( shortcode_string, wp.mce.media_object );
}(jQuery));

1 Answer
1

I suspect that clicking the Select Image button on the popup is
changing the underlying insertion point or selection in the main
editor, but I can’t for the life of me see how I can fix it.

I’m afraid I don’t know why exactly the issue happens only when the image is changed, but I can tell you the issue is with your popupwindow() function which always calls editor.insertContent() (where editor is tinyMCE.activeEditor).

And the proper way to set the content/shortcode is, or here’s how can you fix the issue: Use editor.insertContent() when adding the shortcode, whereas when editing it (e.g. changing the “Main Text” or the image), you should use the update() function that’s passed to your edit() function.

So firstly, define that function like so, and then pass the update to popupwindow():

Note: The your code here means your original code (i.e. no changes).

// 1. Add the "update" parameter.
edit: function( data, update ) {
    // ... your code here (define shortcode_data, etc).

    // 2. Pass the "update" to popupwindow().
    wp.mce.media_object.popupwindow( tinyMCE.activeEditor, values, update );
},

And then define the popupwindow() and onsubmit_callback() functions like so:

// 1. Add the "update" parameter.
popupwindow: function( editor, values, update ) {
    values = values || [];

    var onsubmit_callback = function( e ) {
        // ... your code here (define "args", etc).

        // 2. Use the "update" function, if available.
        if ( update ) { // the shortcode already in the content and is being edited
            update( wp.shortcode.string( args ) );
        } else { // the shortcode is being added for the 1st time
            editor.insertContent( wp.shortcode.string( args ) );
        }
    };

    editor.windowManager.open( /* your code here */ );
}

So I hope that helps and actually, in your code, you could just use $ instead of jQuery, e.g. $('#my-image-box-id').val(json.id). =)

Leave a Comment