Handling Tabs in Textareas across browsers

Sooner or later when you are developing a web based application you will want to enable tabs in textareas so you can format some HTML or CSS. My first thought is why is this not a function of the web browser it would make sense to have a property you could set to enable tabbing. But no you need to use JavaScript. You can find plenty of solutions searching on Google. Most solutions, like the first one I found , detect when you press the tab key cancel the event and insert a tab into the textarea instead. This method is simplistic and limited when you are used to a IDE and want to indent blocks of text. 

Required Textarea Tab Handling Features

  • allow for indenting multi-line blocks of text .
  • use the shift key to outdent text.
  • spaces at the start of a line are removed if there are no tabs left.
  • selected text is not deleted when tabs are inserted.
  • work across browser or at least IE7, Firefox, Google Chrome and Safari.
  • the browser window doesn't scroll when you tab.
Hands down the best solution I have seen so far is the 'Tabby' the Tabs in Textarea Plugin for Javascript jQuery by Ted Devito. I came across the plugin after I had finished developing my own solution to the problem. Tabby does use jQuery so there is a bit of overhead for the script, that's ok if you are already using jQuery. The JavaScript solution I developed does not require jQuery to work and once minified is only 3KB.

The JavaScript Handle Tab Key Down Function

To provide a cross browser textarea tab handling solution you need to have different code for IE. This is due mainly to the JavaScript  text selection. The quirksmode website has a good introduction to browser compatibility and text range.

1 function tabHandleKeyDown(evt) { 
2     var tab = String.fromCharCode(9); 
3     var e = window.event || evt; 
4     var t = e.target ? e.target : e.srcElement ? e.srcElement : e.which; 
5     var scrollTop = t.scrollTop; 
6     var k = e.keyCode ? e.keyCode : e.charCode ? e.charCode : e.which; 
7     if (k == 9 && !e.ctrlKey && !e.altKey) { 
8         if(t.setSelectionRange){ 
9             e.preventDefault(); 
10             var ss = t.selectionStart; 
11             var se = t.selectionEnd; 
12             // Multi line selection 
13             if (ss != se && t.value.slice(ss,se).indexOf("\n") != -1) { 
14                 if(ss>0){ 
15                     ss = t.value.slice(0,ss).lastIndexOf("\n")+1; 
16                 } 
17                 var pre = t.value.slice(0,ss); 
18                 var sel = t.value.slice(ss,se); 
19                 var post = t.value.slice(se,t.value.length); 
20                 if(e.shiftKey){ 
21                     var a = sel.split("\n"
22                     for (i=0;i< />
23                         if(a[i].slice(0,1)==tab||a[i].slice(0,1)==' ' ){ 
24                             a[i]=a[i].slice(1,a[i].length) 
25                         } 
26                     } 
27                     sel = a.join("\n"); 
28                     t.value = pre.concat(sel,post); 
29                     t.selectionStart = ss; 
30                     t.selectionEnd = pre.length + sel.length; 
31                 } 
32                 else
33                     sel = sel.replace(/\n/g,"\n"+tab); 
34                     pre = pre.concat(tab); 
35                     t.value = pre.concat(sel,post); 
36                     t.selectionStart = ss; 
37                     t.selectionEnd = se + (tab.length * sel.split("\n").length); 
38                 } 
39             } 
40             // Single line selection 
41             else { 
42                 if(e.shiftKey){  
43                     var brt = t.value.slice(0,ss); 
44                     var ch = brt.slice(brt.length-1,brt.length); 
45                     if(ch == tab||ch== ' '){ 
46                         t.value = brt.slice(0,brt.length-1).concat(t.value.slice(ss,t.value.length)); 
47                         t.selectionStart = ss-1; 
48                         t.selectionEnd = se-1; 
49                     } 
50                 } 
51                 else
52                     t.value = t.value.slice(0,ss).concat(tab).concat(t.value.slice(ss,t.value.length)); 
53                     if (ss == se) { 
54                         t.selectionStart = t.selectionEnd = ss + tab.length; 
55                     } 
56                     else { 
57                         t.selectionStart = ss + tab.length; 
58                         t.selectionEnd = se + tab.length; 
59                     } 
60                 } 
61             } 
62         } 
63         else
64             e.returnValue=false
65             var r = document.selection.createRange(); 
66             var br = document.body.createTextRange(); 
67             br.moveToElementText(t); 
68             br.setEndPoint("EndToStart", r); 
69             //Single line selection 
70             if (r.text.length==0||r.text.indexOf("\n") == -1) { 
71                 if(e.shiftKey){      
72                     var ch = br.text.slice(br.text.length-1,br.text.length); 
73                     if(ch==tab||ch==' '){ 
74                         br.text = br.text.slice(0,br.text.length-1) 
75                         r.setEndPoint("StartToEnd", br); 
76                     } 
77                 } 
78                 else
79                     var rtn = t.value.slice(br.text.length,br.text.length+1); 
80                     if(rtn!=r.text.slice(0,1)){ 
81                         br.text = br.text.concat(rtn);  
82                     } 
83                     br.text = br.text.concat(tab);  
84                 } 
85                 var nr = document.body.createTextRange(); 
86                 nr.setEndPoint("StartToEnd", br); 
87                 nr.setEndPoint("EndToEnd", r); 
88                 nr.select(); 
89             } 
90             //Multi line selection 
91             else
92                 if(e.shiftKey){      
93                     var a = r.text.split("\r\n"
94                     var rt = t.value.slice(br.text.length,br.text.length+2); 
95                     if(rt==r.text.slice(0,2)){ 
96                         var p = br.text.lastIndexOf("\r\n".concat(tab)); 
97                         if(p!=-1){ 
98                             br.text = br.text.slice(0,p+2).concat(br.text.slice(p+3,br.text.length)); 
99                         } 
100                     } 
101                     for (i=0;i< />
102                         var ch = a[i].length>0&&a[i].slice(0,1); 
103                         if(ch==tab||ch==' '){ 
104                             a[i]=a[i].slice(1,a[i].length) 
105                         } 
106                     } 
107                     r.text = a.join("\r\n"); 
108                 } 
109                 else
110                     if(br.text.length>0){ 
111                         var rt = t.value.slice(br.text.length,br.text.length+2); 
112                         if(rt!=r.text.slice(0,2)){ 
113                             r.text = tab.concat(r.text.split("\r\n").join("\r\n".concat(tab))); 
114                         } 
115                         else
116                             var p = br.text.slice(0,ss).lastIndexOf("\r\n")+2;   
117                             br.text = br.text.slice(0,p).concat(tab,br.text.slice(p,br.text.length)); 
118                             r.text = r.text.split("\r\n").join("\r\n".concat(tab)); 
119                         } 
120                     } 
121                     else
122                         r.text = tab.concat(r.text).split("\r\n").join("\r\n".concat(tab)); 
123                     } 
124                 }  
125                 var nr = document.body.createTextRange(); 
126                 nr.setEndPoint("StartToEnd", br); 
127                 nr.setEndPoint("EndToEnd", r); 
128                 nr.select(); 
129             } 
130         } 
131     } 
132     t.scrollTop = scrollTop; 
133

Comments
Michael Friday April 24 2009 03:09 p.m.
Hi! I am a web-developer, and i write simple blog for training. I want to use ur code for this. But I not so pro in JS and I don't understand, how to use ur code. I try to use event