如何在textarea中的光标位置放置下拉列表?

时间:2013-06-25 10:36:19

标签: css css3 css-float

如何将我的下拉列表放在textarea内的光标位置?我发现这个问题已经在这里多次被问到了,但我无法弄清楚正确的解决方案..

这是JSBIN

请帮助我提出建议

提前致谢

2 个答案:

答案 0 :(得分:3)

我知道这不是问题的确切答案(此解决方案不使用textarea,而是使用contentEditable div),但我认为没有任何方法可以使用该事件获取xy坐标,textarea上的属性或函数或Selection对象上的属性或函数。

我已经编写了一个示例on JSBin。请注意,我没有打扰在其他浏览器中测试兼容性,并且它不会将插入符号返回到您停止的位置。我无法弄清楚代码。我相信window.getSelection()将无法在IE中运行,而在IE8中则完全不同。您可能也想确保菜单不会直接从屏幕边缘显示。


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>

CSS

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

#dummy {
  display: inline-block;
}

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

Javascript&amp; 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;
  } );
} );

解释

此示例的工作原理是您可以在contentEditable中插入元素并将其偏移量检索到屏幕的顶部和左侧。当按下shift +键50时,事件处理程序将阻止写入@,而是插入@ +虚拟对象本身。然后我们从该对象检索偏移量并将下拉菜单移动到该偏移量。此外,我们将字符偏移量保存为菜单的自定义属性x-custom-offset,以便我们可以在该特定位置插入值。然后我们需要删除虚拟div,但是如果我们在虚拟之前用$('#dummy').remove()删除虚拟节点,则虚拟节点后面的文本节点将不会合并。这将删除最后一个textnode,如果我们要在其他地方放置一个@和/或将它放在错误的位置。因此,我们只需再次替换可编辑div的内容。最后,插入符号必须回到它的原始位置。我不知道如何正确地做到这一点。

第二个处理程序是在文本框中插入文本。代码应该是不言自明的。我们之前设置的x-custom-offset属性用于将文本插入文本框中的正确位置。 $('#dropdown').on( 'click', 'li a', function( e ) { ... } );会将点击事件附加到ul而不是li,以便在动态创建li时它会继续有效(但它会只有点击链接部分才会激活。

答案 1 :(得分:0)

您可以获取鼠标的位置,然后将下拉列表移动到此位置。 您只需要确保弹出内容的z-index高于您想要遮挡的元素,并且它的位置设置为绝对。

这是我写过一次的小测试样本。

<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
function toggleClass(element, newStr)
{
    index=element.className.indexOf(newStr);
    if ( index == -1)
        element.className += ' '+newStr;
    else
    {
        if (index != 0)
            newStr = ' '+newStr;
        element.className = element.className.replace(newStr, '');
    }
}
function forEachNode(nodeList, func)
{
    var i, n = nodeList.length;
    for (i=0; i<n; i++)
    {
        func(nodeList[i], i, nodeList);
    }
}

window.addEventListener('load', mInit, false);

function mInit()
{
}

function onShowBtn(e)
{
    var element = byId('popup');
    element.className = element.className.replace(' hidden', '');
    var str = '';//'border-radius: 32px; border: solid 5px;';
    e = e||event;
    str += "left: " + e.pageX + "px; top:"+e.pageY+"px;"
    element.setAttribute('style',str);
}
function onHideBtn()
{
    var element = byId('popup');
    if (element.className.indexOf(' hidden') == -1)
        element.className += ' hidden';
}

</script>
<style>
#controls
{
    display: inline-block;
    padding: 16px;
    border-radius: 6px;
    border: solid 1px #555;
    background: #AAA;
}
#popup
{
    border: solid 1px #777;
    border-radius: 4px;
    padding: 12px;
    background: #DDD;
    display: inline-block;
    z-index: 2;
    position: absolute;
}
#popup.hidden
{
    visibility: hidden;
}
</style>
</head>
<body>
    <div id='controls'>
        <input type='button' value='show' onclick='onShowBtn()'>
        <input type='button' value='hide' onclick='onHideBtn()'>
    </div>
    <br>
    <div id='popup'>
        <p>This is some assorted
            text</p>
            <hr>
        <ul>
            <li>item a</li>
            <li>item 2</li>
            <li>item iii</li>
        </ul>
    </div>
</body>
</html>