【问题标题】:Tag-like autocompletion and caret/cursor movement in contenteditable elementscontenteditable 元素中类似标签的自动完成和插入符号/光标移动
【发布时间】:2010-05-09 15:34:58
【问题描述】:

我正在开发一个 jQuery 插件,它可以让你做@username 样式标签,就像 Facebook 在他们的状态更新输入框中所做的那样。

我的问题是,即使经过数小时的研究和试验,简单地移动插入符号似乎也很困难。我已经设法在<a> 标签中注入了某人的名字,但是在它看起来像火箭科学之后放置插入符号,特别是如果它应该在所有浏览器中工作。

我什至还没有考虑用标签替换输入的 @username 文本,而不是像我现在所做的那样注入它......哈哈

在 Stack Overflow 上有很多关于使用 contenteditable 的问题,我想我已经阅读了所有这些问题,但它们并没有真正涵盖我需要的内容。因此,任何人都可以提供更多信息会很棒:)

【问题讨论】:

标签: javascript contenteditable caret


【解决方案1】:

您可以使用my Rangy library,它会成功尝试规范化浏览器范围和选择实现。如果您按照您的说法成功地插入了<a>,并且您已经将它放在一个名为aElement 的变量中,您可以执行以下操作:

var range = rangy.createRange();
range.setStartAfter(aElement);
range.collapse(true);
var sel = rangy.getSelection();
sel.removeAllRanges();
sel.addRange(range);

【讨论】:

  • 你的问题让我很感兴趣,我一直在编写一些通用代码来做这种事情。我会尽快回复。
【解决方案2】:

我对此很感兴趣,所以我写了一个完整解决方案的起点。以下使用我的Rangy library 及其selection save/restore module 来保存和恢复选择并规范跨浏览器问题。它用一个链接元素包围所有匹配的文本(在这种情况下为@whatever),并将选择定位在之前的位置。这会在一秒钟内没有键盘活动后触发。它应该是可重复使用的。

function createLink(matchedTextNode) {
    var el = document.createElement("a");
    el.style.backgroundColor = "yellow";
    el.style.padding = "2px";
    el.contentEditable = false;
    var matchedName = matchedTextNode.data.slice(1); // Remove the leading @
    el.href = "http://www.example.com/?name=" + matchedName;
    matchedTextNode.data = matchedName;
    el.appendChild(matchedTextNode);
    return el;
}

function shouldLinkifyContents(el) {
    return el.tagName != "A";
}

function surroundInElement(el, regex, surrounderCreateFunc, shouldSurroundFunc) {
    var child = el.lastChild;
    while (child) {
        if (child.nodeType == 1 && shouldSurroundFunc(el)) {
            surroundInElement(child, regex, surrounderCreateFunc, shouldSurroundFunc);
        } else if (child.nodeType == 3) {
            surroundMatchingText(child, regex, surrounderCreateFunc);
        }
        child = child.previousSibling;
    }
}

function surroundMatchingText(textNode, regex, surrounderCreateFunc) {
    var parent = textNode.parentNode;
    var result, surroundingNode, matchedTextNode, matchLength, matchedText;
    while ( textNode && (result = regex.exec(textNode.data)) ) {
        matchedTextNode = textNode.splitText(result.index);
        matchedText = result[0];
        matchLength = matchedText.length;
        textNode = (matchedTextNode.length > matchLength) ?
            matchedTextNode.splitText(matchLength) : null;
        surroundingNode = surrounderCreateFunc(matchedTextNode.cloneNode(true));
        parent.insertBefore(surroundingNode, matchedTextNode);
        parent.removeChild(matchedTextNode);
    }
}

function updateLinks() {
    var el = document.getElementById("editable");
    var savedSelection = rangy.saveSelection();
    surroundInElement(el, /@\w+/, createLink, shouldLinkifyContents);
    rangy.restoreSelection(savedSelection);
}

var keyTimer = null, keyDelay = 1000;

function keyUpLinkifyHandler() {
    if (keyTimer) {
        window.clearTimeout(keyTimer);
    }
    keyTimer = window.setTimeout(function() {
        updateLinks();
        keyTimer = null;
    }, keyDelay);
}

HTML:

<p contenteditable="true" id="editable" onkeyup="keyUpLinkifyHandler()">
    Some editable content for @someone or other
</p>

【讨论】:

  • 太好了。我正在尝试使用 rangy 扩展它以在您键入时突出显示 URL。但是,似乎存在许多问题 - 一旦检测到链接就无法编辑,并且在 A 标签中断内将 contentEditable 设置为 true 也会破坏事情。有没有更好的方法?
  • @MattRoberts:我认为没有根本上更好的方法,只是更好、更仔细的实施。这确实是一个起点。
  • 谢谢蒂姆。我想知道您的新亮点模块是否适合此类任务?
【解决方案3】:

正如您所说,您已经可以在插入符号处插入标签,我将从那里开始。要做的第一件事是在插入标签时给它一个 id。然后你应该有这样的东西:

&lt;div contenteditable='true' id='status'&gt;I went shopping with &lt;a href='#' id='atagid'&gt;Jane&lt;/a&gt;&lt;/div&gt;

这是一个应该将光标放在标签之后的函数。

function setCursorAfterA()
{
    var atag = document.getElementById("atagid");
    var parentdiv = document.getElementById("status");
    var range,selection;
    if(window.getSelection) //FF,Chrome,Opera,Safari,IE9+
    {
        parentdiv.appendChild(document.createTextNode(""));//FF wont allow cursor to be placed directly between <a> tag and the end of the div, so a space is added at the end (this can be trimmed later)
        range = document.createRange();//create range object (like an invisible selection)
        range.setEndAfter(atag);//set end of range selection to just after the <a> tag
        range.setStartAfter(atag);//set start of range selection to just after the <a> tag
        selection = window.getSelection();//get selection object (list of current selections/ranges)
        selection.removeAllRanges();//remove any current selections (FF can have more than one)
        parentdiv.focus();//Focuses contenteditable div (necessary for opera)
        selection.addRange(range);//add our range object to the selection list (make our range visible)
    }
    else if(document.selection)//IE 8 and lower
    { 
        range = document.body.createRange();//create a "Text Range" object (like an invisible selection)
        range.moveToElementText(atag);//select the contents of the a tag (i.e. "Jane")
        range.collapse(false);//collapse selection to end of range (between "e" and "</a>").
        while(range.parentElement() == atag)//while ranges cursor is still inside <a> tag
        {
             range.move("character",1);//move cursor 1 character to the right
        }
        range.move("character",-1);//move cursor 1 character to the left
        range.select()//move the actual cursor to the position of the ranges cursor
    }
    /*OPTIONAL: 
    atag.id = ""; //remove id from a tag
    */
}

编辑: 经过测试和修复的脚本。它绝对适用于 IE6、chrome 8、firefox 4 和 opera 11。手头上没有其他浏览器可供测试,但它不使用最近更改的任何功能,因此它应该适用于任何支持 contenteditable 的东西。

这个按钮便于测试: &lt;input type='button' onclick='setCursorAfterA()' value='Place Cursor After &amp;lt;a/&amp;gt; tag' &gt;

尼哥

【讨论】:

    猜你喜欢
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多