locked
Get/set caret/cursor position in a contenteditable div RRS feed

  • Question

  • Hi,

    I'm trying to get/set the caret/cursor position in an contenteditable div.

    How can I do this in an HTML/JS Metro styled app?

    Thank you

    Tuesday, May 15, 2012 9:31 AM

Answers

All replies

  • Hi DK,

    It is not any different than a standard HTML/JavaScript method.

    You can use textRange for example: http://msdn.microsoft.com/en-us/library/ms533042(v=VS.85).aspx

    -Jeff


    Jeff Sanders (MSFT)

    Tuesday, May 15, 2012 1:54 PM
    Moderator
  • Thank you for your reply,

    I cant see any mention on that page or any of the associated pages any reference to getting and setting the caret/cursor position.

    Is it possible to point me to a page that shows me how I can get and set the caret/cursor position within a contenteditable div?

    Thank you

    Tuesday, May 15, 2012 11:53 PM
  • Hi there!...

    Ummm, you might find this information helpful, perhaps maybe, i dont know but please try and see if it should work for you, as i managed to make this work for me.

    this is just something i punch together by bits and pieces to make work :) so please enjoy :)


    • Firstly i found that i need the <!doctype html> tag at the beginning of the page, this is a massive must note!.


    • and the rest was pretty much these scripts (please excuse the tab spacing format of the IDE)...
      Built in Website Builder 4.0, sorry MSDN, as ticked off as i am with some of the design interface in this GUI i am yet to try more of your freeware WYSIWYG IDE's that have a element inspector with all the default values available, like under styles:float there is no drop-list with None, Left or Right but from the freeware products that i have looked at, i must say that i was extremely impressed!, can not give a bug report or interface friendliness help but yeah, people take note of that too!, Microsoft have a wide range of freeware website building applications for you to checkout like Expression and WebMatrix :)


      <script type="text/javascript">  

      function getCaretPosition(element)
       {
        var ie = (typeof document.selection != "undefined" && document.selection.type != "Control") && true;
        var w3 = (typeof window.getSelection != "undefined") && true;
        var caretOffset = 0;
        if (w3)
         {
          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 (ie)
         {
          var textRange = document.selection.createRange();
          var preCaretTextRange = document.body.createTextRange();
          preCaretTextRange.expand(element);
          preCaretTextRange.setEndPoint("EndToEnd", textRange);
          caretOffset = preCaretTextRange.text.length;
         }
         return caretOffset;
       }

      function setCaretPos(el, sPos)
       {
        /*range = document.createRange();                    
        range.setStart(el.firstChild, sPos);
        range.setEnd  (el.firstChild, sPos);*/
        var charIndex = 0, range = document.createRange();
              range.setStart(el, 0);
              range.collapse(true);
              var nodeStack = [el], node, foundStart = false, stop = false;

              while (!stop && (node = nodeStack.pop())) {
                  if (node.nodeType == 3) {
                      var nextCharIndex = charIndex + node.length;
                      if (!foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
                          range.setStart(node, sPos - charIndex);
                          foundStart = true;
                      }
                      if (foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
                          range.setEnd(node, sPos - charIndex);
                          stop = true;
                      }
                      charIndex = nextCharIndex;
                  } else {
                      var i = node.childNodes.length;
                      while (i--) {
                          nodeStack.push(node.childNodes[i]);
                      }
                  }
              }
        selection = window.getSelection();                 
        selection.removeAllRanges();                       
        selection.addRange(range);
       }    
      </script>



    And the entire code example in my testpages.html file for your pleasure :)

    • testpages.html

      <!doctype html><html>

      <head>
      <script type="text/javascript">
      // JS function to convert BBCode and HTML code - http;//coursesweb.net/javascript/
      var BBCodeHTML = function() {
        var me = this;            // stores the object instance
        var token_match = /{[A-Z_]+[0-9]*}/ig;

        // regular expressions for the different bbcode tokens
        var tokens = {
          'URL' : '((?:(?:[a-z][a-z\\d+\\-.]*:\\/{2}(?:(?:[a-z0-9\\-._~\\!$&\'*+,;=:@|]+|%[\\dA-F]{2})+|[0-9.]+|\\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\\])(?::\\d*)?(?:\\/(?:[a-z0-9\\-._~\\!$&\'*+,;=:@|]+|%[\\dA-F]{2})*)*(?:\\?(?:[a-z0-9\\-._~\\!$&\'*+,;=:@\\/?|]+|%[\\dA-F]{2})*)?(?:#(?:[a-z0-9\\-._~\\!$&\'*+,;=:@\\/?|]+|%[\\dA-F]{2})*)?)|(?:www\\.(?:[a-z0-9\\-._~\\!$&\'*+,;=:@|]+|%[\\dA-F]{2})+(?::\\d*)?(?:\\/(?:[a-z0-9\\-._~\\!$&\'*+,;=:@|]+|%[\\dA-F]{2})*)*(?:\\?(?:[a-z0-9\\-._~\\!$&\'*+,;=:@\\/?|]+|%[\\dA-F]{2})*)?(?:#(?:[a-z0-9\\-._~\\!$&\'*+,;=:@\\/?|]+|%[\\dA-F]{2})*)?)))',
          'LINK' : '([a-z0-9\-\./]+[^"\' ]*)',
          'EMAIL' : '((?:[\\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+@(?:(?:(?:(?:(?:[a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(?:\\d{1,3}\.){3}\\d{1,3}(?:\:\\d{1,5})?))',
          'TEXT' : '(.*?)',
          'SIMPLETEXT' : '([a-zA-Z0-9-+.,_ ]+)',
          'INTTEXT' : '([a-zA-Z0-9-+,_. ]+)',
          'IDENTIFIER' : '([a-zA-Z0-9-_]+)',
          'COLOR' : '([a-z]+|#[0-9abcdef]+)',
          'NUMBER'  : '([0-9]+)'
        };

        var bbcode_matches = [];        // matches for bbcode to html

        var html_tpls = [];             // html templates for html to bbcode

        var html_matches = [];          // matches for html to bbcode

        var bbcode_tpls = [];           // bbcode templates for bbcode to html

        /**
         * Turns a bbcode into a regular rexpression by changing the tokens into
         * their regex form
         */
        var _getRegEx = function(str) {
          var matches = str.match(token_match);
          var nrmatches = matches.length;
          var i = 0;
          var replacement = '';

          if (nrmatches <= 0) {
            return new RegExp(preg_quote(str), 'g');        // no tokens so return the escaped string
          }

          for(; i < nrmatches; i += 1) {
            // Remove {, } and numbers from the token so it can match the
            // keys in tokens
            var token = matches[i].replace(/[{}0-9]/g, '');

            if (tokens[token]) {
              // Escape everything before the token
              replacement += preg_quote(str.substr(0, str.indexOf(matches[i]))) + tokens[token];

              // Remove everything before the end of the token so it can be used
              // with the next token. Doing this so that parts can be escaped
              str = str.substr(str.indexOf(matches[i]) + matches[i].length);
            }
          }

          replacement += preg_quote(str);      // add whatever is left to the string

          return new RegExp(replacement, 'gi');
        };

        /**
         * Turns a bbcode template into the replacement form used in regular expressions
         * by turning the tokens in $1, $2, etc.
         */
        var _getTpls = function(str) {
          var matches = str.match(token_match);
          var nrmatches = matches.length;
          var i = 0;
          var replacement = '';
          var positions = {};
          var next_position = 0;

          if (nrmatches <= 0) {
            return str;       // no tokens so return the string
          }

          for(; i < nrmatches; i += 1) {
            // Remove {, } and numbers from the token so it can match the
            // keys in tokens
            var token = matches[i].replace(/[{}0-9]/g, '');
            var position;

            // figure out what $# to use ($1, $2)
            if (positions[matches[i]]) {
              position = positions[matches[i]];         // if the token already has a position then use that
            } else {
              // token doesn't have a position so increment the next position
              // and record this token's position
              next_position += 1;
              position = next_position;
              positions[matches[i]] = position;
            }

            if (tokens[token]) {
              replacement += str.substr(0, str.indexOf(matches[i])) + '$' + position;
              str = str.substr(str.indexOf(matches[i]) + matches[i].length);
            }
          }

          replacement += str;

          return replacement;
        };

        /**
         * Adds a bbcode to the list
         */
        me.addBBCode = function(bbcode_match, bbcode_tpl) {
          // add the regular expressions and templates for bbcode to html
          bbcode_matches.push(_getRegEx(bbcode_match));
          html_tpls.push(_getTpls(bbcode_tpl));

          // add the regular expressions and templates for html to bbcode
          html_matches.push(_getRegEx(bbcode_tpl));
          bbcode_tpls.push(_getTpls(bbcode_match));
        };

        /**
         * Turns all of the added bbcodes into html
         */
        me.bbcodeToHtml = function(str) {
          var nrbbcmatches = bbcode_matches.length;
          var i = 0;

          for(; i < nrbbcmatches; i += 1) {
            str = str.replace(bbcode_matches[i], html_tpls[i]);
          }

          return str;
        };

        /**
         * Turns html into bbcode
         */
        me.htmlToBBCode = function(str) {
          var nrhtmlmatches = html_matches.length;
          var i = 0;

          for(; i < nrhtmlmatches; i += 1) {
            str = str.replace(html_matches[i], bbcode_tpls[i]);
          }

          return str;
        }

        /**
         * Quote regular expression characters plus an optional character
         * taken from phpjs.org
         */
        function preg_quote (str, delimiter) {
          return (str + '').replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + (delimiter || '') + '-]', 'g'), '\\$&');
        }

        // adds BBCodes and their HTML
        me.addBBCode('[b]{TEXT}[/b]', '<strong>{TEXT}</strong>');
        me.addBBCode('[i]{TEXT}[/i]', '<em>{TEXT}</em>');
        me.addBBCode('[u]{TEXT}[/u]', '<span style="text-decoration:underline;">{TEXT}</span>');
        me.addBBCode('[s]{TEXT}[/s]', '<span style="text-decoration:line-through;">{TEXT}</span>');
        me.addBBCode('[url={URL}]{TEXT}[/url]', '<a href="{URL}" title="link" target="_blank">{TEXT}</a>');
        me.addBBCode('[url]{URL}[/url]', '<a href="{URL}" title="link" target="_blank">{URL}</a>');
        me.addBBCode('[url={LINK}]{TEXT}[/url]', '<a href="{LINK}" title="link" target="_blank">{TEXT}</a>');
        me.addBBCode('[url]{LINK}[/url]', '<a href="{LINK}" title="link" target="_blank">{LINK}</a>');
        me.addBBCode('[font={COLOR}]{TEXT}[/font]', '<font color="{COLOR}">{TEXT}</font>');
        me.addBBCode('[img={URL} width={NUMBER1} height={NUMBER2}]{TEXT}[/img]', '<img src="{URL}" width="{NUMBER1}" height="{NUMBER2}" alt="{TEXT}" />');
        me.addBBCode('[img]{URL}[/img]', '<img src="{URL}" alt="{URL}" />');
        me.addBBCode('[img={LINK} width={NUMBER1} height={NUMBER2}]{TEXT}[/img]', '<img src="{LINK}" width="{NUMBER1}" height="{NUMBER2}" alt="{TEXT}" />');
        me.addBBCode('[img]{LINK}[/img]', '<img src="{LINK}" alt="{LINK}" />');
        me.addBBCode('[color=COLOR]{TEXT}[/color]', '<span style="{COLOR}">{TEXT}</span>');
        me.addBBCode('[highlight={COLOR}]{TEXT}[/highlight]', '<span style="background-color:{COLOR}">{TEXT}</span>');
        me.addBBCode('[quote="{TEXT1}"]{TEXT2}[/quote]', '<div class="quote"><cite>{TEXT1}</cite><p>{TEXT2}</p></div>');
        me.addBBCode('[quote]{TEXT}[/quote]', '<cite>{TEXT}</cite>');
        me.addBBCode('[blockquote]{TEXT}[/blockquote]', '<blockquote>{TEXT}</blockquote>');
      };
      var bbcodeParser = new BBCodeHTML();       // creates object instance of BBCodeHTML()
      </script>
      </head>
      <body>    
      <style type="text/css">.sb{font-weight:800;}.su{text-decoration:underline;}.si{font-style:italic;}</style>
      <div contenteditable="true" id="parea" class="chat" onkeyup="var pArea=document.getElementById('parea'); var chatbox=document.getElementById('adchat'); var chatmsg=pArea.innerHTML; chatbox.value=chatmsg; var formatchat=bbcodeParser.bbcodeToHtml(chatmsg); var tags = /(<([^>]+)>)/ig; var striptags=formatchat.replace(tags, ''); var x_msg=document.getElementById('msgchat'); var x_format=document.getElementById('msgformat'); x_msg.innerHTML=chatmsg.trim().length; x_format.innerHTML=(striptags.trim().length+0); var x_cPos=document.getElementById('msgcPos'); var cPos=0+getCaretPosition(pArea); x_cPos.innerHTML=cPos; pArea.innerHTML=formatchat; var math=(chatmsg.replace(tags, '').length-cPos) -(striptags.length-cPos); if(math>0) {cPos=cPos-math;}; setCaretPos(pArea, cPos); document.getElementById('msgmath').innerHTML=math;">
      This is a paragraph. It is editable. Try to change this text.
      </div>
      <textarea name="adchat" id="adchat" cols="30" rows="5" style="overflow: hidden"></textarea>
      <br>
      <br>
      <button id="button" onclick="var x_cPos=document.getElementById('msgcPos'); var pArea=document.getElementById('parea'); var cPos=x_cPos.innerHTML; setCaretPos(pArea, cPos); pArea.focus();">focus</button><br>
      cPos=<div id="msgcPos" >0</div><br>
      Chatmsg=<div id="msgchat">0</div><br>
      formatmsg=<div id="msgformat">0</div><br>
      math=<div id="msgmath">0</div><br>
      <script type="text/javascript">  
      function getCaretPosition(element)
       {
        var ie = (typeof document.selection != "undefined" && document.selection.type != "Control") && true;
        var w3 = (typeof window.getSelection != "undefined") && true;
        var caretOffset = 0;
        if (w3)
         {
          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 (ie)
         {
          var textRange = document.selection.createRange();
          var preCaretTextRange = document.body.createTextRange();
          preCaretTextRange.expand(element);
          preCaretTextRange.setEndPoint("EndToEnd", textRange);
          caretOffset = preCaretTextRange.text.length;
         }
         return caretOffset;
       }

      function setCaretPos(el, sPos)
       {
        /*range = document.createRange();                    
        range.setStart(el.firstChild, sPos);
        range.setEnd  (el.firstChild, sPos);*/
        var charIndex = 0, range = document.createRange();
              range.setStart(el, 0);
              range.collapse(true);
              var nodeStack = [el], node, foundStart = false, stop = false;

              while (!stop && (node = nodeStack.pop())) {
                  if (node.nodeType == 3) {
                      var nextCharIndex = charIndex + node.length;
                      if (!foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
                          range.setStart(node, sPos - charIndex);
                          foundStart = true;
                      }
                      if (foundStart && sPos >= charIndex && sPos <= nextCharIndex) {
                          range.setEnd(node, sPos - charIndex);
                          stop = true;
                      }
                      charIndex = nextCharIndex;
                  } else {
                      var i = node.childNodes.length;
                      while (i--) {
                          nodeStack.push(node.childNodes[i]);
                      }
                  }
              }
        selection = window.getSelection();                 
        selection.removeAllRanges();                       
        selection.addRange(range);
       }    
      </script>
      </body>
      </html>
    Tuesday, February 18, 2014 9:24 AM