2

I have a contenteditable div with divs inside them like this:

<div id="wrapper" contenteditable=true onkeydown="preventTab(event);">
  <div id="line1" class="line"></div>
  <div id="line2" class="line"></div>
  <div id="line3" class="line"></div>
  <div id="line4" class="line"></div>
</div>

With CSS, using ::before for line numbers , this looks like this:

example

Now, I want, whenever someone presses tab, it automatically inserts four spaces instead of a tab. I got this function, but it doesn't do anything after it has fired the events.

function preventTab(event) {

    event = event || window.event;

    if (event.keyCode === 9) {

        // for loop, so the user can set the amount of spaces they want.
        for (let i = 0; i < tabSize; i++) {
            let e = new KeyboardEvent("keydown", {
                bubbles: true,
                code: "Space",
                key: " ",
                keyCode: 32
            });

            event.target.dispatchEvent(e);
        }

        event.preventDefault();
    }
}

As I said, nothing really happens (maybe because isTrusted: false?), also no errors are thrown.
By the way, I have noticed this question with the same title, but that heavily depends on the jQuery library and I'm looking for a pure JavaScript approach.

Sacha
  • 819
  • 9
  • 27
  • https://stackoverflow.com/questions/6637341/use-tab-to-indent-in-textarea and instead of a `\t`, put 4 spaces – Ismael Miguel Aug 22 '18 at 19:43
  • @IsmaelMiguel a `div` doesn't have the properties `selectionStart` and `selectionEnd`, so that won't work :( – Sacha Aug 22 '18 at 19:47
  • 1
    https://stackoverflow.com/questions/5669448/get-selected-texts-html-in-div try getting the selection and see what you can do with it first – Ismael Miguel Aug 22 '18 at 22:15
  • Have you considered doing this after the content has been submitted? This seems like a much easier, more robust way to handle it. – user1751825 Aug 22 '18 at 22:50

2 Answers2

1

Simulating clicks and keystrokes is considered a security risk. According to the W3C UI Events Working Draft:

"Most untrusted events will not trigger their default actions..."

Instead you can manipulate the contents of the contenteditable=true element using selections and ranges like I have demonstrated in this snippet.

let tabSize = 2;
let spaces = '';
for (i = 0; i < tabSize; i++) {
  spaces += ' ';
}

document.getElementById('editor').addEventListener('keydown', preventTab);

function preventTab(event) {
  if (event.keyCode === 9) {
    event.preventDefault();
    let selection = window.getSelection();
    selection.collapseToStart();
    let range = selection.getRangeAt(0);
    range.insertNode(document.createTextNode(spaces));
    selection.collapseToEnd();
  }
}
html,
body {
  height: 100%;
  width: 100%;
  margin: 0;
}

#editor {
  width: 100%;
  height: 100%;
  font-family: monospace;
  white-space: pre;
}
<div id="editor" contenteditable=true></div>
Besworks
  • 4,123
  • 1
  • 18
  • 34
  • Thanks for your answer! I thought only `HTMLTextAreaElement` had selection properties, I didn't know of `Window` having them too. Works as expected! – Sacha Aug 22 '18 at 22:59
  • I can't seem to get it to work fully for me, It will only insert a single space only after another character. I've changed up the code abit to try to get it to work but it still refuses. This is the only answer that I've found that even does anything. `let node=document.createTextNode(' '); node.innerHTML = '    '; range.insertNode(node);` – vzybilly May 17 '19 at 05:33
  • @vzybilly, if you run the snippet in the answer does it work as expected? I've only tested this in Chrome & Firefox but it's still working for me. I'm not sure what conditions you've introduced that would make it behave the way you're describing... By the way, you can't create a Text node with HTMLEntities in it like you've described. For starters, Text nodes don't even have an innerHTML setter. Furthermore, if you use an   entity in the value passed to createTextNode you would end up with the plain text " " as the content instead of an actual space character. – Besworks May 18 '19 at 17:28
  • Found the issue, The div needs `white-space:pre;` in its style. I remember putting it in somewhere before but it messed up the entire page, but putting it in on only that div solved the issue. Hope this helps others and thank you for your help. – vzybilly May 18 '19 at 18:15
0

Have you tried inserting one &nbsp; per space instead of multiple empty characters? In HTML multiple empty spaces together will not render unless you use &nbsp;.

andriusain
  • 1,211
  • 10
  • 18