简体   繁体   中英

JavaScript, DOM blur/focus, dropdowns

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">

<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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM