Well this post is for people who have used or might use Javascript in their life. Imagine you creating a WYSIWYG text editor in javascript and wanting full control over how the text should be displayed. Say for example you display the text after processing the user input. The processing is done as the user types. One approach which immediately comes to the mind is to use a hidden textarea control to capture the key events and display the processed text inside a div element. You will have to create a custom cursor inside the div for the user to know where he is typing. The cursor problem can be best solved using some character like say '|' inside an absolutely positioned div and then toggling the display of the div with the help of a timer. So far good! But now, imagine you want to go to the middle of a previously typed word and edit it. Where would you show the cursor? You will need to calculate the position of the cursor when the user presses any key, or clicks the mouse button. Needless to say, it is not easy to manipulate the cursor position. Say for example you had the word "foobar" and wanted to insert a 't' before 'b'. You will somehow need to calculate the desired cursor position in pixels and then display it accordingly. How to calculate? Well, not my cup of tea...
In such situations, the only feasible approach seems to be to turn design mode on for the control. So you use an iframe and set its design mode property to true. You no longer need a hidden textarea control to capture the keystrokes. You can capture the key events, process the user input and then display it inside the design mode iframe. Now the big question of reading and setting cursor position! Gecko based browsers support the W3C specified API. For example in firefox, you have the properties anchorNode, anchorOffset, focusNode and focusOffset which help you to determine and set the cursor position wherever you want to. You get the DOM reference and also the offset required. So pretty fine! (The details can be found in the Midas specification). But what about IE? IE's API doesn not even come close to the W3C API in this case (as in most cases). I was playing around with the design mode control last month and was stuck here. I did a lot of search on the internet but could not find any satisfactory way to get and set the cursor position in IE. One inefficient way found over the internet, to read the cursor position was to move the cursor to the left by some arbitrarily huge number, using the method moveStart on a selection object. This method will then return the number of characters moved which will give you the cursor position. You will use something like
document.selection.createRange().duplicate().moveStart(1000000, "characters");
This method will work fine for small text. But as you keep typing, the editor will become slower and slower. Setting the cursor position is also another problem. You will need to calculate the offset in characters from the start of the editable iframe and then use moveStart followed by collapse. This is doubly slow. The complexity of this technique would be O(n) where n is the offset number of characters from the start position. This is clearly unacceptable for large text input. I continued to hunt for a better approach. Eventually, my friend Shivram suggested a unique hack. He found a post on the internet where some person was using the method pasteHTML of the range object in IE to insert some image at the cursor position. You can paste HTML at the current cursor position using this method. The hack was to create a dummy DOM node and insert at the current cursor position using pasteHTML. Then using the DOM attributes, you can get the required DOM reference. The dummy node can then be removed from the DOM tree. So the following hack solved the problem with a complexity of O(1)
getNodeAtCursor = function() {
var range = document.selection.createRange().duplicate();
range.pasteHTML('<span id="foo"></span>');
var temp = getElementById("foo");
var n = temp.parentNode;
removeElement(temp);
return n;
};
The offset can be obtained by finding the length of the previousSibling of the dummy span.
Thus you get the DOM reference as well as the offset number of characters relative to the node without having to iterate over all the nodes before the one under consideration. Not a clean way, but certainly quite efficient for large text. Let us hope future versions of IE conform to the W3C standard as in Firefox and eliminate the need to use such hacks.
Well, I hope this post helps somebody who might be stuck with getting and setting cursor position in design mode controls especially in IE 7 and below. Cheers!!!
No comments:
Post a Comment