How to limit number of characters per line in text area to a fixed value

In my text area, I should be able to enter only 72 characters per line. If I use, cols property set to 72, it is allowing more or less number of characters depending on character width.

Can any one help how to do it?


Duplicate of Textarea Limit characters per line Jquery or Javascript

<TEXTAREA NAME="HARD" COLS="72" ROWS="5" WRAP="HARD">

I had the same problem and tried to solve it with JavaScript. Why not just take the HTML-Code suggested by Juan Mendes?

Well, it's quite simple: It doesn't really work cross browser, or at least with Firefox 25 under Ubuntu, the maximum number of characters per line seems to be limited by the textarea width and depending on font size I could enter +-1 letter. But I wanted the number of characters per line limited to a specific value, no matter what the textarea's width is. So I came up with this code:

var maxLength = 3;
$('#mytext').on('input focus keydown keyup', function() {
    var text = $(this).val();
    var lines = text.split(/(\r\n|\n|\r)/gm); 
    for (var i = 0; i < lines.length; i++) {
        if (lines[i].length > maxLength) {
            lines[i] = lines[i].substring(0, maxLength);
        }
    }
    $(this).val(lines.join(''));
});

I have also prepared a jsFiddle. I hope this helps someone :)

And at the end just a short explanation of how this code works:

  • The function waits for one of the following events: input, focus, keydown, keyup (it may look a bit unnecessary to use this many events but I tested a lot to find this combination which works crossbrowser and always fires, no matter if only single letters are entered, the key is continually pressed or text is pasted into the textarea)
  • it gets the value of the textarea
  • then it splits the textarea at every linebreak into a new array element
  • the for loop iterates over this array and checks for every line respectively element of the array, if it exceeds the before set maxLength
  • if one line exceeds the maxLength, the line is "cut off" after maxLength characters
  • at the end, when there is no line left which is longer than maxLength characters, the array elements are joined together in a string again

EDIT: The only constrain I found out now, is that when entering an additional character at the beginning or within the line, the code "cuts off" the string at the end and not where the characters have been added. This won't matter in most cases but just keep it in mind :) Anyway, it should not be too difficult to change this function appropriately, but in most cases it will be waste of resources ;)


A small addition to complete a previous solution.
I also limit the number of lines.

It serves me in old systems where a comment of 4 lines is saved in 4 database entries.

<textarea id="mytext" rows = "4" style="width:300px"></textarea>

$(function() {

    var maxLength = 30;
    var mawRow = 4;

    $('#mytext').on('input focus keydown keyup', function() {

        //get Textearea text
        var text = $(this).val();

        //Split with \n carriage return
        var lines = text.split("\n"); 

        for (var i = 0; i < lines.length; i++) {
            if (lines[i].length > maxLength) {
                lines[i] = lines[i].substring(0, maxLength);
            }     
        }

        //On supprime ce qui dépasse... :)
        while (lines.length > 4){    
            lines.pop();
        }

        //Join with \n.
        //Set textarea
        $(this).val(lines.join("\n"));
    });
});

Here is a way to restrict a textarea in both characters per line and amount of lines. To also make the input interaction feel intuitive to a user it needs to handle (1) the value of the input and (2) the cursor position:

  1. (a) READ VALUE from the textarea, (b) DETECT IF TEXT PER LINE IS TOO LONG as required by the length restrictions, (c) PUSH OVERFLOWING TEXT from a line to the next line and (d) WRITE VALUE back to the textarea.
  2. (a) READ THE CURSOR POSITION to store the cursor position, and (b) POSITION THE CURSOR where a user would expect it after WRITE DATA.

Check out the codepen here: https://codepen.io/MattWritingCode/pen/bmexwa

This is the essential javascript code (tested on Safari and Chrome, it also works fine when pasting text into the textarea):

var charactersPerLine=document.getElementById("charactersPerLine").value;
var maxLines=document.getElementById("maxLines").value;
var textOutput="";
var onPaste=false;

function formatTextAsRequired() {
  /*
  This function handles two aspects:
  1. (a) READ VALUE from the textarea, (b) DETECT IF TEXT PER LINE IS TOO LONG  as required by the length restrictions, (c) PUSH OVERFLOWING TEXT from a line to the next line and (d) WRITE VALUE back to the textarea.
  2. (a) READ THE CURSOR POSITION to store the cursor position, and (b) POSITION THE CURSOR where a user would expect it after WRITE DATA.
  */
  var textInput=document.getElementById("flexibleInputField").value;//1a: READ VALUE
  var inputAsRows=textInput.split("\n");// create array from input => each element contains one row of the textarea
  var inputAsOneLine=textInput.replace(/(\r\n\t|\n|\r\t)/gm,"");//remove all line-breaks
  var cursorPositionOnInput=document.getElementById("flexibleInputField").selectionStart;//2a: READ CURSOR POSITION
  var cursorOffsetAfterOutput=0;//set default value for cursor offset. cursor offset is needed when re-posiotioning the cursor after WRITE DATA

  var totalRows=inputAsRows.length; //don't put inputAsRows.length in the for statement, as the array is growing in the loop which results in an infinite loop
  var row;
  var lineBreakCount=0;
  var characterCount=0;
  for (row = 0; row < totalRows; ++row) {
    if(inputAsRows[row].length>charactersPerLine){ //1b DETECT IF TEXT PER LINE IS TOO LONG 
      if (inputAsRows[row+1] === undefined) {
        inputAsRows[row+1]="";// the row did not exist
        totalRows++;
        }
      //1c PUSH OVERFLOWING TEXT: move text that is too long for this row to the next row:
      inputAsRows[row+1]=inputAsRows[row].substring(charactersPerLine)+inputAsRows[row+1];
      inputAsRows[row]=inputAsRows[row].substring(0,charactersPerLine);
      //determine, if cursor was at the end of the line that got a line-break:
      var newOutput=inputAsRows.join("\n");
      if(newOutput.substr(cursorPositionOnInput-1,1)=="\n"){
        cursorOffsetAfterOutput=1; }
      }
    }

  if(inputAsRows.length<=maxLines && inputAsOneLine.length<=(maxLines*charactersPerLine)){//data is within max number of rows and max total digits
    textOutput=inputAsRows.join("\n");
    document.getElementById("flexibleInputField").rows=inputAsRows.length;//resize textarea
    document.getElementById("errors").innerHTML="";//remove error message
    document.getElementById("count").innerHTML=inputAsOneLine.length+"/"+(maxLines*charactersPerLine);//show digits count
    if(onPaste){ cursorOffsetAfterOutput=cursorOffsetOnPaste(textInput,cursorPositionOnInput,totalRows)
      }
    }
    else //data would be too long 
    {
    document.getElementById("errors").innerHTML="This field can only have "+maxLines+" lines with "+charactersPerLine+" characters per line.";//display error message
    document.getElementById("count").innerHTML="";//remove digits count
    cursorOffsetAfterOutput=-1;
  }
  document.getElementById("flexibleInputField").value=textOutput;//1d: WRITE VALUE
  document.getElementById("flexibleInputField").selectionStart=cursorPositionOnInput+cursorOffsetAfterOutput; //2b: POSITION CURSOR
  document.getElementById("flexibleInputField").selectionEnd=cursorPositionOnInput+cursorOffsetAfterOutput; //set a single cursor, not a selection
  onPaste=false;
}

function countLineBreaks(string,lengthFromStart){
  var left=string.substr(0,lengthFromStart);
  var countOfLinebreaks=(left.split("\n")).length;
  return countOfLinebreaks;
}

function handlePaste(){
  //some improvements when pasting content can still be made (particularly on the cursor position)
  onPaste=true;
}

function cursorOffsetOnPaste(textInput,cursorPositionOnInput,totalRows){
  //offset the cursor by 1 for each added line break:
  var countOld=countLineBreaks(textInput,cursorPositionOnInput);  
  var countNew=countLineBreaks(textOutput,cursorPositionOnInput+totalRows);
  cursorOffsetAfterOutput=countNew-countOld;
  return cursorOffsetAfterOutput;
}

This is an old thread but i have just developed a little jQuery plugin solution. Check it out here. Find the readme for further details. My plugin has a bit more to it but the basic are as follows:

$(document).ready(function(){
        var linesUsed = $('#linesUsed');
        var charsUsed = $('#charsUsed');
        var errorreading = $('#errors');

        // HANDLES PASTE EVENTS
        $('.line_control').on('paste', function (e) {
            var $el = $(this);
            var lines = $el.attr("lines");
            var chars = $el.attr("chars");
            var errors = [];
            setTimeout(function (e) {
                var newLines = $el.val().split("\n");
                console.log(newLines);
                linesUsed.text(newLines.length);
                charsUsed.text(newLines[newLines.length - 1].length + 1);
                for (var i = 0, len = newLines.length; i < len; i++) {
                    if (newLines[i].length >= chars) {
                        let line = i + 1;
                        let count = newLines[i].length;
                        errors.push({
                            'line': line,
                            'count': count
                        })
                    }
                }
                if (errors.length > 0) {
                    var html = '<p>Errors:</p>';
                    var alertMessage = "Warning!\n\nYour pasted content has exceeded the line limitations. Please review the following:\n\n"
                    for (var i = 0, len = errors.length; i < len; i++) {
                        html = html + '<span>Line: ' + errors[i]['line'] + '</span></br><span>Count: ' + errors[i]['count'] + '</span></br>'
                        alertMessage = alertMessage + 'Line: ' + errors[i]['line'] + ' Over: ' + (errors[i]['count'] - chars) + ' Count: ' + errors[i]['count'] + '\n';
                    }
                    alert(alertMessage);
                    errorreading.html(html);
                }
                console.log(errors);
                if (newLines.length >= lines) {
                    linesUsed.css('color', 'red');
                    return false;
                } else {
                    linesUsed.css('color', '');
                }
                if (newLines[newLines.length - 1].length >= chars) {
                    charsUsed.css('color', 'red');
                    return false;
                } else {
                    charsUsed.css('color', '');
                }

            }, 100);
        });
        //HANDLES AND KEYDOWN EVENTS
        $('.line_control').keydown(function (e) {
            var lines = $(this).attr("lines");
            var chars = $(this).attr("chars");
            newLines = $(this).val().split("\n");
            linesUsed.text(newLines.length);
            charsUsed.text(newLines[newLines.length - 1].length + 1);
            if (newLines.length > lines && e.keyCode !== 8 && e.keyCode !== 46) {
                linesUsed.css('color', 'red');
                return false;
            } else if (e.keyCode !== 13 && e.keyCode !== 8 && e.keyCode !== 46 && newLines[newLines.length - 1].length >= chars) {
                charsUsed.css('color', 'red');
                return false;
            } else {
                linesUsed.css('color', '');
            }
        });
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<textarea class="line_control" lines="2" chars="8" style="resize: none;"></textarea>