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.

In the visual editor you click the button,

fill in the text in the popup,

choose the image

and hurrah:

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.

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:

Open the popup and the object looks selected still behind:

(Admin CSS tweaked to make this more visible)

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

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));

Topic media-modal tinymce visual-editor Wordpress

Category Web


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). =)

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.