Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i position a dropdown at cursor position inside a textarea?

Tags:

css

css-float

How can i position my dropdown at cursor position inside a textarea? I have found this question was already asked here many times but i cant able figure out the correct solution ..

this is the JSBIN

please help me with your suggestions

Thanks in advance

like image 466
selvagsz Avatar asked Jun 25 '13 10:06

selvagsz


People also ask

How do I change the cursor position in textarea?

To set the cursor at the end of a textarea: Use the setSelectionRange() method to set the current text selection position to the end of the textarea. Call the focus() method on the textarea element. The focus method will move the cursor to the end of the element's value.

How do I set a dropdown position?

Example Explained HTML) Use any element to open the dropdown content, e.g. a <span>, or a <button> element. Use a container element (like <div>) to create the dropdown content and add whatever you want inside of it. Wrap a <div> element around the elements to position the dropdown content correctly with CSS.

How do I find the cursor position in text area?

First, get the current position of cursor with the help of property named as selectionStart on textarea/inputbox. To insert the text at the given position we will use slice function to break the string into two parts and then we will append both parts to the text(text_to_insert) in front and end of the text.


1 Answers

I know it isn't an exact answer on the question (this solution doesn't use a textarea, but a contentEditable div), but I don't think there is any way of getting x-y-coordinates using either the event, an attribute or function on the textarea or an attribute or function on the Selection object.

I have meshed up an example on JSBin. Please note that I haven't bothered testing for compatibility in other browsers and that it won't return the caret to where you left off. I can't figure out the code for that. I believe window.getSelection() will not work in IE, and in IE8- it would be completely different. You probably want to make sure too, that the menu will not be displayed right from the edge of the screen.


The HTML

<div id="target" contentEditable="true">Type @ to see the dropdown.... </div>
<div class="dropdown">
  <ul id="dropdown" class="dropdown-menu hide" role="menu" aria-labelledby="dropdownMenu">
    <li><a>One</a> </li>
    <li><a>Two</a></li>
    <li><a>Three</a></li>
    <li><a>Four</a> </li>

  </ul>
</div>

The CSS

#target {
  height: 100px;
  border: 1px solid black;
  margin-top: 50px;
}

#dummy {
  display: inline-block;
}

.dropdown {
  position: absolute;
  top: 0;
  left: 0;
}

The Javascript & JQuery

$("#target").keydown( function(e) {

  if(e.which === 50 && e.shiftKey === true ) {
    //Prevent this event from actually typing the @
    e.preventDefault();

    //console.log( window.getSelection() );
    var sel = window.getSelection();

    var offset = sel.baseOffset;
    var node = sel.focusNode.parentNode;

    //Get the text before and after the caret
    var firsttext = node.innerHTML.substr(0,sel.baseOffset);
    var nexttext = (sel.baseOffset != sel.focusNode.length ) ? node.innerHTML.substr( sel.baseOffset, sel.focusNode.length) : "";

    //Add in @ + dummy, because @ is not in there yet on keydown
    node.innerHTML = firsttext + '@<div id="dummy"></div>' + nexttext;

    //Transfer all relevant data to the dropdown menu

    $('.dropdown').css('left', $('#dummy')[0].offsetLeft).css('top', $('#dummy')[0].offsetTop).prop('x-custom-offset', offset + 1);

    //Delete the dummy to keep it clean
    //This will split the contents into two text nodes, which we don't want
    //$('#dummy').remove(); 
    node.innerHTML = firsttext + '@' + nexttext;

    //Put the caret back in place where we left off
    //...I can't seem to figure out how to correctly set the range correctly...

    $('#dropdown').removeClass('hide').addClass('show');    
  } else {
    $('#dropdown').removeClass('show').addClass('hide');
    $('.dropdown').removeProp('x-custom-offset');
  }
});

$('#dropdown').on( 'click', 'li a', function( e ) {
  e.preventDefault();

  $('#target').html( function( i, oldtext ) {
    var firsttext = oldtext.substr( 0, $('.dropdown').prop('x-custom-offset') );
    var nexttext = oldtext.substr( $('.dropdown').prop('x-custom-offset'), oldtext.length );

    console.log( e );

    var inserttext = e.target.innerText;

    //Cleanup
    $('#dropdown').removeClass('show').addClass('hide');

    return firsttext + inserttext + nexttext;
  } );
} );

The explanation

This example works based on that you can insert an element in a contentEditable and retrieve it's offset to the top and the left of the screen. When shift + key 50 is pressed, the event handler will prevent the @ from being written and instead inserts the @ + dummy object itself. Then we retrieve the offset from this object and move the dropdown menu to that offset. Furthermore, we save the character-offset as a custom property x-custom-offset of the menu, so that we can insert a value at that specific location. We then need to remove the dummy div, but if we would remove the dummy with $('#dummy').remove() the text node before the dummy and the text node behind the dummy will not merge. This will delete the last textnode if we were to put an other @ somewhere and/or place it in the wrong location. Therefore, we simply replace the contents of the editable div again. Last, the caret must be set back to it's original position. I cannot figure out how to do this properly though.

The second handler is to insert text into the textbox. The code should be self-explanatory. The x-custom-offset property we set earlier is used here to insert the text into the correct place in the textbox. $('#dropdown').on( 'click', 'li a', function( e ) { ... } ); will attach the click event to the ul instead of the li's, so that it will keep working if you dynamically create the li's (but it will only fire if you click the link part).

like image 193
Sumurai8 Avatar answered Sep 19 '22 21:09

Sumurai8