Stateful (on/off) hallo.js plugins for Wagtail
egj wagtail
Jeffrey Hearn and Joss Ingram have published examples of using Wagtail’s hooks system to register custom hallo.js plugins for additional WYSIWYG features. Using their examples I was able to quickly wire up some plugins that I needed for a Wagtail project, including:
- Indent and outdent buttons, for nested lists and indented text blocks
- Superscript and subscript buttons
- Underline and strikethrough buttons
- “Paste as plain text” and “edit raw html” buttons (but more on these in another post)
There was one missing piece that was bugging me though — how to make the buttons stateful, so that (e.g.) when the cursor is within a block of text that’s already superscript, the “superscript” button would show up as already on, and clicking it would disable superscript.
Digging into Wagtail and hallo.js source I found an answer — the `queryState` option to the `hallobutton` constructor. This option should be sent in as a callback function which can tell the hallo button whether it should be drawn as enabled or disabled:
[…]
populateToolbar: function(toolbar) {
var button, widget;
var getEnclosing = function(tag) {
var node = widget.options.editable.getSelection().commonAncestorContainer;
return $(node).parents(tag).get(0);
};
button = $(’<span></span>‘);
button.hallobutton({
uuid: this.options.uuid,
editable: this.options.editable,
label: ‘Strikethrough’,
icon: ‘fa fa-strikethrough’,
command: null,
queryState: function(event) {
return button.hallobutton(’checked’, !!getEnclosing(”strike”));
}
});
toolbar.append(button);
button.on(’click’, function(event) {
return widget.options.editable.execute(’strikeThrough’);
});
}
[…]
This handles the UI aspects of statefulness.
In some cases — like strikethrough and underline — nothing else is needed: the browser’s `execCommand(”strikeThrough”)` will already check whether the selection is already enclosed in a strike tag, and disable strikethrough if so.
In other cases — like superscript and subscript — the browser’s execCommand function doesn’t seem to implement the toggle behavior; in those cases, the plugin button’s onclick function will need to check for an enclosing sup/sub tag and do the work of removing the formatting instead:
button.on('click', function(event) {
return widget.options.editable.execute(
getEnclosing("sup") ? 'removeFormat' : 'superscript');
});