Skip to content

Custom Buttons in the WordPress HTML Editor

I use the WordPress HTML editor exclusively, with the Text Control plugin with no formatting, so the posts contain exactly the markup I want. The editor comes with "Quick Tag" buttons, that let you insert HTML tags with one click, and allows you to add custom buttons. So, for all my code samples, I want to have a <pre> button, I just create a javascript file (say, called quicktags.custom.js):

QTags.addButton('pre', 'pre', '<pre>', '</pre>\n');

And include it with the following PHP either in a plugin or my theme functions.php:

if(is_admin()){
	// it's an admin page. Add the custom editor buttons
	wp_register_script('customeditor', plugins_url('quicktags.custom.js', __FILE__), array('quicktags')); // this script depends on 'quicktags'
	wp_enqueue_script('customeditor');
}

And now I have a button that inserts <pre></pre> pairs. But there's much more you can do.

The documentation, as far as I can tell, is only in the unminified source code, wp-includes/js/quicktags.dev.js. The only "official" API is the QTags.addButton function, with the following parameters:

	 * @param id string required Button HTML ID
	 * @param display string required Button's value="..."
	 * @param arg1 string || function required Either a starting tag to be inserted like "<span>" or a callback that is executed when the button is clicked.
	 * @param arg2 string optional Ending tag like "</span>"
	 * @param access_key string optional Access key for the button.
	 * @param title string optional Button's title="..."
	 * @param priority int optional Number representing the desired position of the button in the toolbar. 1 - 9 = first, 11 - 19 = second, 21 - 29 = third, etc.
	 * @param instance string optional Limit the button to a specifric instance of Quicktags, add to all instances if not present.

So QTags.addButton('pre', 'pre', '<pre>', '</pre>\n'); creates a button, <input type="button" id="qt_content_pre" class="ed_button" title="" value="pre"> (the id is a predefined prefix followed by what was passed as the id parameter), that inserts <pre> before the selection and </pre> after the selection (plus a newline).

But the power is in using a function rather than a string as the arg1. They provide a function QTags.insertContent(string) that replaces the current selection with string, but you also need a simple selection-getting function:

function getSelectedText(canvas){ // "canvas" is what they call the textarea of the editor
	canvas.focus();
	if (document.selection) { // IE
		return document.selection.createRange().text;
	}else{ // standards
		return canvas.value.substring(canvas.selectionStart, canvas.selectionEnd);
	}
}

And now I can add buttons to do the inconvient HTML encoding/decoding:

QTags.addButton('toHTML', 'HTML Entities', function(el, canvas){
	QTags.insertContent(getSelectedText(canvas).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'));
}, 'Encode HTML Entities');
QTags.addButton('fromHTML', 'Decode HTML', function(el, canvas){
	QTags.insertContent(getSelectedText(canvas).replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'));
}, 'Decode HTML Entities');

The callback function is passed two paramers, el, the element that was clicked, and canvas, the textarea of the editor.

But I want even more. My code examples use the language-javascript etc. standard, which involves a lot of typing. I could create separate buttons for '<code class="language-javascript">', '<code class="language-html">', '<code class="language-php">', ad nauseum, but that gets very cluttered. I'd rather have a pull-down menu of languages that will change the code button itself.

To do this, I need to hack a bit: the buttons are all represented by a global array, edButtons of objects that represent the data that goes into creating each button. We need the index of the code button (which is 110, by inspecting the source), so our pull-down menu will be index 111. Each object in the array has a member html, which is a function that is called with the prefix to affix to the id:

var languages = ['html','javascript','lisp','pdf','php','vb'];
edButtons[111] = { // insert right after the code button
	html: function(idPrefix){
		return '<select id="'+idPrefix+'code_language" class="language-select">'+
		'<option></option>'+ // include a blank option
		'<option>'+languages.join('</option><option>')+'</option>'+
		'</select>';
	}
}

The other members of the objects in the edButtons don't concern us now, except for the tagStart member, which is the string that is inserted as the start tag. This is what we will modify in the code button itself:

$('body').on('change', 'select.language-select', function(){
	var lang = $(this).val();
	edButtons[110].tagStart = lang ? '<code class="language-'+lang+'" >' : '<code>';
});

In reality, we'd like to make this more general without the 110 index hard-wired, but that is straightforward. And now we have a customizable code button along with our HTML encoding goodies, and editing code snippets is much easier.

You can see a sample page with my custom Prism syntax highlighting. It avoids hard-coding the index of the button by searching edButtons for the appropriate button.

{ 10 } Comments


  1. Fatal error: Uncaught Error: Call to undefined function ereg() in /home/public/blog/wp-content/themes/barthelme/functions.php:178 Stack trace: #0 /home/public/blog/wp-content/themes/barthelme/comments.php(34): barthelme_commenter_link() #1 /home/public/blog/wp-includes/comment-template.php(1469): require('/home/public/bl...') #2 /home/public/blog/wp-content/themes/barthelme/single.php(44): comments_template() #3 /home/public/blog/wp-includes/template-loader.php(74): include('/home/public/bl...') #4 /home/public/blog/wp-blog-header.php(19): require_once('/home/public/bl...') #5 /home/public/blog/index.php(17): require('/home/public/bl...') #6 {main} thrown in /home/public/blog/wp-content/themes/barthelme/functions.php on line 178