1

I'm making a web application to test regular expressions. I have an input where I enter the regexp and a contenteditable pre element where I enter the text where the matches are found and highlighted.

Example: asuming the regexp is ab, if the user types abcab in the pre element, both regexp and text are sent to an api I implemented which returns

    <span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>

and this string is set as the innerHTML of the pre element

This operation is made each time the user edites the content of the pre element (keyup event to be exact). The problem I have (and I hope you can solve) is that each time the innterHTML is set, the caret is placed at the beginning, and I want it to be placed right after the last character input by de user. Any suggestions on how to know where the caret is placed and how to place it in a desired position? Thanks.

UPDATE For better understanding...A clear case: Regexp is ab and in the contenteditable element we have:

    <span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>

Then I type a c between the first a and the first b, so now we have:

    acbc<span style='background-color: lightgreen'>ab</span>

At this moment the caret has returned to the beginning of the contenteditable element, and it should be placed right after the c I typed. That's what I want to achieve, hope now it's more clear.

UPDATE2

function refreshInnerHtml() {
  document.getElementById('textInput').innerHTML = "<span style='background-color: lightgreen'>ab</span>c<span style='background-color: lightgreen'>ab</span>";
}
      <pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" ></pre>
  • 1
    Have you looked at https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div?rq=1 – koolkat Aug 30 '17 at 20:23
  • I did but my case seems to be more complex, check my update. –  Aug 30 '17 at 21:11

3 Answers3

0

<html>
 <head>
 <script> 
  function test(inp){
   document.getElementById(inp).value = document.getElementById(inp).value; 
  }

 </script>
 </head>
 <body> 
  <input id="search" type="text" value="mycurrtext" size="30" 
       onfocus="test(this.id);" onclick="test(this.id);" name="search"/>
 </body> 
</html>

I quickly made this and it places the cursor at the end of the string in the input box. The onclick is for when the user manually clicks on the input and the onfocus is for when the user tabs on the input.

<input id="search" type="text" value="mycurrtext" size="30" 
   onfocus="test(this.id);" onclick="test(this.id);" name="search"/>

function test(inp){
        document.getElementById(inp).value = document.getElementById(inp).value;  
    }
schylake
  • 434
  • 3
  • 14
0

Here to make selection easy, I've added another span tag with nothing in it, and given it a data-end attribute to make it easy to select.

I then simply create a range from this and use window.getSelection addRange to apply it.

Update: modified to place caret after the first ab

var e = document.querySelector('[data-end]');
var range = document.createRange();
range.setStart(e, 0);
range.setEnd(e, 0);

document.querySelector('[contenteditable]').focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
<div contenteditable="true">
  <span style='background-color: lightgreen'>ab</span><span data-end></span>c<span style='background-color: lightgreen'>ab</span>
</div>
Keith
  • 22,005
  • 2
  • 27
  • 44
  • I updated the post in order to make it more clear. Any solution or even an idea to make what I'm trying in another way will be welcome. –  Aug 30 '17 at 21:14
  • Not really 100% sure what your doing.. But if you place a `` at the point you want when you create the contenteditable it will place the caret there.. In the above example I placed it at the end, but I could have placed it anywhere. In fact I'll modify my snippet to place after `ab` – Keith Aug 30 '17 at 21:43
  • "The problem I have (and I hope you can solve) is that each time the innterHTML is set, the caret is placed at the beginning, and I want it to be placed right after the last character input by de user." That's the part I think you still don't understand. –  Aug 30 '17 at 22:47
  • In the above snippet, if you run it, the caret is not at the beginning of the line, it's were I told it to go. So if you do your innerHTML, place the `` at the point you want the caret to appear, and you run the code I've show here,. It should do what you want,.. Have you tried running the Snippet here I've created?. – Keith Aug 30 '17 at 22:50
  • The thing is that every time a key is pressed, the application calculates a new value for the innerHTML, and this value is asigned, so the caret appears again at the beginning. In the case of your solution, I would have to adivinate where to put the –  Aug 30 '17 at 23:11
  • `In the case of your solution, I would have to adivinate where to put the ` yes, and why is that an issue?. You of course need to work out were to place the , but that's a different problem. And I can't really help you here without seeing more of your code. – Keith Aug 30 '17 at 23:14
  • Please test the code of my UPDATE2 @Keith. Notice that it's imposible to guess where to put the beacause I can't predict what the content of the new innerHTML will be. –  Aug 30 '17 at 23:41
  • I've added a new answer that basically uses this technique to re-select the text. – Keith Aug 31 '17 at 01:06
  • That's what I was looking for. Very close to the solution I wanted. Thanks for your time! –  Aug 31 '17 at 12:38
0

With some help from these functions from here ->

Add element before/after text selection

I've created something I think your after. I basically place some temporary tags into the html where the current cursor is. I then render the new HTML, I then replace the tags with the span with a data-cpos attribute. Using this I then re-select the cursor.

var insertHtmlBeforeSelection, insertHtmlAfterSelection;

(function() {
    function createInserter(isBefore) {
        return function(html) {
            var sel, range, node;
            if (window.getSelection) {
                // IE9 and non-IE
                sel = window.getSelection();
                if (sel.getRangeAt && sel.rangeCount) {
                    range = window.getSelection().getRangeAt(0);
                    
                    range.collapse(isBefore);
        
                    // Range.createContextualFragment() would be useful here but is
                    // non-standard and not supported in all browsers (IE9, for one)
                    var el = document.createElement("div");
                    el.innerHTML = html;
                    var frag = document.createDocumentFragment(), node, lastNode;
                    while ( (node = el.firstChild) ) {
                        lastNode = frag.appendChild(node);
                    }
                    range.insertNode(frag);
                }
            } else if (document.selection && document.selection.createRange) {
                // IE < 9
                range = document.selection.createRange();
                range.collapse(isBefore);
                range.pasteHTML(html);
            }
        }
    }
    
    insertHtmlBeforeSelection = createInserter(true);
    insertHtmlAfterSelection = createInserter(false);
})();




function refreshInnerHtml() {
  var
    tag_start = '⇷',  //lets use some unicode chars unlikely to ever use..
    tag_end = '⇸',
    sel = document.getSelection(),
    input = document.getElementById('textInput');
   
  //remove old data-cpos
  [].forEach.call(
    input.querySelectorAll('[data-cpos]'),
    function(e) { e.remove() });
  
  //insert the tags at current cursor position
  insertHtmlBeforeSelection(tag_start);
  insertHtmlAfterSelection(tag_end);
  
  //now do our replace
  let html = input.innerText.replace(/(ab)/g, '<span style="background-color: lightgreen">$1</span>');
  input.innerHTML = html.replace(tag_start,'<span data-cpos>').replace(tag_end,'</span>');
  
  //now put cursor back 
  var e = input.querySelector('[data-cpos]');
  if (e) {
    var range = document.createRange();
    range.setStart(e, 0);
    range.setEnd(e, 0);
    sel.removeAllRanges();
    sel.addRange(range);  
  }
}

refreshInnerHtml();
Type some text below, with the letters 'ab' somewhere within it. <br>

<pre contenteditable onkeyup="refreshInnerHtml()" id="textInput" style="border: 1px solid black;" >It's about time.. above and beyond</pre>
Keith
  • 22,005
  • 2
  • 27
  • 44