Set focus on div contenteditable element
Asked Answered
H

11

106

I have a <div contenteditable=true> where I define by a WYSIWYG some elements. For example <p>,<h1>, etc. I would like to directly put the focus on one of these elements.

For example on <p id="p_test">. But it seems that focus() function doesn't work on <div> elements, <p> elements...

Is there another means to define the focus in my case?

Hospitable answered 5/3, 2010 at 16:15 Comment(0)
M
99

Old post but none of the solutions worked for me. I figured it out eventually though:

var div = document.getElementById('contenteditablediv');
setTimeout(function() {
    div.focus();
}, 0);
Motionless answered 11/5, 2016 at 12:3 Comment(4)
not sure why, this totally works!!! wrapping the .focus() logic in setTimeout did the trick!! thanks!Primula
I only experienced this when placing code into other element's onClick handler. My guess is that there's race condition: button clicked -> onClick handler focuses some element -> button gets focus. Placing setTimeout will postpone code which will make it to persist focus.Loring
I am from future, and this is work on the future tooDesirae
Im from 2024 and this works! Thanks!!Triggerfish
B
44

In modern browsers you can use:

var p = document.getElementById('contentEditableElementId'),
    s = window.getSelection(),
    r = document.createRange();
r.setStart(p, 0);
r.setEnd(p, 0);
s.removeAllRanges();
s.addRange(r);

But if your element is empty I got some strange problems so for empty elements you can do this:

var p = document.getElementById('contentEditableElementId'),
    s = window.getSelection(),
    r = document.createRange();
p.innerHTML = '\u00a0';
r.selectNodeContents(p);
s.removeAllRanges();
s.addRange(r);
document.execCommand('delete', false, null);

After deleting nbsp cursor stays inside p element

P.S. just ordinary space doesn't work for me

Blowzed answered 31/5, 2013 at 18:49 Comment(6)
what is the variable p in your examples?Cuticula
@BT I presume its the root P element, although he should have clarified it without leaving space for doubts.Geezer
Out of the multitude of suggested solutions out there, this is the only one that has worked for me, to allow me to give focus to a contenteditable div within a jquery dialog.Narah
Great Solution.. Thanks :)Inverson
I changed the zeroes to ones in r.setStart(p, 1) and r.setEnd(p, 1) to place the cursor at the end of the contenteditable div. I don't know why that worked, but it did. Thanks!Terryterrye
wow, this was an absolute nightmare to findFrannie
Z
32

I noticed that jQuery focus() did not work for my contenteditable DIV with width and height of 0. I replaced it with .get(0).focus() - the native javascript focus method - and it works.

Zeb answered 5/2, 2013 at 5:56 Comment(2)
Which browser? It doesn't seem to work in Safari, but Firefox and Chrome handle it fine.Pedi
Which part? Select the element and focus() e.g., document.getElementById("#mydiv").focus()Zeb
G
18

A lot of the time if you can't call .focus() on an element, it's because the tabIndex is -1, which means the tab key can't focus on it when you're pressing tab to navigate.

Changing your tabIndex to >= 0 will let you focus on the elements. If you need to do it dynamically, you can just add a tabindex >= 0 to your element in the event listener.

Gumboil answered 2/7, 2018 at 4:8 Comment(1)
this should be approved answer. "you can not call a .focus() method on an element unless it has a tabindex attribute"Zealand
P
7

You can try this code, it can auto focus in your last insert location.

let p = document.getElementById('contenteditablediv')
let s = window.getSelection()
let r = document.createRange()
r.setStart(p, p.childElementCount)
r.setEnd(p, p.childElementCount)
s.removeAllRanges()
s.addRange(r)
Podite answered 4/3, 2017 at 17:8 Comment(1)
Thanks, the partially same answer above yours didn't worked for me, I needed to add p.childElementCount instead of 1 as you wrote.Brail
R
6

To build further on @Surmon answer. If you are looking to auto focus right after your last letter/number in the text instead of the last insertion location (last insertion location will move the cursor to a new line if the child nodes are enclosed in block type tags and not plain text) then apply this small modification:

r.setStart(p.lastChild, 1);
r.setEnd(p.lastChild, 1);

This is an extended function that checks if the editor/element has any child nodes and depending on the situation sets the cursor accordingly at the end:

let p = document.getElementById('contenteditablediv');
p.focus();  // alternatively use setTimeout(() => { p.focus(); }, 0);
// this is enough to focus an empty element (at least in Chrome)

if (p.hasChildNodes()) {  // if the element is not empty
  let s = window.getSelection();
  let r = document.createRange();
  let e = p.childElementCount > 0 ? p.lastChild : p;  
  r.setStart(e, 1); 
  r.setEnd(e, 1);    
  s.removeAllRanges();
  s.addRange(r);
} 
Reno answered 21/12, 2019 at 15:55 Comment(0)
R
4

In case someone, who uses MediumEditor that manages contenteditable element, stumbles upon this issue, the following has worked for me:

editor.selectElement(editorDOMNode);
  1. editor is an instance of MediumEditor.
  2. editorDOMNode is a reference to the actual contenteditable DOM node.
  3. It is also possible to focus on a specific child node within the editor as follows:

    editor.selectElement(editorDOMNode.childNodes[0]);
    
Rechabite answered 2/3, 2016 at 10:14 Comment(0)
O
4

One more thing to check: If you have the browser's console window open, make sure the console does not have focus when you load the page, or else the element on the page won't get focus as expected.

This also seems to imply that you can't run document.querySelector('#your-elem').focus() in the console (tried with Chrome, Safari and Firefox on a Mac, Aug 2020).

Ostia answered 8/8, 2020 at 14:19 Comment(1)
Life saver! Did nothing in the console, but when trying this in the actual code: document.querySelector('.pell-content').focus(); worked perfectly, for Pell RTE. Crazy, thanks!Louettalough
S
0

Edit: I realized this question is actually asking how to programatically place the caret on a child element inside a contenteditable div (not how to place the caret on user interaction). I tried to apply solutions from the other answers, but cannot get it to work in the code widget.


Note that the click event handler will require a click to focus the element (without triggering the caret), and another click to trigger the caret. Methods such as setTimeout and .get(0) will not work as expected.

Using a mousedown event handler instead works as expected using just the focus() call. No need for other workarounds. Tested on Windows/Chrome (mouse) and Android/Chrome (touch).

jQuery example:

const ns = {
  focus: e => {
    let el = $(e.currentTarget);
    el.focus();
  }
}

$(document).ready(() => {
  $('body').on('mousedown', '[contenteditable]', ns.focus); //use mousedown instead of click

  let p = $('#auto_focus').get(0),
    s = window.getSelection(),
    r = document.createRange(),
    e = p.childElementCount > 0 ? p.lastChild : p;

  setTimeout(() => p.focus(), 0);

  //r.setStart(p, 0);
  //r.setEnd(p, 0);
  //r.setStart(e, 1);
  //r.setEnd(e, 1);
  r.setStart(p, p.childElementCount)
  r.setEnd(p, p.childElementCount)
  s.removeAllRanges();
  s.addRange(r);
});
[contenteditable] {
  border-bottom: 1px solid lightgray;
  /* prevents shifting on select */
  line-height: 1.2em;
}

[contenteditable]:focus {
  border-bottom: 1px solid black;
  /* prevents slight visual shifting on select */
  outline: 1px solid transparent;
}

[contenteditable=true]:empty:not(:focus):before {
  content: attr(placeholder) '...';
  font-style: italic;
  font-size: .9em;
  text-transform: lowercase;
  opacity: .3;
  font-weight: normal;
}

[contenteditable]>* {
  min-height: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div contenteditable=true placeholder="edit me"></div>
<div contenteditable=true placeholder="edit me, too"></div>
<div contenteditable=true>
  <p>paragraph 1</p>
  <p id="auto_focus">paragraph 2</p>
</div>
Starveling answered 30/10, 2023 at 4:36 Comment(0)
T
0

Adding to Chris' answer, it's also nice to ensure the cursor is at the end of the word/sentence:

[..]
setTimeout(function() {
  const range = document.createRange();
  const selection = window.getSelection();
  range.selectNodeContents(div);
  range.collapse(false); // <-- Set the cursor at the end of the selection
  selection?.removeAllRanges();
  selection?.addRange(range);
  div.focus();
}, 0);
Triggerfish answered 23/3 at 9:10 Comment(0)
B
-12

Bind a "click" event handler to all elements within the contenteditable div, and change the class/style on click (i.e. add class "focused" to the element);

You can identify, to the user, which element has focus by adding style such as a colorful border or an inner box-shadow. Then when you want to access the focused element in your jQuery script, just do something like this:

var focused = $('.focused');

Beatrix answered 20/4, 2012 at 20:5 Comment(2)
He's asking about actually moving the user's caret to the element, not just styling selected elements.Pedi
This isn't of any help.Cetane

© 2022 - 2024 — McMap. All rights reserved.