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'); });