| Development |

Customizing the TinyMCE Editor to Add Buttons in WordPress

Written by John Sparrow

Shortcodes are great – they save time, reduce coding errors and help to keep things looking consistent. But they’re not always intuitive for everyone and lack the more familiar GUI integration many people prefer. In this guide, we’re going to create a plugin for the TinyMCE editor, inside our WordPress theme. When we’re done, we will have a new button in the editor’s toolbar, which will trigger a modal window. This window will offer the user three text fields, where they can add the author, citation and citation URL in a more simplified fashion. Confirming these properties will convert the selected text into a shortcode for the blockquote, and insert any defined attributes automatically.

Our final blockquote functionality with custom button

File structure

We’ll start with the file structure. As with the previous tutorial, we’re going to write our PHP in the regular functions.php file, although again, I’d recommend having a separate functions folder with dedicated files, and requiring those, for a tidier approach.

We will need to create a js folder (if you don’t already have one), and inside that, create a tiny-mce folder in which we’ll create an img folder and a tiny-mce.js file. So, your theme folder should contain the following:

				js
				|- tiny-mce
				| |- img
				| |- tiny-mce.js
			

Create an icon

You can do this however you want, but you’ll want to end up with a 48px x 48px image file. Our file will be called quote.png.

HINT: There is an excellent project called Font-Awesome-SVG-PNG that has black and white SVG and PNG versions of Font Awesome’s icon sets.

Once you’re done making your icon image, place it in the img folder.

The JavaScript

Now, let’s work on the JavaScript! Start with the opening functions. We’ll use the create method of the tinymce object to create the plugin:

				(function(){
				  tinymce.create('tinymce.plugins.MyPluginName', {
				  
				  });
				  tinymce.PluginManager.add( 'mytinymceplugin', tinymce.plugins.MyPluginName );
				})();
			

Currently, we have an empty function for our plugin. We’ll go ahead and add that plugin beneath the function. Rename the placeholder names to suit your theme.

The second parameter of the create method, is where the meat of our plugin exists. It’s an object, and we’ll put in two methods: the init method, which contains the meat’s meat, and the getInfo method, which provides some information about our plugin:

				tinymce.create('tinymce.plugins.MyPluginName', {
				  init: function(ed, url){
				  
				  },
				  getInfo: function() {
				    return {
				      longname: 'My Custom Buttons',
				      author: 'Plugin Author',
				      authorurl: 'https://www.axosoft.com',
				      version: "1.0"
				    };
				  }
				});
			

The getInfo method calls a function that returns the values above, which should be self-explanatory. Let’s work on the init method next.

The function called by init contains two parameters we’ve defined above as ed and url. ed is the editor object, and url is the URL relative to the folder in which the current .js file resides.

Inside the function, we will call a couple of methods against the ed object, addButton and addCommand. addButton, err, adds the button, and addCommand binds the functionality to that button exclusively.

We’ll add the unique name of our button as the first parameter of addButton, and then the object that forms the second parameter will contain our image, the title of the button (shown on mouseover), and the icon image (using url):

				ed.addButton('myblockquotebtn', {
				  title: 'My Blockquote',
				  cmd: 'myBlockquoteBtnCmd',
				  image: url + '/img/quote.png'
				});
			

Next, we can set up the command with the addCommand method. This is broken down as follows:

  1. The first parameter of the method is the cmd value you defined in addButton
  2. The second parameter is a function made up of five basic parts:
    1. Retrieval of the selected text from the editor
    2. Opening the window using ed.WindowManager and setting:
      1. the title
      2. the window’s fields
      3. the window’s buttons and how they function
      4. what happens when information is submitted from the window

Let’s go through each part.

We grab the selected text from the editor and define its format as HTML so we retain any HTML code.

				ed.addCommand('myBlockquoteBtnCmd', function(){
				  var selectedText = ed.selection.getContent({format: 'html'});
				});
			

The remaining four steps are all contained as properties in an object defined in the windowManager.open method. title is a string, body and buttons are arrays of objects, and onsubmit is a function.

body defines the fields we’ll have in our window:

				ed.addCommand('myBlockquoteBtnCmd', function(){
				  var selectedText = ed.selection.getContent({format: 'html'});
				  var win = ed.windowManager.open({
				    title: 'Blockquote Properties',
				    body: [
				      {
				        type: 'textbox',
				        name: 'author',
				        label: 'Quote Author',
				        minWidth: 500,
				        value: ''
				      },
				      {
				        type: 'textbox',
				        name: 'cite',
				        label: 'Quote Citation',
				        minWidth: 500,
				        value: ''
				      },
				      {
				        type: 'textbox',
				        name: 'link',
				        label: 'Citation URL',
				        minWidth: 500,
				        value: ''
				      }
				    ]
				  });
				});
			

Comprehensive documentation on TinyMCE’s available field types is hard to find; I found that this Stack Overflow question and answers contained the most useful breakdown of available options. The above code should be pretty easy to interpret. name should be unique for each button, so its value can be referenced later. value ought to be empty unless you want to define some default text to populate the field.

Next up are buttons:

				buttons: [
				  {
				    text: "Ok",
				    subtype: "primary",
				    onclick: function() {
				      win.submit();
				    }
				  },
				  {
				    text: "Skip",
				    onclick: function() {
				      win.close();
				      var returnText = '[ blockquote]' + selectedText + '[/blockquote ] ';
				      ed.execCommand('mceInsertContent', 0, returnText);
				    }
				  },
				  {
				    text: "Cancel",
				    onclick: function() {
				      win.close();
				    }
				  }
				]
			

We have three buttons:

  1. Ok: This will trigger the win.submit() method
  2. Skip: Closes the window and returns the selected text as-is, but contained within the blockquote shortcode minus any parameters
  3. Cancel: Aborts the whole blockquote process, closing the window and taking no further action.

Finally, the onsubmit method:

				onsubmit: function(e){
				  var params = [];
				  if( e.data.author.length > 0 ) {
				    params.push('author="' + e.data.author + '"');
				  }
				  if( e.data.cite.length > 0 ) {
				    params.push('cite="' + e.data.cite + '"');
				  }
				  if( e.data.link.length > 0 ) {
				    params.push('link="' + e.data.link + '"');
				  }
				  if( params.length > 0 ) {
				    paramsString = ' ' + params.join(' ');
				  }
				  var returnText = '[ blockquote' + paramsString + ']' + selectedText + '[/blockquote ]';
				  ed.execCommand('mceInsertContent', 0, returnText);
				}

			

We pass e as the object containing all the info from the submission. This includes e.data, an object containing all the button data. Next, we simply create a params array, push relevant values to that array (if they’re not empty), and then make it a string, joined by a space, to add to our shortcode as attributes. Then, we insert the new content with paramsString in a functionally similar way to how we did on Skip.

Now, we can bring the entire script together:

				(function(){
				  tinymce.create('tinymce.plugins.MyPluginName', {
				    init: function(ed, url){
				      ed.addButton('myblockquotebtn', {
				        title: 'My Blockquote',
				        cmd: 'myBlockquoteBtnCmd',
				        image: url + '/img/quote.png'
				      });
				      ed.addCommand('myBlockquoteBtnCmd', function(){
				        var selectedText = ed.selection.getContent({format: 'html'});
				        var win = ed.windowManager.open({
				          title: 'Blockquote Properties',
				          body: [
				            {
				              type: 'textbox',
				              name: 'author',
				              label: 'Quote Author',
				              minWidth: 500,
				              value: ''
				            },
				            {
				              type: 'textbox',
				              name: 'cite',
				              label: 'Quote Citation',
				              minWidth: 500,
				              value : ''
				            },
				            {
				              type: 'textbox',
				              name: 'link',
				              label: 'Citation URL',
				              minWidth: 500,
				              value: ''
				            }
				          ],
				          buttons: [
				            {
				              text: "Ok",
				              subtype: "primary",
				              onclick: function() {
				                win.submit();
				              }
				            },
				            {
				              text: "Skip",
				              onclick: function() {
				                win.close();
				                var returnText = '[ blockquote]' + selectedText + '[/blockquote ]';
				                ed.execCommand('mceInsertContent', 0, returnText);
				              }
				            },
				            {
				              text: "Cancel",
				              onclick: function() {
				                win.close();
				              }
				            }
				          ],
				          onsubmit: function(e){
				            var params = [];
				            if( e.data.author.length > 0 ) {
				              params.push('author="' + e.data.author + '"');
				            }
				            if( e.data.cite.length > 0 ) {
				              params.push('cite="' + e.data.cite + '"');
				            }
				            if( e.data.link.length > 0 ) {
				              params.push('link="' + e.data.link + '"');
				            }
				            if( params.length > 0 ) {
				              paramsString = ' ' + params.join(' ');
				            }
				            var returnText = '[ blockquote' + paramsString + ']' + selectedText + '[/blockquote ]';
				            ed.execCommand('mceInsertContent', 0, returnText);
				          }
				        });
				      });
				    },
				    getInfo: function() {
				      return {
				        longname : 'My Custom Buttons',
				        author : 'Plugin Author',
				        authorurl : 'https://www.axosoft.com',
				        version : "1.0"
				      };
				    }
				  });
				  tinymce.PluginManager.add( 'mytinymceplugin', tinymce.plugins.MyPluginName );
				})();

			

The PHP

Thankfully, the PHP is not as involved as the JavaScript. Open up your functions.php file, and we’ll start with two functions to add and register our custom buttons:

				function tiny_mce_add_buttons( $plugins ) {
				  $plugins['mytinymceplugin'] = get_template_directory_uri() . '/js/tiny-mce/tiny-mce.js';
				  return $plugins;
				}

				function tiny_mce_register_buttons( $buttons ) {
				  $newBtns = array(
				    'myblockquotebtn'
				  );
				  $buttons = array_merge( $buttons, $newBtns );
				  return $buttons;
				}
			

We add the name we defined in the tinymce.PluginManager.add method in our .js file as a new element in the $plugins array we pass into the tiny_mce_add_buttons function. Then, we point the value to the location of our custom JavaScript file and return the revised array.

Next, register the buttons in a separate function, bringing in the current buttons array, defining our new buttons in a separate array, and finally returning a merged array.

Almost done! Now we just need to call these functions with the appropriate hooks. We create one final function to add these two functions as filters, and then we call this final function as an init action.

				add_action( 'init', 'tiny_mce_new_buttons' );

				function tiny_mce_new_buttons() {
				  add_filter( 'mce_external_plugins', 'tiny_mce_add_buttons' );
				  add_filter( 'mce_buttons', 'tiny_mce_register_buttons' );
				}
			

All done! Upload your revised theme files and test out your post editor. You should now be able to highlight your text, hit your new button and customize how your blockquote is made. Here’s how it should look:

Our final blockquote functionality with custom button