Limit characters in div with contenteditable

4

I have a div with contenteditable="true" and I would like text that exceeds 10 characters to be taxed with background red, as in Twitter.

My main question is how to only tax the surplus text, if you have to do this with only CSS or you need to go to JavaScript, and how to do it, because that's what I do not know ..

    
asked by anonymous 30.07.2016 / 20:29

3 answers

2

Here's a suggestion for the 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>

Steps:

  • Check the number of characters by ignoring tags
  • Create a <span> with red background to put what you have after 10 chars inside it
  • Assign concatenation to event keyup if user is using IE, and input otherwise
  • Capture the position of caret and "set it" so that replace does not move

The first two steps are very simple so I will not focus too much on them.

Function to capture the position of 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;
}

Available in: link

SetCaret function:

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 at: link

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 <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 textContent to capture content, but there's 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 rendering the markup, since IE does not support input in a div contenteditable
31.07.2016 / 02:36
1

Well, your question is very generic, but I would do it like this:

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

In 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.
    }
}
    
30.07.2016 / 23:38
0

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

PS : The function getCaretPosition is used to get the position of the pointer in the text, while setCaretPosition is used to define the position of the pointer in the 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);
    }
}
    
31.07.2016 / 03:04