0

I'm trying to replace a certain string inside of a contenteditable div. Replace() function is not working for me, unsure where I am going wrong.

My code:

<div contenteditable="true" id="contenteditableDiv"></div>

$(document).ready(function(){
    $('#contenteditableDiv').on('keyup', function() {

        var content = $('#contenteditableDiv').html();
        var watcher = (content.match(/cat/gi)||[]).length;

        if (watcher === 1) {
            $('#contenteditableDiv').html().replace(/cat/g, "dog");
        };
    });
});

This is a somewhat simplified example (replacing the word 'cat' with the word 'dog'). Eventually, I plan to trigger this action when a sequence of strings is typed (ie, four sequential linebreaks).

user3180105
  • 291
  • 5
  • 14
  • That's kind of a duplicate of your own question here, no? https://stackoverflow.com/questions/50074303/trigger-event-based-on-specific-inputs-into-a-textarea But now that you got code from someone, you have an "actual" question. – Takit Isy Apr 28 '18 at 20:17
  • it's not a duplicate at all. It's a separate issue that happens to be on the same project. greatly appreciate the solution you've provided, very helpful! – user3180105 Apr 28 '18 at 20:28
  • Both are much easier to resolve without jquery. It is confusing you a lot i can see it. – NVRM Apr 28 '18 at 21:51
  • @Cryptopat can you elaborate? – user3180105 Apr 29 '18 at 01:16
  • I just did. Good luck. – NVRM Apr 29 '18 at 04:55

5 Answers5

3

Problem

From what I have gathered from the question is:

  • A contenteditable <div> is being used.

  • As the user types, if a keyword ("cat") is entered it will be replaced by a new keyword ("dog").

  • keyup event fails to facilitate this behavior.

Explination

Keyboard events such as keyup are too temperamental:

"Focusable elements can vary between browsers, but form elements can always get focus so are reasonable candidates for this event type."

-- jQuery - .keyup()

So the key to receiving events like keyboard events is being able to get focus. Elements that can get focus 100% the time are form elements (ex. <input>, <textarea>, etc.). While keyboard events are ok on form elements, change and input events are specialized exclusively for form events.

Solution

Since OP code requires a contenteditable <div> and not a <textarea>, the Demo has:

  • One div#view that is contenteditable

  • One textarea#edit that is under div#view with only it's cursor visible.

  • As the user types into div#view, textarea#edit is actually listening for the input event and div#view listens for the keyup event.

  • When the input event happens, #edit immediately sets the text of #view to that of its own value. On each keyup event #view sets the focus back to #edit.

  • So basically, div#view and textarea#edit occupy the same space with #view being up front and #edit behind #view. User input is transparent text on #edit but its cursor is visible (because it's always getting focus from #view during keyup event). #view gets its text from #edit.

As a bonus, the user enters any string to change (like "cat") and enters the string to change to (like "dog").

Note: The reason why I went for such a convoluted solution is because focus of the cursor. Typing without one feels artificial and typing with a cursor that pops back to the beginning is disorienting.

Demo

Details commented in Demo

If you want to render HTML, see the comments in the jQuery section

$(document).ready(function() {

  // textarea listens for input event...
  $('#edit').on('input', function() {

    // Collect values from inputs and textarea 
    var from = $('#from').val();
    var to = $('#to').val();
    var value = $(this).val();

    /* if the value of input#from is in the value of textarea...
    || indexOf() will return its index number...
    || so if indexOf() doesn't find it then it returns -1
    */
    if (value.indexOf(from) !== -1) {
    
      // Using RegExp Object for variable string
      var rgx = new RegExp(from, 'g');
      
      // New value of textarea#edit replaced 'from' with 'to'
      value = value.replace(rgx, to);
    }
    
    // The text of div#view is the new value of #edit
    /* Change .text to .html is you want to render HTML but it
    || be disorienting as what is actually typed is not seen.
    || A more feasible solution is to move #edit below view and
    || change #edit color to a visible color.
    */
    $('#view').text(value);
    
    // The value of #edit is the new value
    $(this).val(value);
  });

  // #view listens for keydown event...
  $('#view').on('keydown', function() {

    // Move focus to #edit
    $('#edit')[0].focus();
  });
});
html,
body {
  font: 400 16px/1.3 Consolas;
}


/* Wrap fieldset.main around both textarea and div 
|| div will be on top of textarea because they are absolute
*/

.main {
  position: relative;
  width: 90vw;
  border: 0
}

input {
  font: inherit;
  width: 20ch;
}


/* Only textarea#edit's cursor is visible */

#edit {
  border: 0;
  outline: 0;
  color: transparent;
  caret-color: red;
  position: absolute;
  width: 100%;
  font: inherit;
  left: 2px;
}


/* Editable div#view is over textarea#edit */


/* textarea#edit will actually get an input event */


/* User will see the div's text that comes from #edit*/


/* The value typed into #edit is transparent so user only sees
div#view's text and textarea#edit's cursor */

#view {
  position: absolute;
  pointer-events: none;
  z-index: 1;
  outline: 3px inset grey;
  width: 100%;
  min-height: 50px;
  left: 2px;
}
<!--UI enter target string and new string-->
<fieldset class='ui'>
  <legend>Convert String</legend>
  <label>From: </label><input id='from'>
  <label>To: </label><input id='to'>
</fieldset>

<!--Wrap editable div and textarea in an relative element-->
<fieldset class='main'>

  <!--Div and textaera are absolute see CSS-->
  <div id="view" contenteditable="true"></div>
  <textarea id='edit'></textarea>
</fieldset>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Community
  • 1
  • 1
zer00ne
  • 41,936
  • 6
  • 41
  • 68
1

You replace the string but not assign back.

Note that after replacement, the cousor would not stay at the position that you input something.

$(document).ready(function() {
  $('#contenteditableDiv').on('keyup', function(e) {
    var $this = $(this)
    var content = $this.html();
    var watcher = (content.match(/cat/gi) || []).length;
    
    var position = e

    if (watcher === 1) {
      var text = content.replace(/cat/g, "dog");
      $this.html(text);
    };
  });
});
div {
  border: 1px gray solid;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div contenteditable="true" id="contenteditableDiv"></div>
Hikarunomemory
  • 4,237
  • 2
  • 11
  • 21
0

What about this easier solution?

$("#contenteditableDiv").on('keyup', function() {
  $(this).val($(this).val().replace(/cat/g, "dog"));
});
#contenteditableDiv {
  width: 90%;
  height: 100px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="contenteditableDiv"></textarea>

Your problem was that $('#contenteditableDiv').html() returns the value of the element when it was rendered, i.e. empty.
To get the current value, you needed to use .val().

Check this question to know when to use .html(), .val(), .text().
When do I use .val() vs .innerHTML?

Takit Isy
  • 9,688
  • 3
  • 23
  • 47
  • @user3180105 You used "#contenteditableDiv" as an id in your code. I did the same. If there is another part of your code we should know about, please include it in your question. – Takit Isy Apr 28 '18 at 21:03
  • as mentioned in the title, I'm using a div not a textarea. Unfortunately, this doesn't work in this context. Added example to code for clarification – user3180105 Apr 29 '18 at 01:16
0

There is no downside if the element is set to contenteditable.

It works as any other element, the following snippet is NOT using jquery, this is pure ecma6 javascript replace().

Available in browsers since the 2015+. There is huge chances that your library doesn't handle well modern js, there is a probable conflict.

onload =(()=>{
  ctd.innerHTML = ctd.innerHTML.replace("world","<mark>world</mark>")
})
<div contenteditable="true" id="ctd">Hello world</div>

This said, regarding the specific contenteditable attribute, we can use --since not so long time-- the document.execCommand(), which come with the browser editing api.

See more capabilities on the great mdn demo. The final words, my point of view, jquery is out now.

Ecma6 is the way to go, we can do everything that jquery provide with less code, more speed and very close to complete browsers support.

Js is what he is now because jquery.

NVRM
  • 11,480
  • 1
  • 88
  • 87
0

After finding this thread and struggling with the topic myself for a while, I created an npm package that enables automatic transformation of given keywords in a contenteditable Element.

https://www.npmjs.com/package/replace-keywords

Justin H.
  • 103
  • 1
  • 9