May
11
2011

WordPress: Adding custom buttons to TinyMCE

This is an introduction to adding custom buttons to the WordPress TinyMCE

With the frequency of WordPress themes including dozens of shortcodes I’ve created a class to place custom buttons into the WordPress TinyMCE editor. I have to give credit to Justin Tadlock for venting his concerns with over using shortcodes. It helped me realize that so many shortcodes are redundant. If one is determined to make life easier for your clients and customers I feel adding a button to the editor a far better solution.

The key reason is that few people can remember off the top of their heads all 75 shortcodes that came with the template! List item options, alone, average 6-10 unique shortcodes. So I thought to myself, why not add a button in the WordPress TinyMCE that could open up a window and they could simply create their list and use a radio button to select the icon they wish to use in their list. The same logic can be applied to so many other elements currently in shortcodes. I’d like to give credit to Brett Terpstra for his tutorial, as it helped move me in the right direction.

So, as the image above shows, we are going to ease into the code by creating two custom buttons for the TinyMCE editor. I’m a lazy coder so I put the primary code in to a class file and load it in the functions.php so I can reuse it over and over again. As Justin Tadlock mentions, it is easy enough to create a plugin from the class file. Creating plugin’s is beyond the scope of this tutorial.

Quick link to javascript Breakdown

You can get the files, and folder structure, here. Simply place it in your theme folder.

Class Constructor

The class file has comments which should explain most functions. I’ll leave you to question anything if you are unsure how they work and I’ll answer them for clarity. If many questions are asked I’ll rewrite the tutorial with more explanation.

The important function which does require clarity is

function __construct($seperator, $btn_name,$javascrip_location)

This is the class constructor function that initiates as soon as the class is created (instantiated). The arguments it accepts are:

  • $seperator- The seperator between the buttons
  • $btn_name- The name of the button and should be unique
  • $javascrip_location- The full file path of the javascript file

At the moment the seperator is really just a spacer that doesn’t serve much purpose. Using the | adds a single character space to the left of the button.

The button name needs to be unique. It doesn’t actually affect the name of the button when your mouse moves over it in the editor. What it does do is define the unique name of the button that gets added to the TinyMCE button array. If any names in the array are identical the name that is not part of the core files of TinyMCE will be ignored and, thus, won’t load.

The location of the javascript file that details all the information for the new button is using

get_bloginfo('template_url').'/scripts/buttons/name_of_file.js

Although you can use whatever method suits you.

The Full Class Code
//class start
class add_new_tinymce_btn {

public $btn_arr;
public $js_file;
/*
 * call the constructor and set class variables
 * From the constructor call the functions via wordpress action/filter
*/
function __construct($seperator, $btn_name,$javascrip_location){
  $this->btn_arr = array("Seperator"=>$seperator,"Name"=>$btn_name);
  $this->js_file = $javascrip_location;
  add_action('init', array(&$this,'add_tinymce_button'));
  add_filter( 'tiny_mce_version', array(&$this,'refresh_mce_version'));

}
/*
 * create the buttons only if the user has editing privs.
 * If so we create the button and add it to the tinymce button array
*/
function add_tinymce_button() {
   if ( ! current_user_can('edit_posts') && ! current_user_can('edit_pages') )
     return;
   if ( get_user_option('rich_editing') == 'true') {
     //the function that adds the javascript
     add_filter('mce_external_plugins', array(&$this,'add_new_tinymce_plugin'));
	 //adds the button to the tinymce button array
     add_filter('mce_buttons', array(&$this,'register_new_button')); 
   }
}
/*
 * add the new button to the tinymce array
*/
function register_new_button($buttons) {
   array_push($buttons, $this->btn_arr["Seperator"],$this->btn_arr["Name"]);
   return $buttons;
}
/*
 * Call the javascript file that loads the 
 * instructions for the new button
*/
function add_new_tinymce_plugin($plugin_array) {
   $plugin_array[$this->btn_arr['Name']] = $this->js_file;
   return $plugin_array;
}
/*
 * This function tricks tinymce in thinking 
 * it needs to refresh the buttons
*/
function refresh_mce_version($ver) {
  $ver += 3;
  return $ver;
}

}//class end
The Javascript file
(function() {
    tinymce.create('tinymce.plugins.Green', {
        init : function(ed, url) {
            ed.addButton('green', {
                title : 'Prompt Text',
                image : url+'/images/btn_green.jpg',
                onclick : function() {
                                        var prompt_text = prompt("Green Button", "Green Button Text");
					var caret = "caret_pos_holder";
					var insert = "<div>" + prompt_text + "</div>";
                                        if (prompt_text != null &amp;&amp; prompt_text != 'undefined')
				        {
                       	                  ed.execCommand('mceInsertContent', false, insert);
				          ed.selection.select(ed.dom.select('span#caret_pos_holder')[0]); //select the span
 				          ed.dom.remove(ed.dom.select('span#caret_pos_holder')[0]); //remove the span
				        }
                }
            });
        },
        createControl : function(n, cm) {
            return null;
        },
        getInfo : function() {
			/*
			 * I intentionally left the information of
			 * Brett Terpstra, as his code was the
			 * foundation for this.
			*/
            return {
                longname : "Brett's YouTube Shortcode",
                author : 'Brett Terpstra',
                authorurl : 'http://brettterpstra.com/',
                infourl : 'http://brettterpstra.com/',
                version : "1.0"
            };
        }
    });
    tinymce.PluginManager.add('green', tinymce.plugins.Green);
})();
Breaking Down the Javascript

We start by creating the custom button

(function() {
    tinymce.create('tinymce.plugins.Green', {

And then initialize the class object for the editor so we can use it’s built in functions to put in our html.

init : function(ed, url) {

Next we physically add the button

ed.addButton('green', {

And give it the actual name that will appear when the mouse hovers over the button, followed by the location of the image for the button.

                title : 'Prompt Text',
                image : url+'/images/btn_green.jpg',

Then we need to do something when the button is actually clicked. Give it an action:

onclick : function() {

Next we create a javascript prompt to get some user input and assign it to a variable. You don’t have to do this as you can use any html to insert that you like. Using prompt simply shows you that you can get user input and place it in the TinyMCE editor. You can even built an entire form with jquery and more when a user clicks the button!

Wordpress-Adding-TinyMCE-Buttons_popup

var prompt_text = prompt("Green Button", "Green Button Text");

We finish building the html we want to insert and call the function from the TinyMCE object we created and called ed

var caret = "caret_pos_holder";
var insert = "
<div>" + prompt_text + "</div>";
if (prompt_text != null &amp;&amp; prompt_text != 'undefined')
{
	ed.execCommand('mceInsertContent', false, insert);
	ed.selection.select(ed.dom.select('span#caret_pos_holder')[0]); //select the span
	ed.dom.remove(ed.dom.select('span#caret_pos_holder')[0]); //remove the span
}

You may have noticed the strange addition of caret and the span tags. By default adding anything with your buttons will set the cursor to the beginning of the editor. This is quite annoying, actually. What we want to do is put the text cursor at the end of our added text, not the beginning of the document. We do this be selecting the inserted span tags and removing them. Once the selection is removed the cursor defaults to the end of the newly inserted element.

Lastly we create the control, itself which is a built in function of TinyMCE:

createControl : function(n, cm) {
	return null;
},

All the above needs to be identified somehow so we add a name and version and all that to an array which TinyMCE uses to identify the custom plugin button. Here I also give credit to the source of the javascript from Brett Terpstra, though it has been modified to be more generic:

       
getInfo : function() {
            return {
                longname : "Brett's YouTube Shortcode",
                author : 'Brett Terpstra',
                authorurl : 'http://brettterpstra.com/',
                infourl : 'http://brettterpstra.com/',
                version : "1.0"
            };

Now we bring everything together and instantiate (create) the whole operation with:

tinymce.PluginManager.add('green', tinymce.plugins.Green);
tinymce.PluginManager.add() – broken down:
  • ‘green’- The name you entered in the class
  • tinymce.plugins.Green- where Green is the name you used in line 2 for tinymce.create(‘tinymce.plugins.Green’
Putting it All Together

Now that we have a class, and have built the javascript for the button we need to actually build it. We do this in our functions.php file by first instantiating (creating) a copy of our class file, and feeding it the information it asks for:

//load custom buttons class
require_once (TEMPLATEPATH . '/Admin/class.new_tinymce_btn.php'); 

//create an instance of the class
$t = new add_new_tinymce_btn('|','green',get_bloginfo('template_url').'/scripts/buttons/green_button.js');

That’s it! To add additional buttons past the first you can see how I did this by following the rules in this tutorial and viewing the blue_button.js that is in the downloadable zip file.

I hope this tutorial helps everyone build a cleaner, feature rich wordpress button!

Related posts:

About the Author: Neil Davidson

Neil Davidson writes articles and tutorials about web development. He is happily married with three dogs, two cats and an ominous spirit in his home.

13 Comments + Add Comment

  • wonderful! I understood every.single.bit , great tutorial which will become part of my coding conciousness from now on. :-)

    • I am very glad you are finding the tutorial useful. We are always learning new trends, and even mistakes we, ourselves, make through the eyes of others.

  • This is what I consider cutting edge – or even bleeding edge – in the WP field. Big thumbs up.

    Shortcodes have their place – like for inserting really complicated things generated by plugins, like forms (thinking Gravity Forms) or shopping cart stuff, or related posts, etc.

    But I hate the whole concept of using a shortcode just to add a class to a section of text. And you can’t see the results ‘live’ in the Visual Editor.

    The whole idea of bypassing shortcodes and simply inserting the HTML via a TinyMCE button (and styling it via editor.css) is so blindingly obvious!

    Now, I just wish I had a basic understanding of JS so I could follow this tut better. :-P

    • In answer to your comment on Justin Tadlock’s blog, as well as to the understanding of javascript – Using the class created in this tutorial leaves your hands free of much PHP programming. By only needing to create custom functionality in the javascript file(s) you can avoid short code use entirely.

      Now, to better understand javascript, jquery and all that – the web is full of tutorials to get you started so I left much unsaid as to the basics of javascript and objects.

      Knowing that it’s possible for you, as a designer, should be more then enough to feed your programmer and encourage them. If they give you a doubtful, squinty eye just point them to this tutorial!

      • Awesome! It looks like it’s worth learning.

  • this tutorial rocked, it works, I found this from your post on nettuts, great job keep it up.

  • Thank you so much for an incredibly useful and straightforward tutorial. I was feeling so disheartened at the complexity of the resources that I’d found so far. I though I was going to have to dig right into the TinyMCE API without really knowing what I was doing. Now I just followed your instructions, tweaked quite a bit, and have a plugin that I fully understand. Awesome!

  • Thanks Neil, for the tutorial on adding buttons to Tiny MCE.

    I have been fighting with shortcodes vs. the wp-autop mangling of inserted HTML and would like to give my user a way to wrap a piece of content in a div with a specific CSS class, so I can use jQuery to do great things with it.

    I have overridden wp-autop to not be applied to content that starts with <section so it doesn't mangle my hand coded HTML5, but I still want its formatting applied to simple text entered by users. Giving the user the ability to add the HTML using Tiny MCE sounds ideal.

    I'm going to give this a try and will report back on how it goes.

    BTW. I found you from Justin's blog and have read his great book. I just wish he'd do one on theme development.

    /peter

  • I got it to work, but in the end I still use short codes since the divs aren’t visible to the visual editor user. The tinyMCE plugin adds the shortcodes. I’ll package it up as a WordPress plugin when I get a chance, since it really doesn’t belong in the theme. Wrapping a selection was easy, the hard part was wrapping from a cursor to the end when there was no selection. There was no reasonable post online showing how to move the cursor to the start or end on the TinyMCE content, so here it is:

    ed.selection.select(ed.getBody(), true); // select the content

    ed.selection.collapse(false); // collapse the selection to the end (use true for start)

    /peter

    • Thanks for your feedback, Peter. I used javascript to add span tags to the end of the body and then removed them which allowed the cursor to appear at the end of that location. I’m glad you found an alternate method.

      I also look forward to seeing the plugin you build from this tutorial! In particular I look forward to seeing how you implement the plugin to allow custom information to be input such as from form fields activated by the custom button.

      All of my tutorials are created for the sole purpose of better understanding the potential of WordPress and ways we can use it. I’ve never spent time building plugins from them so it’s good to know others are!

      Happy Coding

  • Wonderful tutorial. The scenario making it as class is very cool. I just did, but as separated function :D . Never too late to migrate into class model. thanks a lot Neil :)

  • How about adding some button to comment area?

    • While that is entirely possible in the WordPress administration that would require a different tutorial as it is beyond the scope of this tutorial.

      For working with TinyMCE added anywhere on the site, including front end, some of the rules in this tutorial apply. In it’s current state this class will only load buttons to TinyMCE if the user is logged in. This can also affect users logged in for comment areas.

      You would begin adding/editing the function to fit your needs in the class at

      add_tinymce_button()

      That is where the class builds the rules for applying anything to the TinyMCE editor. As you will have custom code to include an editor for comments anyway, this is where you would begin to re-use this class.

Leave a comment