Depending on your exact needs the disappearance of the Control Manager in TinyMCE 4 may make it easier or harder for you to enable and disable your plugin’s toolbar buttons. Here we look at the easier and the harder way to do it.
In TinyMCE 3 the onNodeChange event was passed a reference to its controlManager object, allowing you to easily enable or disable the state of a toolbar button or menu option based on the currently selected node.
By way of an example, let’s say we’re writing a plugin which allows the user to preview or validate a hyperlink. We only want the button to be enabled if the currently selected element is an <a/> tag. To do this in TinyMCE 3 we just need to add an appropriate onNodeChange event handler during plugin initialisation:-
editor.onNodeChange.add(function(editor, controlManager, node) { controlManager.setDisabled('mylinkplugin', editor.selection.getNode().nodeName != 'A'); });
In fact, since the definition of onNodeChange was specifically for a change in selection, we could also use the node passed to us in the event:-
editor.onNodeChange.add(function(editor, controlManager, node) { controlManager.setDisabled('mylinkplugin', node.nodeName != 'A'); });
In TinyMCE 4 the control manager is no longer in evidence, but it does offer a rather neat declarative alternative. When we create our control we can define a disabledStateSelector property containing a CSS selector which matches the appropriate elements. Our control will automatically be enabled or disabled based on whether or not this selector matches the currently selected element:-
tinymce.PluginManager.add('mylinkplugin', function(editor, url) { editor.addButton('mylinkplugin', {title : 'check link', image : url + '/img/mylinkplugin.png', disabledStateSelector : 'a', onclick : function() { alert('Clicked!');}}); });
A quick search through the TinyMCE 4 code suggests very little (i.e. no) use is being made of this facility, which is a shame really because it has a bit of an easy to spot flaw. It evaluates in exactly the same way as stateSelector is evaluated, which controls whether the button is marked active (true) or inactive (false). Unfortunately the effect of disabledStateSelector has the opposite sense – disabled (true) or enabled (false). A stateSelector of ‘a’ will activate the control if the current element is a hyperlink and make it inactive otherwise. A disabledStateSelector of ‘a’ disables the control when a hyperlink is selected and enables it otherwise. If that’s the behaviour you need for your plugin, this is a neat, code-light way of getting it. Unfortunately it’s the exact opposite of what we’re after here.
CSS does have a potential solution for this though, – the :not(<element>) selector:-
tinymce.PluginManager.add('mylinkplugin', function(editor, url) { editor.addButton('mylinkplugin', {title : 'check link', image : url + '/img/mylinkplugin.png', disabledStateSelector : ':not(a)', onclick : function() { alert('Clicked!');}}); });
Unfortunately it doesn’t appear to be supported by TinyMCE 4’s Selector class. This code leaves our button permanently disabled.
We can still programmatically enable and disable buttons in TinyMCE 4, but we have a hoop to jump through first. Since we don’t have the controlManager any more, we need to save a reference to the created button when our plugin initialises. We can do this by adding an onPostRender handler:-
tinymce.PluginManager.add('mylinkplugin', function(editor, url) { var myButton = null; editor.addButton('mylinkplugin', {title : 'check link', image : url + '/img/mylinkplugin.png', onPostRender : function() { myButton = this; }, onclick : function() { alert('Clicked!');}}); });
With this reference saved we can now add a NodeChange handler to enable or disable based on the current selection, checking first of course that our plugin’s button actually exists:-
tinymce.PluginManager.add('mylinkplugin', function(editor, url) { var myButton = null; editor.addButton('mylinkplugin', {title : 'check link', image : url + '/img/mylinkplugin.png', onPostRender : function() { myButton = this; }, onclick : function() { alert('Clicked!');}}); editor.on('NodeChange', function(event) { if(myButton) { myButton.disabled(!(event.element.nodeName == 'A')); } }); });
There’s a potential flaw here though – the documentation for NodeChange in TinyMCE 4 is slightly different to onNodeChange in TinyMCE 3:-
“Fires when the selection is moved to a new location or if the DOM is updated by some command.”
I may be a little paranoid here, but that bit about the event firing when the DOM is updated suggests scenarios where event.element mightn’t be what’s selected. So it may be safer to save a reference to the editor too and use its selection property instead:-
tinymce.PluginManager.add('mylinkplugin', function(editor, url) { var myButton = null; var myEditor = editor; editor.addButton('mylinkplugin', {title : 'check link', image : url + '/img/mylinkplugin.png', onPostRender : function() { myButton = this; }, onclick : function() { alert('Clicked!');}}); editor.on('NodeChange', function(event) { if(myButton) { myButton.disabled(!( myEditor.selection.getNode().nodeName == 'A')); } }); });
Well if tinymce used valid Syntax, it would work with the not-Pseudoclass 😉 as specified in CSS Selectors Module Level 3
But that is not the case, since :not() is ignored if it is not used with another selector beforehand – your solution works, but this solution is much neater:
disabledStateSelector : ‘*:not(a)’,
a simple *-Selector will solve the problem
i first used your solution but after digging through some tinymce code, i found some cases, where :not() is used and works – then i went deeper
Ah, well spotted! You persevered a bit more with
disabledStateSelector
than I did.Indeed, in the example above ‘*:not(a)’ works a treat and is much cleaner:-
editor.addButton('mylinkplugin',
{title : 'my link button',
image : url + '/img/mylinkplugin.png',
disabledStateSelector : '*:not(a)',
onclick : function() { alert('Clicked!');}});
I tripped up here with my “simple” example – the real plugin I was working on at the time had a more involved, programmatic requirement that
disabledStateSelector
isn’t suitable for and would still need the posted solution; but for the example I used in the post ‘*:not(a)’ is definitely better.