Search code examples
jquerywordpressbuttontinymceshortcode

tinyMCE cursor placed in the middle of text after button click


In WordPress I have an operational shortcode button in my editor that I would like the capability of, once clicked, would add two shortcode tags (one beginning and one at the end) around the last location of the cursor, but I've been unable to find a previous Q&A on the topic and I've referenced the TinyMCE documentation for Command Identifiers. Here is my shortcode.js file:

(function() {
    tinymce.PluginManager.add('foobar', function(editor, url) {
        editor.addButton('foobar', {
            title : 'This is just a test',
            text : '<foobar>',
            icon : false,
            onclick : function() {
                editor.execCommand(
                    "mceInsertContent",
                    false,
                    '[foobar][/foobar]'
                );
            }
        });
    });
})(jQuery);

So once the button <foobar> is clicked it generates:

<foobar></foobar>cursor is here

with the cursor coming after the closing </foobar>. Am I using the wrong approach in the editor? This might be a related question but is there even a way if I wanted to build another shortcode that it would add it three lines, such as:

<foobar>
<!-- Cursor lands here
</foobar>

What is the appropriate command to be able to control the cursor location in tinyMCE?


Solution

  • Injecting Shortcodes into WordPress tinyMCE editor

    In my experience there are two routes that one can travel to accomplish this. Personally, after working with both I am a fan of the latter approach however each has their own drawbacks. I'll explain each in turn and you can make up your mind what sounds best for you.

    Via TinyMCE

    In the code below note how the commands powering this TinyMCE plugin are split out into separate functions. This version also extends the existing function to wrap whatever the user currently has selected with our <foobar> element at the time of button press.

    (function() {
        // Note the namespacing 'my_project'.  You don't want to conflict with other
        // tinyMCE plugins that may exist so its a good idea to namespace your plugins.
        tinymce.PluginManager.add('my-project_foobar', function(editor, url) {
    
            // Register our Foobar button with tinyMCE
            editor.addButton('my-project_foobar', {
                title : 'This is just a test',
                text : '<foobar>',
                icon : false,
                onclick : function() {
                    var selection = editor.selection;
                    editor.execCommand('so_39887396_insert_shortcode', '', selection);
                }
            });
    
            // Register our button callback command to insert shortcode content.
            editor.addCommand('so_39887396_insert_shortcode', function (ui, selection) {
                // Grab our existing selection, if there is one.
                var content = selection.getContent({
                  format: 'html',
                });
                
                // If no selection is found, default content to empty string.
                if (!content.length) {
                    content = '';
                }
    
                // Setup the name of our shortcode.
                var shortcodeName = 'foobar';
                
                // Generate our new content, wrapping any existing selection with our shortcode tags.
                // These opening and closing tags could be broken out into different variables as well  
                // if you were looking to pass additional vars within the shortcode's brackets.
                var newContent = '['+ shortcodeName +']' + content + '[/' + shortcodeName + ']';
                
                // Inject our content into tinyMCE at current selection.
                selection.setContent(newContent);
            });
        });
    })(jQuery);

    Via ShortCake (Shortcode UI)

    So while we can accomplish this via the tinyMCE editor, lately I've been a fan of using Shortcake, formerly Shortcode UI. It provides a user friendly interface for inserting shortcodes into the post content and in my experience is very simple to integrate into existing shortcodes :)

    They have an example in the github repo which has great commenting. I recommend giving it a look if you're having questions about initial setup, required hooks, etc.

    <?php
    /**
     * All this code was taken from the `dev.php` example on the Shortcake github repo.
     * @see https://github.com/wp-shortcake/Shortcake/blob/master/dev.php
     */
    
     /**
      * Register Shortcode
      */
    function so_39887396_register_shortcode() {
    	add_shortcode( 'my-project_foobar', 'so_39887396_shortcode_callback' );
    }
    add_action( 'init', 'so_39887396_register_shortcode' );
    
    /**
     * Shortcode UI setup for 'my-project_foobar'
     */
    function so_39887396_register_foobar_with_shortcake() {
    	$args = array(
    		/*
    		 * How the shortcode should be labeled in the UI. Required argument.
    		 */
    		'label' => esc_html__( 'Foobar', 'my-project' ),
    		/*
    		 * Include an icon with your shortcode. Optional.
    		 * Use a dashicon, or full HTML (e.g. <img src="/path/to/your/icon" />).
    		 */
    		'listItemImage' => 'dashicons-editor-quote',
    		/*
    		 * Limit this shortcode UI to specific posts. Optional.
    		 */
    		'post_type' => array( 'post' ),
    	);
    	shortcode_ui_register_for_shortcode( 'my-project_foobar', $args );
    }
    add_action( 'register_shortcode_ui', 'so_39887396_register_foobar_with_shortcake' );
    
    /**
     * Callback for the 'my-project_foobar' shortcode.
     */
    function so_39887396_shortcode_callback( $attr, $content, $shortcode_tag ) {
    	// Shortcode callbacks must return content, hence, output buffering here.
    	ob_start();
    	?>
    	<foobar>
            <?php if ( ! empty( $content ) ) : ?>
                <?php echo wp_kses_post( $content ); ?>
            <?php endif; ?>
    	</foobar>
    	<?php
    	return ob_get_clean();
    }

    In Short

    If you are only working with shortcodes, I find Shortcake to be much easier to work with both as a developer and as a user. Even if you are just trying to insert generic HTML, often it can be easier to write a shortcode to do so and hook it up to Shortcake. TinyMCE's fragility ends up cascading into malformed content and all sorts of random side effects that can be very confusing for an editor or admin.

    There are also other community plugins that build on Shortcake that we can utilize like Shortcake Bakery.