Rich Text Editing: Part II

Synopsis

In the previous article I discussed dynamically creating an iframe to be used as the input area for a rich text control; the focus of this article is to attach event handlers to the iframe.

There are at least two good reasons for capturing events on the iframe. The first is that if you are authoring a rich text control, then you will need to capture some of the iframe events so that the toolbar is appropriately updated when the user clicks in the editing area. For example, if the user clicks in the middle of a bold word, you will want to depress the corresponding icon on the toolbar. The second reason is that the client application of your rich text control may want to attach event handlers to your object; in order to provide this functionality, your control will have to export this functionality.

A working example can be seen here.

Compatibility

This works in newer versions of most browsers. The two exceptions I know of to date are Safari and Konqueror; support of the designMode property for these browsers is expected.

Series
  1. Part I - dynamic iframe
  2. Part II - iframe events
The HTML

The HTML for this example is slightly modified from Part I. A <textarea> is created on top of the page; as the iframe events fire the contents of this <textarea> will be updated. There is a Clear button to clear the contents of the <textarea>. The iframe is dynamically created and appended to the document with Javascipt, so it is the last element on the page.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head><!-- HEADER CONTENT --></head>
  <body>
    <h1>Rich Text Editing - iframe events</h1>
    <h4>Textarea</h4>
    <p>
      <textarea id="ta" rows="10" cols="40"></textarea>
      <input type="button" id="clear" value="Clear">
    </p>
    <h4>iframe</h4>
  </body>
</html>
Modified onload

The window.onload event handler is modified to attach an event to the Clear button. We also add a function call at the end of the handler to addEvents(), which is the function we will be creating.

window.onload = function(){
  // Code from Part I, unchanged
  var clear = document.getElementById("clear");  // Attach an event handler
  clear.onclick = function(){                    // to the textarea control.
    document.getElementById("ta").value = "";
  }
  addEvents(iframe);                             // Attach events to the iframe
}
Event Registration

It is beyond the scope of this article to discuss event registration in detail, but there are two event models you should use when adding event handlers. There is the DOM Level 2 model created by the W3C and there is the IE model. There is also the DOM Level 0 model, which I will not bother with for this article.

Event Registration - DOM L2

Adding a click event to the iframe for DOM L2 browsers.

function addEvents(iframe){
  iframe.contentDocument.addEventListener("click", function(){
    alert("click()");
  }, false);
}
Event Registration - Internet Explorer

Adding a click event to the iframe for IE browsers.

function addEvents(iframe){
  iframe.contentWindow.document.attachEvent("onclick", function(){
    alert("click()")
  });
}
Event Registration - Cross Browser

Note that in the simple examples above, the event was attached to the iframe's document. The following events can be attached in that manner: mousedown, mouseup, mouseover, mouseout, mousemove, click, keydown, keyup, keypress.

Also note that accessing the event object from within the handler will be trickier with IE; compliant browsers accept the event object as a parameter to the handler while IE uses a global object. Since we are dealing with an iframe, instead of window.event we have to use iframe.contentWindow.document.parentWindow.event.

function addEvents(iframe){
  var events = new Array( "mousedown", "mouseup", "mouseout", "mouseover",
                          "click", "keydown", "keyup", "keypress" );
  for(var i = 0; i < events.length; i++){
    if(iframe.contentDocument && iframe.contentDocument.addEventListener){
      // DOM L2
      iframe.contentDocument.addEventListener(events[i], function(oEvent){
        var txt = document.getElementById("ta");
        txt.value = oEvent.type + "()\n" + txt.value;
      }, false);
    }else if(iframe.contentWindow && iframe.contentWindow.document &&
             iframe.contentWindow.document.attachEvent){
      // IE
      iframe.contentWindow.document.attachEvent("on" + events[i], function(){
        var txt = document.getElementById("ta");
        var event = iframe.contentWindow.document.parentWindow.event;
        txt.value = event.type + "()\n" + txt.value;
      });
    }
  }
}
Remaining Events

The following events remain to be captured:

  • select - selection within control changes
  • blur - object loses focus
  • focus - object gains focus
  • change - content of control changes

At this time, I've discovered how to handle all but the change event.

select

Compliant browsers provide a document.getSelection method while IE provides a document.selection property for obtaining the currently selected text within a document. I will briefly show how to use this property with the mouseup event. A more complete example would be one that starts with the mousedown event and waits for a mouseup event with no mouseout or blur events in between.

function addEvents(iframe){
  // Previous handler code here

  if(iframe.contentDocument){ // select
    // DOM L2
    iframe.contentDocument.addEventListener("mouseup", function(oEvent){
      var txt = iframe.contentDocument.getSelection();
      if(txt.length > 0){
        var ctrl = document.getElementById("ta");
        ctrl.value = oEvent.type + "( " + txt + " )\n" + ctrl.value;
      }
    }, false);
  }else if(iframe.contentWindow.document.attachEvent){
    // IE
    iframe.contentWindow.document.attachEvent("onmouseup", function(){
      var range = iframe.contentWindow.document.selection.createRange();
      var txt = range.text;
      if(txt.length > 0){
        var ctrl = document.getElementById("ta");
        var event = iframe.contentWindow.document.parentWindow.event;
        ctrl.value = event.type + "( " + txt + " )\n" + ctrl.value;
      }
    });
  }
}
blur, focus

In compliant browsers, attach the blur and focus events to the iframe's document as before. In IE, attach the blur and focus events to the iframe itself.

function addEvents(iframe){
  // Previous handler code here

  if(iframe.contentDocument){ // focus, blur
    iframe.contentDocument.addEventListener("blur", function(oEvent){
        var txt = document.getElementById("ta");
        txt.value = oEvent.type + "()\n" + txt.value;
    }, false);
    iframe.contentDocument.addEventListener("focus", function(oEvent){
        var txt = document.getElementById("ta");
        txt.value = oEvent.type + "()\n" + txt.value;
    }, false);
  }else if(iframe.contentWindow.document.attachEvent){
    iframe.attachEvent("onblur", function(){
        var txt = document.getElementById("ta");
        var event = window.event;
        txt.value = event.type + "()\n" + txt.value;
    });
    iframe.attachEvent("onfocus", function(){
        var txt = document.getElementById("ta");
        var event = window.event;
        txt.value = event.type + "()\n" + txt.value;
    });
  }
}
Further

The next step is to discover how to mimic the change event, if anyone knows how I'd love to hear it.

Comments

thnx a lot

This is truly a very good explanation of event handling with RTEs.

How can i clear all events

How can i clear all events and pointed functions.

Each time you attach a

Each time you attach a listener, create an object of the event name and callback function; push this object onto the end of an array. When you want to detach all events you can pop each element off the array and call the appropriate function to remove the listener. You could also save the content of the DOM container, destroy and recreate it, and replace the content.

Thanks

This is a nice article. a advanced article. thanks for writing this type of article

Thanks for this post. It

Thanks for this post. It helped me fix a serious problem where blur events were not being captured for me in IE.

Have you figured out how to handle the change event yet?

and would you be willing to consider an update to test sf3?

thanks

I actually have not sat down

I actually have not sat down and figured out how to do it. I've been semi-working on a series of articles for form processing in PHP; while I can not predict when I might finish them, I think I will attempt to sort out the onchange behavior then.

By sf3 did you mean Firefox 3?

RE: Problem in calling events other than onload

Hopefully you have this figured out by now, but when you use a function as a parameter you don't include the parentheses. What is happening is you are telling Javascript to call the function myfunc() and then use the return value as the event handler. So unless myfunc() returns a reference to a valid event handler function, the code will not work.

Use this:
fullIframe.contentWindow.document.attachEvent("onkeydown", myfunc);

Notice the lack of parens after myfunc.

Problem in calling events other than onload

Thanks for this very informative article.
I need to call keydown event on iframe but whatever eventlistener i attache to the iframe, its calling the function on load of iframe only. My code is:
fullIframe.contentWindow.document.attachEvent("onkeydown",myfunc());
but myfunc() is getting called when my page is loading. Can you help??

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options