javascripthtmljquery

How to select a subset of characters from a content editable element?


I have a list of items where one of the attributes can be double clicked and then edited.

I have to use [contenteditable="true"] instead of using a standard input.

The format of the data which is editable may also include a unit of measure, e.g. "150mm".

I'm currently using .select() which selects everything, including the unit of measure.

See example here https://codepen.io/c01gat3/pen/GgRbwoj

I want to select just the numeric values, or the length of the string less the characters at the end.

Happy for plain javascript or jQuery answers for this project.

$('body').on('dblclick', '.value', function(){
  $(this).attr('contenteditable', true);
  //*** this is where I need some help   
  $(this).focus().select();
  //How to only select the numbers, not include the "mm"?
  //or how to select (length - 2) characters?
});
$(document).mouseup(function(e){
  var element = $('body').find('.value[contenteditable="true"]');
  if (!element.is(e.target) && element.has(e.target).length === 0){
    $('.value').attr('contenteditable', false);
  }
});
$(document).keypress(function(e) {
  if($('.value[contenteditable="true"]')){
    if(e.which == 13){ //enter
      $('.value').attr('contenteditable', false);
    }
  }
});
.list{
  display: flex;
  flex-direction: column;
  gap: 10px;
  .item{
    display: flex;
    .id{
      width: 20px;
      font-weight: 600;
    }
    &:has(.value[contenteditable="true"]){
      .id{
        color: red;
      }
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="list">
  <div class="item">
    <span class="id">1</span>
    <span class="value">100mm</span>
  </div>
  <div class="item">
    <span class="id">2</span>
    <span class="value">2,500mm</span>
  </div>
  <div class="item">
    <span class="id">3</span>
    <span class="value">340mm</span>
  </div>
</div>


Solution

  • One solution is to: Get hold of the text node inside the spans you want to edit, then find the position of the last digit, and tell the browser to select from the beginning of the text to that digit.

    $('body').on('dblclick', '.value', function(){
      $(this).attr('contenteditable', true);
      //*** this is where I need some help   
      //$(this).focus().select();
      
      //======= BEGIN SOLUTION =======
      var str = this.textContent;
      // use a regex to select all chars up to and including the last number.
      const match = str.match(/^.*\d/);
      // it returns an array, 
      //     if the string had digit(s) in it its returned all text up to the position of the last digit
      //     if its empty its null. You should decide how to handle this. (e.g dont select anything: if(!match) return; )
      console.debug(match[0]);        // this is what you matched
      console.debug(match[0].length); // ...and how long it is
      
      // get a reference to the node that is the text we want
      const textNode = this.firstChild; // This is the node that has the text inside the span.
      console.debug(textNode.nodeValue);
      
      const range = document.createRange(); // A Range is used to represent a fragment of a document. Including parts of text nodes
    
      range.setStart(textNode, 0);             // Start index
      range.setEnd(textNode, match[0].length); // End index
      
      const selection = window.getSelection();
      selection.removeAllRanges();  // clear out any existing selections the browser has...
      selection.addRange(range);    // ...and tell the browser to select the one we want.
      //======= END SOLUTION =======
      
      //How to only select the numbers, not include the "mm"?
      //or how to select (length - 2) characters?
    });
    $(document).mouseup(function(e){
      var element = $('body').find('.value[contenteditable="true"]');
      if (!element.is(e.target) && element.has(e.target).length === 0){
        $('.value').attr('contenteditable', false);
      }
    });
    $(document).keypress(function(e) {
      if($('.value[contenteditable="true"]')){
        if(e.which == 13){ //enter
          $('.value').attr('contenteditable', false);
        }
      }
    });
    .list{
      display: flex;
      flex-direction: column;
      gap: 10px;
      .item{
        display: flex;
        .id{
          width: 20px;
          font-weight: 600;
        }
        &:has(.value[contenteditable="true"]){
          .id{
            color: red;
          }
        }
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <div class="list">
      <div class="item">
        <span class="id">1</span>
        <span class="value">100mm</span>
      </div>
      <div class="item">
        <span class="id">2</span>
        <span class="value">2,500mm</span>
      </div>
      <div class="item">
        <span class="id">3</span>
        <span class="value">340mm</span>
      </div>
    </div>