6

I have seen a few questions about this on StackOverflow, but it seems hard to find a jQuery-based solution. Hence, I would like to ask this question.

I would like to replace text on-the-fly inside a div with the attribute contenteditable="true".

I'm looking for a jQuery-based solution that will do the following:

  • Automatically replace written text on-the-fly (as you type)
  • Being able to continue writing (whilst the replacement is being done)

I looked at SC Editor (http://www.sceditor.com/), it seems like it does exactly that (for instance, if you try type :) it gets replaced by an emoticon.

I think a good start would be an array with all the elements to replace:

$.settings = {
    path: 'https://example.com/images/',
    emoticons: {
        ':(' : 'stupid.jpg',
        ':)' : 'smart.jpg',
    }
}

I have been unable to find good examples of this. Would be happy if somebody can share their thoughts and any code regarding this.

How would the replacement get done in the best possible way, with the above code?

Robin
  • 177
  • 10
  • I don't know if its the best way but you can add a listener to the input and make it read character when you type if the last character writen matches the last one of your emotion you test possible prior characters that match any emotion. If i write :( when it catcher ( it will font for an : if finds replace by your emotion. – Rodrigo Dec 19 '15 at 21:49

3 Answers3

1

I found this. If you adjust it, it might suit your needs. It replaces { with {} and ( with () and the cursor ends up in the middle.

    <script type="text/javascript">
        $(document).ready(function () {
            $("#d").keypress(function (e) {
 
       
                    var charTyped = String.fromCharCode(e.which);
                    if (charTyped == "{" || charTyped == "(") {
                        // Handle this case ourselves
                        e.preventDefault();

                        var sel = window.getSelection();
                        if (sel.rangeCount > 0) {
                            // First, delete the existing selection
                            var range = sel.getRangeAt(0);
                            range.deleteContents();

                            // Insert a text node with the braces/parens
                            var text = (charTyped == "{") ? "{}" : "()";
                            var textNode = document.createTextNode(text);
                            range.insertNode(textNode);

                            // Move the selection to the middle of the text node
                            range.setStart(textNode, 1);
                            range.setEnd(textNode, 1);
                            sel.removeAllRanges();
                            sel.addRange(range);
                        }
                    }
         

            });
        });
    </script>
</head>

<body>
    <div id="d" contentEditable="true">....</div>
</body>
</html>
peter
  • 40,314
  • 5
  • 58
  • 99
Bindrid
  • 3,350
  • 2
  • 15
  • 18
0
$('div').keyup(function(){
    //make here for loop which replace all emoticons
    $(this).text().replace(':(', 'stupid.jpg');
});
Mi-Creativity
  • 9,101
  • 10
  • 33
  • 44
wiz6
  • 51
  • 7
  • if you are using a text control, you will not be able to insert images. If you are using a div, then you will have to capture every key stroke check the key and see what html control should be used in its place, all the while, keeping track of where the cursor should be. That is what S C Editor is doing. – Bindrid Dec 19 '15 at 22:02
0

Posting what I eventually wrote after failing to find an answer to this question. I hope that it will be helpful to someone else who comes to this question looking for an answer (:

I'm going to post a much more general find and replace solution (contained in a class). This is for content editable divs and works while the user is typing, additionally it does not affect the caret position. This implementation uses a case insensitive search (although it would be trivial to disable this in the code). Another advantage this has is that it will work even if you are typing in the middle of a paragraph (not just at the end of the line) and will work on pasted text. Give it a go!

class FindAndReplace {

 constructor($contentEditable, findAndReplaceData) {

  var self = this;

  $contentEditable.on('input blur', function () {

   var textNodes = self.getTextNodes($contentEditable);

   textNodes.each(function (i) {

    // Perform all replacements on text
    findAndReplaceData.forEach(function (findAndReplaceDatum) {

     var find = findAndReplaceDatum.find;
     var replace = findAndReplaceDatum.replace;

     var regexEscapedFind = self.escapeRegExp(find);
     var regexEscapedReplace = self.escapeRegExp(replace);

     var regexEscapedCaseInsensitiveFind = self.makeRegexCaseInsensitive(regexEscapedFind);

     // Case insensitive search for the find with a negative lookahead to check its not a case sensitive match of the replacement (aka to check its actually going to make a difference)
     var regexString = `(?!${regexEscapedReplace})${regexEscapedCaseInsensitiveFind}`;

     do {

      // Get the latest version of the text node
      textNode = self.getTextNodes($contentEditable)[i];
      var text = textNode.data;

      var regex = new RegExp(regexString);
      var matchIndex = text.search(regex);
      var matchFound = (matchIndex !== -1);

      if (matchFound) {

       // Select the match
       var range = document.createRange();
       range.setStart(textNode, matchIndex);
       range.setEnd(textNode, matchIndex + find.length);
       // Delete it
       range.deleteContents();

       // Create the replacement node
       var textNode = document.createTextNode(replace);
       // Insert it
       range.insertNode(textNode);

       // Set the range to the end of the selected node
       range.collapse(false);
       // Set the user selection the range
       var sel = window.getSelection();
       sel.removeAllRanges();
       sel.addRange(range);

       // Make sure there a no adjacent or empty text nodes
       $contentEditable[0].normalize();

      }

     } while (matchFound)

    });

   });

  });

 }

 escapeRegExp(string) {

  // https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string

 }

 getTextNodes($contentEditable) {
  return $contentEditable.contents().filter(function () {
   return this.nodeType == 3; // Text node
  });

 }

 makeRegexCaseInsensitive(string) {

  var stringArray = string.split('');

  stringArray = stringArray.map(function (char) {

   if (char.toLowerCase() !== char.toUpperCase())
    return '[' + char.toLowerCase() + char.toUpperCase() + ']';
   else
    return char;

  });

  return stringArray.join('');

 }

}
div {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<script>

$(function(){

  var findAndReplaceData = [
   {
      'find': 'find me',
      'replace': 'and replace with me!'
    },
    {
      'find': 'foo',
      'replace': 'bar'
    },
    {
      'find': 'no',
      'replace': 'yes'
    }
  ];

  $contentEditable = $('div');

  new FindAndReplace($contentEditable,findAndReplaceData);

});

</script>

<div contenteditable="true"></div>
Henry Howeson
  • 514
  • 5
  • 18