Tabs in a TextBox

Anyone who uses the web and happens to be programmer knows that at some point you are in a HTML TextBox and as your typing away you hit the tab to indent the line….. and you end up in the next field.

This is correct, but when you are typing in code it can get really annoying and you end up having to use the space bar to fake out the indentation.

We’ll, I couldn’t take it anymore so I wrote my own JQuery plug-in that will allow you to do real tabbing in an TextBox. Let me state a few things:

  • I know there are others out there but most had issues where it would support the tab but not put the cursor at the tab or if it was a tab that caused the TextBox to invoke the vertical scrollbar, it would screw up. I’ve addressed these issues
  • Some of the other libraries, if you cut/paste in text with tabs it wouldn’t follow the indentation of the previous line. Mine does
  • This plug works perfectly in FireFox and very well in I.E. (there appears to be a minor bug with I.E. placing an ASCII 10 character and not just a 13 so it gets a little sketchy cursor positioning wise under a specific instance where you had a previously tabbed line and you hit enter when the cursor is in the middle of a word). As I don’t use I.E. I’m not going to worry about it unless someone asks me to 🙂

Requirements

The Code

<html>
<head>
<script type="text/javascript" src="jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="jquery-fieldselection.js"></script>
<script type="text/javascript">
(function( $ ) {
       $.fn.tabSupport = function() {
               var CAR_RETURN = '\n';
               var TAB = '\t';
               this.keydown(function(event) {
                       if (event.which == 9) {
                               event.preventDefault();
                               var pos = $(this).getSelection();
                               scrollLoc = $(this)[0].scrollTop;
                               pos.start = pos.start + 1;
                               pos.end = pos.end + 1;
                               $(this).replaceSelection(TAB);
                               if (!$.browser.msie) positionCursor($(this), pos, buildTabs($(this).val(), pos));
                               $(this)[0].scrollTop = scrollLoc; 
                       }
               });

               this.keyup(function(event) {
                       if (event.which == 13) {
                               var pos = $(this).getSelection();
                               var tabs = buildTabs($(this).val(), pos);
                               scrollLoc = $(this)[0].scrollTop;
                               if (tabs.length > 0) {
                                       $(this).replaceSelection(tabs);
                               }
                               if (!$.browser.msie) positionCursor($(this), pos, tabs);
                               $(this)[0].scrollTop = scrollLoc; 
                       }
               });

               function buildTabs(data, pos) {
                       var prevLineEnd = data.substring(0, pos.end).lastIndexOf(CAR_RETURN);
                       var prevLineStart = ($.browser.msie)
                               ?data.substring(0, prevLineEnd-1).lastIndexOf(CAR_RETURN)
                               :data.substring(0, prevLineEnd-1).lastIndexOf(CAR_RETURN)+1;
                       if (data.charCodeAt(prevLineStart) == 10) prevLineStart ++; 

                       var tabs = '';
                       var tc = 0;
                       for (var i = prevLineStart; i <= prevLineEnd; i++) {
                               if (data.charAt(i) != TAB) break;
                               tabs += TAB;
                               tc++;
                       }
                       $("#tabCount").val(tc);
                       return tabs;
               }

               function positionCursor(obj, pos, tabs) {
                       if (obj[0].setSelectionRange) {
                               obj[0].setSelectionRange(pos.start+tabs.length, pos.end+tabs.length);
                       }
                       else if (obj[0].createTextRange) {
                           var range = obj[0].createTextRange();
                           range.collapse(true);
                           range.moveEnd('character', pos.start);
                           range.moveStart('character', pos.end);
                           range.select();
                       }
               }
       }
}) ( jQuery );

Usage

<script type="text/javascript">
$(function() {
       $('#testTextArea').tabSupport();
});
</script>

<form>
<textarea id="testTextArea" rows="20" cols="40"></textarea>
</form>

Quick Explanation

Without going into too much detail the code watches for the ENTER and TAB button to be pressed. When the TAB button is pressed it’s normal functionality is disabled (event.preventDefault();) then it uses the fieldSelection library to find the position in the TextArea and then inserts a ‘\t’ into wherever TAB was pressed.

The ENTER button when pressed isn’t handled till after the button is up so that the browser still causes the ENTER function. If you do it on keyPress and not keyUp the browser will not do the line feed and the TextBox won’t scroll (even if you inject a \n). On keyUp the code looks are the previous line, determines how many \t’s there are and inserts that amount into the beginning of the existing line. In this way we support cut/paste as well as people hopping around in the document and changing the tabbing.

Example

Click here to try it!

About sseaman

Connect with me on Google+
This entry was posted in JavaScript, Programming and tagged , . Bookmark the permalink.

2 Responses to Tabs in a TextBox

Leave a Reply

Your email address will not be published.