Putting an icon inside a menulist · 2006-06-15 18:17 by Wladimir Palant

Warning: Huge hack ahead!

So I wanted the inline editor for Adblock Plus filters to have an icon that cancels it. Like this:

Inline editor with a close button

Shouldn’t be much of a problem? Yes, that’s what I thought. But the editor is an editable menuitem — and a look at chrome://global/content/bindings/menulist.xml shows that you have to use a regular menulist if you want an image there.

So I played a little with backgrounds, with limited success. Finally I decided to do it the proper way and extend the menulist binding, with the faint hope that nobody will ever change this XBL in toolkit. This starts pretty simple:

<?xml version="1.0"?>

<bindings id="menulistExtensions"
   xmlns="http://www.mozilla.org/xbl"
   xmlns:html="http://www.w3.org/1999/xhtml"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">
  <binding id="menulist-editable-image" extends="chrome://global/content/bindings/menulist.xml#menulist-editable">
    <content sizetopopup="pref">
      <xul:hbox class="menulist-editable-box textbox-input-box" xbl:inherits="context" flex="1">
        <xul:image class="menulist-icon" anonid="icon" xbl:inherits="src"/>
        <html:input class="menulist-editable-input" flex="1" anonid="input" allowevents="true"
                    xbl:inherits="value=label,disabled,tabindex,readonly"/>
      </xul:hbox>
      <xul:dropmarker class="menulist-dropmarker" type="menu"/>
      <children includes="menupopup"/>
    </content>
  </binding>
</bindings>

Now we can apply this binding to our menulist and use list-style-image on it (or maybe rather on menulist .menulist-icon for SeaMonkey’s sake). The problems start when we try to recognize clicks — any click on our icon opens the dropdown list by default. This list captures mousedown on the upper-most level, prevents further propagation of the event (so attaching event handlers to the image itself is no use) and ignores preventDefault() so that it cannot be canceled easily.

The only solution I could come up with is rather hacky. I attach the handler to menulist itself and do some calculations with the event coordinates to decide whether the icon has been clicked. If this is the case I fire an iconmousedown event to let the application know and disable the menulist to prevent the dropdown from appearing. This is done by the following handlers block in XBL:

    <handlers>
      <handler event="mousedown" phase="capturing">
        <![CDATA[
          var image = document.getAnonymousElementByAttribute(this, "anonid", "icon");
          if (event.screenX >= image.boxObject.screenX x%x%x%x%
              event.screenX < image.boxObject.screenX + image.boxObject.width) {
            var e = document.createEvent("MouseEvents");
            e.initMouseEvent("iconmousedown", false, false, document.defaultView,
                             event.detail, event.screenX, event.screenY, event.clientX,
                             event.clientY, event.ctrlKey, event.altKey, event.shiftKey,
                             event.metaKey, event.button, event.relatedTarget);
            this.dispatchEvent(e);

            if (!this.disabled) {
              this.disabled = true;
              setTimeout(function(list) {list.disabled = false}, 0, this);
            }
          }
        ]]>
      </handler>
    </handlers>

Note that there seems to be a bug that makes the caret disappear if a focused menulist was disabled and then re-enabled again. It doesn’t matter in this case since the editor will be hidden anyway but it might be important somewhere else.

Tags:

Comment

Commenting is closed for this article.