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:
- Set focus on the iFrame using my mouse.
- Type 111space222space333space444 (where space = the space bar and not the word space!).
- Double-click on "222" to highlight it.
- Click "Show Colour Picker".
- Click "Red".
- Watch nothing happen in Firefox!
Is 100ms not enough? What would be?
Thanks again.