Forum Moderators: open

Message Too Old, No Replies

Move keyboard caret to end of contenteditable child paragraph?

Code works fine with a form field, not with editable non-form elements.

         

JAB Creations

5:57 pm on Jun 7, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



So I have some XHTML that looks like this...

<div contenteditable="true" id="editor_test">
<p tabindex="3">here is some text</p>
<p tabindex="3">here is some text</p>
<p tabindex="3">here is some text</p>
</div>


The problem is when the last element before the editable divisible element is blurred focus is given to the divisible element with the keyboard caret. This inserts the keyboard caret before the first paragraph[0] meaning text can be entered outside of a paragraph where it does not belong in Firefox however it works as desired in Chrome and Opera. The presence of line breaks within the editable divisible element does not seem to have any effect.

Incorrect Caret Position
<div contenteditable="true" id="editor_test">*****HERE*****<p tabindex="3">here is some text</p>


Desired Caret Position
<div contenteditable="true" id="editor_test"><p tabindex="3">here is some text*****HERE*****</p>


With JavaScript I can set the keyboard caret position in a text field as so...

function caret_position(id)
{
var d = document.getElementById(id);
d.focus();
d.selectionStart = d.value.length;
d.selectionEnd= d.value.length;
}

caret_position('id_of_form_field');


Here is my attempt to make it work with paragraph elements...

function caret_position_p(i)
{
var a = document.getElementById('editor_test').getElementsByTagName('p')[i];
a.focus();
a.selectionStart = a.firstChild.nodeValue.length;
a.selectionEnd = a.firstChild.nodeValue.length;
}

caret_position_p(0);


However this does not work with paragraph elements. I'm trying to avoid having the user type text outside of a paragraph and unfortunately everyone in the search results seems to be asking to do the opposite which is semantically incorrect. Right now I'm just trying to get this to work in standards compliant browsers, if I can't get that far I won't even bother to think about wasting time in IE. Thoughts please?

- John

coopster

5:26 pm on Jun 8, 2011 (gmt 0)

WebmasterWorld Administrator 10+ Year Member



Couple of thoughts ...
  • The HTML paragraph element does not support the tabindex attribute [w3.org]
  • According to DOM2, the HTML paragraph element does not have a focus method [w3.org]. Even though DOM2 shows no focus method for the HTML paragraph element I am seeing it in the Firebug DOM inspector. Am I seeing the DOM focus Event here? More below on this.
  • I thought I would take a shot at this using the anchor element instead so I modified what you have here since the DOM states that the focus method is part of anchor elements [w3.org]. That didn't work either. Even after adding tabindex and name attributes. Now I'm wondering if you didn't discover a bug.


Although it seems like it may be a bug I read this in the DOM Event documentation [w3.org]:

DOMFocusIn
The DOMFocusIn event occurs when an EventTarget receives focus, for instance via a pointing device being moved onto an element or by tabbing navigation to the element. Unlike the HTML event focus, DOMFocusIn can be applied to any focusable EventTarget, not just FORM controls.
emphasis added

Fotiman

7:17 pm on Jun 8, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



@coopster, I believe you are seeing the window.focus method.

I don't believe this is a bug. There is no focus method for paragraph elements.

JAB Creations

7:52 pm on Jun 8, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Fotiman, we're talking about contenteditable / rich text editing.

Apparently deprecated in DOM Level 3 after I came across a Firefox related bug, interesting and probably very on topic find though!

I came across a script (that actually works, doesn't use frameworks and is cross-browser) that when you blur an editable element when you give focus it puts the caret in the previous spot. The problem though is that I can't find any way to make it work with an integer. Here is the standards version of it (without all the IE...stuff)...

var savedRange;

function saveRange() {savedRange = window.getSelection().getRangeAt(0);}

function loadRange()
{
window.getSelection().removeAllRanges();
window.getSelection().addRange(savedRange);
}


However I can't seem to use alert with the savedRange variable to get back anything in Firefox, Opera or Chrome and if I could I think it would solve this issue in a heartbeat.

The reason being is Firefox's bug (which this morning I was told they weren't interested in fixing ([bugzilla.mozilla.org ]) I'm looking to setting the addRange to the end of the fist paragraph.

How I get the number I'd like to pass...
alert(document.getElementById('editor').getElementsByTagName('p')[0].firstChild.nodeValue.length);


So I'm at a bit of a loss here. I've searched around some more and read about serialization with some folks suggesting things like Xpath...no clue though the working script I have seems to be the best lead I have. It's from another site (found from hours of searching) though here is the cleaned up version...

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Example</title>
<script type="text/javascript">
//<![CDATA[
var savedRange,isInFocus;

function saveSelection()
{
if (window.getSelection)
{
savedRange = window.getSelection().getRangeAt(0);
}
else if (document.selection)
{
savedRange = document.selection.createRange();
}
}

function restoreSelection()
{
isInFocus = true;
document.getElementById("area").focus();

if (savedRange != null)
{
if (window.getSelection)//Standards
{
var s = window.getSelection();

if (s.rangeCount > 0)
{
s.removeAllRanges();
s.addRange(savedRange);
}
}
else if (document.createRange)//non IE and no selection
{
window.getSelection().addRange(savedRange);
}
else if (document.selection)//IE
{
savedRange.select();
}
}
}

//this part onwards is only needed if you want to restore selection onclick
var isInFocus = false;
function onDivBlur()
{
isInFocus = false;
}

function cancelEvent(e)
{
if (isInFocus == false && savedRange != null)
{
if (e && e.preventDefault)
{
//alert("FF");
e.stopPropagation(); // DOM style (return false doesn't always work in FF)
e.preventDefault();
}
else
{
window.event.cancelBubble = true;//IE stopPropagation
}
restoreSelection();
return false; // false = IE style
}
}
//]]>
</script>
</head>

<body>

<textarea style="border: #000 solid 1px;"></textarea>

<div id="area" style="border: #000 dotted 1px; width:300px;height:300px;" onblur="onDivBlur();"
onmousedown="return cancelEvent(event);" onclick="return cancelEvent(event);"
contentEditable="true"
onmouseup="saveSelection();" onkeyup="saveSelection();" onfocus="restoreSelection();"><p>111</p><p>222</p><p>333</p></div>

<textarea style="border: #000 solid 1px;"></textarea>

</body>
</html>


Thoughts please?

- John

JAB Creations

8:14 pm on Jun 9, 2011 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Someone in the Firefox bug posted a snippet of code that actually worked because it was apparently easier then admitting Firefox has a bug. I don't know why they don't simply fix the bug though at least the terminology now makes a bit more sense on MDC [developer.mozilla.org]; for clarification the "selection" correlates to the keyboard caret and the "focus" is where the caret is, using collapse moves the keyboard caret. Here is a function that in my testing is triggered onkeypress for the divisible element...

function keydown_test()
{
if (!bb_containsNode('editor_test','p'))
{
if (document.getElementById('editor_test').getElementsByTagName('p')[0])
{
var p = document.getElementById('editor_test').getElementsByTagName('p')[0];
p.focus();
window.getSelection().extend(p,0);
}
else
{
var p = document.createElement('p');
p.setAttribute('tabindex','3');
document.getElementById('editor_test').appendChild(p);
keydown_test();
}
}
}

function bb_containsNode(id,n)
{
var a = document.getElementById(id).getElementsByTagName(n);
var l = false;
for (var i=0;i<=a.length;i++) {if (window.getSelection().containsNode(document.getElementsByTagName(n)[i],true)) {l = true; break;}}
return l;
}


It works in Firefox where I need it to work though I haven't tested it with Internet Explorer. I have to scan the divisible element for textNodes not contained inside of paragraphs (shouldn't be difficult) in order to keep what is typed or somehow misplaced by any browser bug. The function as-is does resolve my original question. Hopefully this thread will in the future save some people a bit of sanity. :)

- John