The Third Bear

Just Right.

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>‘);
                    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”));


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