views:

240

answers:

1

I am building a rich text/wysiwyg editor component in HTML and JavaScript. I have an unordered list of toolbar items which contains an image input element for a colour picker. The colour picker is an overlay which is displayed on the click event of the input element.

The problem:

I want the colour picker overlay to hide when focus is lost from the toolbar item input element. I therefore handle the blur event of the input element and call hide on the colour picker overlay. I also employ a slight timeout, as the blur event of the input element will occur before the click event handler inside the colour picker overlay. In IE this all works fine, however, in FireFox, the timeout needs to be rather more substantial than I'd hoped.

I tried changing the input element to an anchor and placing the overlay inside (in a rather hilarious attempt to say the toolbar item still had focus when clicking in the overlay).

How do people tend to handle this situation with normal dropdowns?

Thanks, Ben

A horribly simplified, non-OOP example:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;

<html xmlns="http://www.w3.org/1999/xhtml" >
    <head>
       <title>Test</title>
       <style type="text/css">
           .richTextEditor
           {
           }
           .richTextEditor .toolbar
           {
           }
           .richTextEditor iframe
           {
               border: 1px solid #000;
               background: #FFF;
           }
       </style>
    </head>
<body>
   <div id="notes" class="richTextEditor">
       <ul class="toolbar">
           <li command="bold"><input type="button" value="Bold" /></li>
           <li command="foreColor"><input type="button" value="Show Colour Picker" /></li>
       </ul>
       <iframe frameborder="0"></iframe>
   </div>
   <script type="text/javascript">
       var notes = document.getElementById("notes");
       var editorElement = notes.getElementsByTagName("IFRAME")[0];
       var colourPicker = null;

       function showColourPicker(toolbarItemElement) {
           if (!colourPicker) {
               colourPicker = document.createElement("DIV");
               var red = document.createElement("A");
               red.href = "javascript:void(0);";
               red.style.backgroundColor = "red";
               red.innerHTML = "red";
               red.onclick = function() {
                   var toolbarItemCommand = toolbarItemElement.getAttribute("command");
                   editorElement.contentWindow.focus();
                   editorElement.contentWindow.document.execCommand(toolbarItemCommand, false, "rgb(255,0,0)");
                   editorElement.contentWindow.focus();
                   colourPicker.style.display = "none";
               };
               colourPicker.appendChild(red);
               document.body.appendChild(colourPicker);
           } else {
               colourPicker.style.display = "block";
           }
           toolbarItemElement.getElementsByTagName("INPUT")[0].onblur = function() {
               window.setTimeout(function() {
                   colourPicker.style.display = "none";
               }, 100);
           };
       }

       function toolbarItemInputElement_click(e) {
           var e = e || window.event;
           var target = e.target || e.srcElement;
           while (target.tagName != "LI") {
               target = target.parentNode;
           }
           var toolbarItemCommand = target.getAttribute("command");
           if (toolbarItemCommand == "foreColor" || toolbarItemCommand == "backColor") {
               showColourPicker(target);
           } else {
               editorElement.contentWindow.focus();
               editorElement.contentWindow.document.execCommand(toolbarItemCommand, false, null);
               editorElement.contentWindow.focus();
           }
           if (e.preventDefault) {
               e.preventDefault();
           } else {
               e.cancelBubble = true;
           }
           return false;
       }


       window.onload = function() {
           // turn on design mode
           editorElement.contentWindow.document.designMode = "on";

           // attach toolbar item event handlers
           var toolbarItemElements = document.getElementsByTagName("LI");
           for (var i = 0; i < toolbarItemElements.length; i++) {
               var toolbarItemInputElement = toolbarItemElements[i].getElementsByTagName("INPUT")[0];
               toolbarItemInputElement.onclick = toolbarItemInputElement_click;
           }
       };
   </script>
</body>
</html>

It's fairly random, so try a few times refreshing the page if it doesn't happen for you. In Firefox I do the following:

  1. Set focus on the iFrame using my mouse.
  2. Type 111space222space333space444 (where space = the space bar and not the word space!).
  3. Double-click on "222" to highlight it.
  4. Click "Show Colour Picker".
  5. Click "Red".
  6. Watch nothing happen in Firefox!

Is 100ms not enough? What would be?

Thanks again.

A: 

Instead of changing the input element to an anchor, wrap both the input and the overlay in an anchor or other element that will fire the blur event and attach the event handler there.

I think you could also use a mousemove event handler on an element that is above all of the toolbar and other controls, and change state based on moving from the overlay or input to anything but the overlay and input.

For timeouts I seem to recall that there was some reason to not use 0ms but to use 1ms.

Edit: Adding link to discussion of using mousemove / event delegation to replace onblur.

PPK: Delegating the focus and blur events

J5
If I'm not mistaken, the focus and blur events do not bubble so placing the event handler on the container element and not on the input will result in the event not firing.The mousemove idea is sound, but I am not that desperate yet. Give it 5 more minutes though!Timeouts do indeed need to be greater than 0ms, however it needs to be greater than around 90ms in the example I have just posted (for IE, even greater for Firefox).Thanks for the ideas.
Ben Hinman