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