Focusing on Nested Contenteditable Element

Focusing on nested contenteditable element

The way [contenteditable] elements are handled by browser made any nested [contenteditable] not handling any event, the editing host is the former editable parent. See spec:

If an element is editable and its parent element is not, or if an
element is editable and it has no parent element, then the element is
an editing host. Editable elements can be nested. User agents must
make editing hosts focusable (which typically means they enter the tab
order). An editing host can contain non-editable sections, these are
handled as described below. An editing host can contain non-editable sections that contain further editing hosts.

Now as a workaround, you could make focused nested editable element the hosting host by setting any of its editable parent temporaly not editable. See e.g:

$('div.top [contenteditable]').on('focusin focusout', function(e) {
$(this).parents('[contenteditable]').prop('contenteditable', e.type === "focusout");
});

-updated jsFiddle-

Using :focus with nested contenteditable elements

In the end, I added a wrapper element around the spans that cannot be contenteditable. This makes the focus work correctly.

<div contenteditable>
<span contenteditable="false">
<span contenteditable class="focus">content</span>
</span>
</div>

I don't like this solution very much, but I also don't want to add a lot of javascript to this.

contenteditable with nested span. Who has the focus?

To make any element focusable, not only interactive content ones, you have to set tabindex attribute.

In your sample, it would be:

<div id='myeditdiv' contenteditable='true'>
<span class='foo_cl' tabindex="-1">FOO<span class='bar_cl' tabindex="-1">bar</span</span>
</div>

Note: negative tabindex makes element focusable but not tabbable because using tabbing method would start at 0 using absolute value (spec).

Now in jQuery, you could delegate focus event to these elements:

$('[contenteditable]').on('focus', '*', function(e){
e.stopPropagation();
console.log(this);
});

-jsFiddle-

As a side note, jQuery UI has a :focusable pseudo selector. If you wish to dynamically set tabindex attribute to not focusable elements, you could use:

$('[contenteditable]').find(':not(:focusable)').attr('tabindex', -1);

-jsFiddle (including jQuery UI)-

If you don't want to include jQuery UI just to get :focusable pseudo selector, you can create your own custom selector:

//include IIFE if not already including jQuery UI
(function () {
function focusable(element, isTabIndexNotNaN) {
var map, mapName, img,
nodeName = element.nodeName.toLowerCase();
if ("area" === nodeName) {
map = element.parentNode;
mapName = map.name;
if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") {
return false;
}
img = $("img[usemap='#" + mapName + "']")[0];
return !!img && $(img).is(':visible');
}
return (/^(input|select|textarea|button|object)$/.test(nodeName) ? !element.disabled :
"a" === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) &&
// the element and all of its ancestors must be visible
$(element).is(':visible');
}
$.extend($.expr[":"], {
focusable: function (element) {
return focusable(element, !isNaN($.attr(element, "tabindex")));
}
});
})();

-jsFiddle-

CSS: :focus of elements within contenteditable

I think I found a solution.

With the following code snippet you can get the parent element at the current caret position when the selection changes.

var selectedElement = null;
function setFocus(e) {
if (selectedElement)
selectedElement.style.outline = 'none';

selectedElement = window.getSelection().focusNode.parentNode;
// walk up the DOM tree until the parent node is contentEditable
while (selectedElement.parentNode.contentEditable != 'true') {
selectedElement = selectedElement.parentNode;
}
selectedElement.style.outline = '1px solid #f00';
};
document.onkeyup = setFocus;
document.onmouseup = setFocus;

Here I change the outline property manually but you could of course add a class attribute and set the style via CSS.

You still have to manually check if selectedElement is a child of our <div> with the contenteditable attribute. But I think you can get the basic idea.

Here's the updated JSFiddle.

EDIT: I updated the code as well as the JSFiddle to make it work in Firefox and Internet Explorer 9+, too. Unfortunately these Browsers do not have a onselectionchange event handler, so I had to use onkeyup and onmouseup.

HTML5 - contenteditable=false nested in contenteditable=true

This is a bug in Internet Explorer 11. Section 7.6.1 of the HTML5 specification states:

The contenteditable attribute is an enumerated attribute whose keywords are the empty string, true, and false. The empty string and the true keyword map to the true state. The false keyword maps to the false state. In addition, there is a third state, the inherit state, which is the missing value default (and the invalid value default).

The true state indicates that the element is editable. The inherit state indicates that the element is editable if its parent is. The false state indicates that the element is not editable.

This means that a contenteditable=false element within a contenteditable=true element should not be editable.

Furthermore, a contenteditable=true element within a contenteditable=true element should have its content editable. Meaning the answer to the question you linked is actually invalid (although when it was posted this may have been the case).

Focusing contentEditable=true parent by clicking on contentEditable=false child nodes

Check this:
HTML contenteditable with non-editable islands

Basic idea is to use empty span with ::before { content:"caption"; }

span.non-editable::before {   content:attr(name);   background:gold;   display:inline-block;  }
<div class="editable" contentEditable="true">Some more text here  <span class=non-editable name="placeholder"></span>  Hi this is my content</div>


Related Topics



Leave a reply



Submit