Limit characters in div with contenteditable

Asked

Viewed 649 times

4

I have a div with contenteditable="true" and would like the text that goes beyond 10 characters to be taxed with background red, as happens on Twitter.

My main question is how to tax only the surplus text, if you have to do it only with CSS or you need to go to Javascript, and how to do it, because that’s what I don’t know..

inserir a descrição da imagem aqui

  • It would be nice to post what you tried and what didn’t work. The way it is, the question is somewhat broad, since you can not know where exactly is your difficulty.

  • @Bacco summarized the question.

3 answers

2


Here’s a suggestion for style, I’ll leave the final result here at the beginning:

var span = document.getElementsByTagName('span')[0];

function getCaretPosition(element) {
  var caretOffset = 0;
  if (typeof window.getSelection != "undefined") {
    var range = window.getSelection().getRangeAt(0);
    var preCaretRange = range.cloneRange();
    preCaretRange.selectNodeContents(element);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    caretOffset = preCaretRange.toString().length;
  } else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
    var textRange = document.selection.createRange();
    var preCaretTextRange = document.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }
  return caretOffset;
}
function setCaret(el, pos) {
  for (var i = 0; i < el.childNodes.length; i++) {
    var node = el.childNodes[i];
    if (node.nodeType == 3) {
      if (node.length >= pos) {
        var range = document.createRange(),
          sel = window.getSelection();
        range.setStart(node, pos);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
        return -1;
      } else {
        pos -= node.length;
      }
    } else {
      pos = setCaret(node, pos);
      if (pos == -1) {
        return -1;
      }
    }
  }
  return pos;
}

function detectIE() {
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf('MSIE ');
    if (msie > 0) return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
    var trident = ua.indexOf('Trident/');
    if (trident > 0) { 
        var rv = ua.indexOf('rv:');
        return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
    }
    var edge = ua.indexOf('Edge/');
    if (edge > 0) return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
    return false;
}

var caretPositions = [];
var contents = [];
var maxLength = 10;
var event;
var div = document.getElementsByTagName('div')[0];

if(detectIE() == false){ 
	event = "input" 
}else{
	event = "keyup"; 
}
div.addEventListener(event, insertMark); 
function insertMark(e) {
  var self = e.target;
  var content = self.textContent; 
  caretPositions.push(getCaretPosition(this)); 
  contents.push(content);
  if (contents[contents.length - 1] != contents[contents.length - 2]) {
    if (content.length > maxLength) {
      self.innerHTML = content.substring(0, maxLength) + "<span class='marker'>" + content.substring(maxLength, content.length) + "</span>";
    } else {
      self.innerHTML = content;
    }
    setCaret(self, (caretPositions[caretPositions.length - 1]));
  }
}
div.addEventListener('click', function() {
  caretPositions.push(getCaretPosition(this)); 
})
div {
  box-sizing: border-box;
  width: 100%;
  height: 100px;
  background: #fff;
  border: 1px solid #666;
  padding: 7px;
  line-height: 20px;
  border-radius: 3px;
}
div span {
  background: rgba(230, 0, 0, 0.5);
  border-radius: 3px;
  line-height: 20px;
}
<div contenteditable></div>

Footsteps:

  • Check the amount of characters ignoring tags
  • Create a <span> with red background to put what you have after 10 chars inside it
  • Assign concatenation to the event keyup if the user is using IE, and input otherwise
  • Capture the position of Caret and "set it" so that at the time of the replace the pipe don’t move

The first two steps are very simple so I won’t focus too much on them.

Function to capture the position of the Caret in a div contenteditable:

function getCaretPosition(element) {
    var caretOffset = 0;
    if (typeof window.getSelection != "undefined") {
        var range = window.getSelection().getRangeAt(0);
        var preCaretRange = range.cloneRange();
        preCaretRange.selectNodeContents(element);
        preCaretRange.setEnd(range.endContainer, range.endOffset);
        caretOffset = preCaretRange.toString().length;
    } else if (typeof document.selection != "undefined" && document.selection.type != "Control") {
        var textRange = document.selection.createRange();
        var preCaretTextRange = document.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}

Addressable in: https://stackoverflow.com/questions/16736680/get-caret-position-in-contenteditable-div-including-tags

Function setCaret:

function setCaret(el, pos) {
  for (var node of el.childNodes) {
    if (node.nodeType == 3) { 
      if (node.length >= pos) {        
        var range = document.createRange(),
            sel = window.getSelection();
        range.setStart(node, pos);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);
        return -1;
      } else {
        pos -= node.length;
      }
    } else {
      pos = setCaret(node, pos);
      if (pos == -1) {
        return -1; 
      }
    }
  }
  return pos;
}

Available in: https://stackoverflow.com/questions/36869503/set-caret-position-in-contenteditable-div-that-has-children

Function to recognize IE:

function detectIE() {
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf('MSIE ');
    if (msie > 0) return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
    var trident = ua.indexOf('Trident/');
    if (trident > 0) { 
        var rv = ua.indexOf('rv:');
        return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
    }
    var edge = ua.indexOf('Edge/');
    if (edge > 0) return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
    return false;
}

Including the <span>:

var caretPositions = []; // array com as posições dos cursores
var contentLengths = []; // array com as conteudos da tag
var maxLength = 10; // máximo de caracteres
var event; // receberá o evento
var div = document.getElementsByTagName('div')[0]; // div contenteditable

if(detectIE() == false){ 
    event = "input" // se não estiver usando o IE usar "input" 
}else{
    event = "keyup"; // se estiver usando o IE usar "keyup"
}
div.addEventListener(event, insertMark); // atribui a adição ao evento keyup  ou input
function insertMark(e) {
  var self = e.target; // captura o elemento em questão
  var content = self.textContent; // captura somente o text escrito na div, sem tags
  caretPositions.push(getCaretPosition(this)); 
  contents.push(content); // adiciona ao array o conteúdo da tag a cada adição
  // só executa o bloco de código de houver tido alguma mudança no tag (ignorando navegação com as setas, por exemplo)
  if (contents[contents.length - 1] != contents[contents.length - 2]) {
    if (content.length > maxLength) {
      // seleciona do primeiro ao 10º char e concatena com uma <span> com estilo predefinido que irá conter do 10º ao último char
      self.innerHTML = content.substring(0, maxLength) + "<span class='marker'>" + content.substring(maxLength, content.length) + "</span>";
    } else {
      self.innerHTML = content; // retira a marcação 
    }
    // sempre que houver a mudança no conteúdo da div, o curso irá pro início do text, e como não se quer isso:
    setCaret(self, (caretPositions[caretPositions.length - 1])); // colocar o cursor no mesmo lugar de antes
  }
}
div.addEventListener('click', function() {
  caretPositions.push(getCaretPosition(this)); // adiciona a posição do curso ao array definido a cada click
})

Obs:

  • I’m using the textContent to capture the content, but there is also the option of innerText, see the difference here
  • I use a function to check if the user is in IE, the only difference is a small delay in the marked rendering, since IE does not support input in a div contenteditable
  • 1

    I liked the demo, etc., and until I asked the answer, I am only concerned with the following situation: Normally it is used contenteditable for WYSIWYG content, with formatting, images, Bullets, etc. I wonder what would happen in this case. It’s another thing to review IE, the JS is breaking.

  • 1

    @Bacco, thank you very much. As for the issues of use for WYSIWYG content are more in-depth and should take care of more, I thought basically the issue of AP for marking excess characters. Already the problem in IE, is basically in the use of for ...of and the event input, but I’m getting around and I already edit the answer.

  • @Bacco, I edited the answer, I had to recognize if the user was in IE, since I could not assign event input a div, or any "similar" as "Domcharacterdatamodified", instead I use the keyup that has a small delay, but in other browsers the input works and the delay does not happen.

  • 1

    This part is boring even cross browser.

1

Well, your question is very general, but I would do it this way:

    <div><p id="meuParagrafo" onkeydown="validarTexto()">
Meu texto que quero transformar aqui</p></div>

In the Javascript code:

   // Função invocada toda vez que o usuário aperta uma tecla
   function validarTexto(){
   var p = document.getElmentById('meuParagrafo').innerHTML; 
   if(p.length > 10){ // Verifica se tem mais de 10 caracteres

    // Aqui, você deve aplicar o estilo de fonte desejada
    // nos caracteres a partir do décimo elemento.
    }
}
  • How to apply the style that is the focus of the question, I edited the question.

0

To do this just take the property .innerHTML of div, check if the character size is greater than the limit, then use the function substring to cut the string and add an HTML class to the last part of it, then set the .innerHTML div with the cuts made in it. If the amount of characters does not exceed the limit, you have to take out any element with the added class.

PS: The function getCaretPosition serves to pick up the pointer position in the text, while setCaretPosition serves to set pointer position in text.

// essa função pertence à: http://stackoverflow.com/q/4811822/#4812022
// retorna a posição do ponteiro no texto
function getCaretPosition(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection !== "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type !== "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}

// essa função pertence à: http://stackoverflow.com/q/512528/#512542
// define a posição do ponteiro no texto
function setCaretPosition(elemId, caretPos) {
    var elem = document.getElementById(elemId);
    if(elem !== null) {
        if(elem.createTextRange) {
            var range = elem.createTextRange();
            range.move('character', caretPos);
            range.select();
        }else{
            if(elem.selectionStart) {
                elem.focus();
                elem.setSelectionRange(caretPos, caretPos);
            } else elem.focus();
        }
    }
}

// limite de caracteres no texto
var divLimit = 20;

// checa o texto da div
function checkDivText() {
    var inner = div.innerText;
    if(inner.length > divLimit) {
        var caretPos = getCaretPosition(div);
        // adiciona a classe "except" para os caracteres de excesso
        div.innerText =
                inner.substring(0, divLimit + 1) +
                '<span class="except">' +
                    inner.substring(divLimit + 1) +
                '</span>';
        setCaretPosition(div, caretPos);
    }else if(div.querySelector('except')) {
        var caretPos = getCaretPosition(div);
        div.innerText = inner;
        setCaretPosition(div, caretPos);
    }
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.