74

I would like a textarea that handles a situation of pressing tab key.

In default case if you press a tab key then focus leaves the textarea. But what about the situation when user wants to type tab key in textarea?

Can I catch this event and return focus to the textarea and add a tab to a current cursor position?

1
  • I reviewed some of the existing answers and they all fall short in some way (no undo, poor performance, incorrect selection after unindent). I recently wrote a small module that handles this correctly, called indent-textarea Commented Dec 15, 2019 at 21:07

6 Answers 6

133

You can: http://jsfiddle.net/sdDVf/8/.


$("textarea").keydown(function(e) { if(e.keyCode === 9) { // tab was pressed // get caret position/selection var start = this.selectionStart; var end = this.selectionEnd; var $this = $(this); var value = $this.val(); // set textarea value to: text before caret + tab + text after caret $this.val(value.substring(0, start) + "\t" + value.substring(end)); // put caret at right position again (add one for the tab) this.selectionStart = this.selectionEnd = start + 1; // prevent the focus lose e.preventDefault(); } }); 
Sign up to request clarification or add additional context in comments.

12 Comments

@Amine: To prevent the default tab function of the browser. I now see it's not necessary. I'm going to have a search on this. EDIT: return false seems to include preventDefault: stackoverflow.com/questions/1357118/….
Implemented selection overwrite too.
@sergzach: My solution does not work at all in IE8, because it does not support selectionStart/selectionEnd. You'd have to create text ranges, but I still don't understand how that works I'm afraid...
This brakes the browsers undo feature (Ctrl+z)
Not sure how/why this was missed, on the last substring call start should be given as the first argument: + value.substring(start, end)); -- Otherwise tabbing when selecting text erases the text.
|
32

Here is a modified version of pimvdb's answer that doesn't need JQuery:

document.querySelector("textarea").addEventListener('keydown',function(e) { if(e.keyCode === 9) { // tab was pressed // get caret position/selection var start = this.selectionStart; var end = this.selectionEnd; var target = e.target; var value = target.value; // set textarea value to: text before caret + tab + text after caret target.value = value.substring(0, start) + "\t" + value.substring(end); // put caret at right position again (add one for the tab) this.selectionStart = this.selectionEnd = start + 1; // prevent the focus lose e.preventDefault(); } },false); 

I tested it in Firefox 21.0 and Chrome 27. Don't know if it works anywhere else.

3 Comments

To apply this to all textarea use querySelectorAll, enumerate the list returned, and add the event listener to each element.
To get this approach to work, you need to attach the eventListener to individual DOM elements. To do that: 1) get all the elements document.querySelectorAll("textarea") 2) loop though the array to attach the event listener to each element. Then it should work
Breaks the undo functionality as the accepted answer.
16

Good god, all previous answers failed to provide the commonly decent (i.e. for programmers) tab control.

That is, a hitting TAB on selection of lines will indent those lines, and SHIFTTAB will un-indent them.

_edited (Nov 2016): keyCode replaced with charCode || keyCode, per KeyboardEvent.charCode - Web APIs | MDN

(function($) { $.fn.enableSmartTab = function() { var $this; $this = $(this); $this.keydown(function(e) { var after, before, end, lastNewLine, changeLength, re, replace, selection, start, val; if ((e.charCode === 9 || e.keyCode === 9) && !e.altKey && !e.ctrlKey && !e.metaKey) { e.preventDefault(); start = this.selectionStart; end = this.selectionEnd; val = $this.val(); before = val.substring(0, start); after = val.substring(end); replace = true; if (start !== end) { selection = val.substring(start, end); if (~selection.indexOf('\n')) { replace = false; changeLength = 0; lastNewLine = before.lastIndexOf('\n'); if (!~lastNewLine) { selection = before + selection; changeLength = before.length; before = ''; } else { selection = before.substring(lastNewLine) + selection; changeLength = before.length - lastNewLine; before = before.substring(0, lastNewLine); } if (e.shiftKey) { re = /(\n|^)(\t|[ ]{1,8})/g; if (selection.match(re)) { start--; changeLength--; } selection = selection.replace(re, '$1'); } else { selection = selection.replace(/(\n|^)/g, '$1\t'); start++; changeLength++; } $this.val(before + selection + after); this.selectionStart = start; this.selectionEnd = start + selection.length - changeLength; } } if (replace && !e.shiftKey) { $this.val(before + '\t' + after); this.selectionStart = this.selectionEnd = start + 1; } } }); }; })(jQuery); $(function() { $("textarea").enableSmartTab(); })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <textarea rows="10" cols="80"> /* Just some code to edit with our new superTab */ (function($) { $.fn.enableSmartTab = function() { $this = $(this); $this.keydown(function(e) { if ((e.charCode === 9 || e.keyCode === 9) && !e.metaKey && !e.ctrlKey && !e.altKey) { e.preventDefault(); } } } } </textarea>

5 Comments

I've just write my code to achieve this feature. In circumstances where you must not use jQuery, consider using my code.
@K._ nice code, but I would recommend running it through babel or google closure to remove the ES6 components. ES6 is great, but there are just too many users who aren't using ES6 compatible browsers. Also event.key is not (IIRC) consistent across browsers, keys maybe called different things in different browsers, and Safari doesn't support the property at all. This is the other reason people use jQuery, although often the forgotten one, it's to guarantee compatibility across browsers. I'm not criticizing your efforts, I think you did great work, but it has to work be cross-browser
@K._ if you look at the code I wrote, there's actually not very much jQuery in there. Excluding the listener addition, the only actual use of jQuery is $.fn.val. The handler itself already uses native event properties already.
What does ~ and !~ mean?
@1.21gigawatts it's the bitwise not operator, there's a pretty good explanation for why it is used here here
7

In Vanilla (Default) JS this would be:

var textareas = document.getElementsByTagName('textarea'); if ( textareas ) { for ( var i = 0; i < textareas.length; i++ ) { textareas[i].addEventListener( 'keydown', function ( e ) { if ( e.which != 9 ) return; var start = this.selectionStart; var end = this.selectionEnd; this.value = this.value.substr( 0, start ) + "\t" + this.value.substr( end ); this.selectionStart = this.selectionEnd = start + 1; e.preventDefault(); return false; }); } }
textarea { border: 1px solid #cfcfcf; width: 100%; margin-left: 0px; top: 0px; bottom: 0px; position: absolute; }
<textarea> var x = 10; var y = 10; </textarea>

3 Comments

Thanks, man. People don't realize that not everyone is using jQuery.
@Mark Can you decrease the tab size?
Is this better?
1

Found this while searching google. I made a really short one that can also indent and reverse indent selections of text:

 jQ(document).on('keydown', 'textarea', function(e) { if (e.keyCode !== 9) return; var Z; var S = this.selectionStart; var E = Z = this.selectionEnd; var A = this.value.slice(S, E); A = A.split('\n'); if (!e.shiftKey) for (var x in A) { A[x] = '\t' + A[x]; Z++; } else for (var x in A) { if (A[x][0] == '\t') A[x] = A[x].substr(1); Z--; } A = A.join('\n'); this.value = this.value.slice(0, S) + A + this.value.slice(E); this.selectionStart = S != E ? S : Z;; this.selectionEnd = Z; e.preventDefault(); }); 

Comments

1

Enable tabbing inside (multiple) textarea elements

Correcting @alexwells answer and enable a live demo

var textAreaArray = document.querySelectorAll("textarea"); for (var i = textAreaArray.length-1; i >=0;i--){ textAreaArray[i].addEventListener('keydown',function(e) { if(e.keyCode === 9) { // tab was pressed // get caret position/selection var start = this.selectionStart; var end = this.selectionEnd; var target = e.target; var value = target.value; // set textarea value to: text before caret + tab + text after caret target.value = value.substring(0, start) + "\t" + value.substring(end); // put caret at right position again (add one for the tab) this.selectionStart = this.selectionEnd = start + 1; // prevent the focus lose e.preventDefault(); } },false); }
<textarea rows="10" cols="80"></textarea> <textarea rows="10" cols="80"></textarea>

1 Comment

Using vanilla javascript to enable tabbing inside textarea elements

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.