制作像facebook这样的自动完成提及系统的问题

时间:2011-09-15 20:05:50

标签: jquery facebook html autocomplete contenteditable

我正在制作一个类似facebook中的提及系统,现在我用textarea实现了它,但现在我想让它与一个可信的div一起工作,这样我就可以格式化文本。

现在,当你按下“@”和一个字母在数组中搜索包含该字母的结果时,使用JQuery-UI自动完成功能,当你选择结果时它会在div中显示结果,但现在我要强调像facebook这样的提及,所以我提出了一个:

<b style="background: #somecolor"></b>

所以提到的看起来像这样:

<b style="background: #somecolor">Mention</b>

到目前为止一直很好,但是当我想继续写作时,我写的所有新文本都在<b></b>里面,如下所示:

<b style="background: #somecolor">Mention all my new text</b>

而不是<b style="background: #somecolor">Mention</b> all my new text

所有新文本都被突出显示,这是一张图片:

http://dev.frinki.com/question/1.png

所以这是使用

的javascript代码
$("#Conversacion").bind("keydown", function(event) {
            if (event.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
                event.preventDefault();
            }           

        }).autocomplete({
            minLength: 0,
            source: function(request, response) {
                var term = request.term,
                    results = [];
                    console.log(term);
                if (term.indexOf("@") >= 0) {
                    term = extractLast(request.term);
                    if (term.length > 0) {
                        results = $.ui.autocomplete.filter(availableTagsFollowing, term);
                    } else {
                        //results = ['Start typing...'];
                    }
                }
                response(results);
            },
            focus: function() {
                // prevent value inserted on focus
                return false;
            },
            select: function(event, ui) {
                var terms = split(this.innerHTML);
                // remove the current input
                terms.pop();
                // add the selected item
                terms.push('<b style="background:#E0FFC5">' + ui.item.label + '</b>');
                this.innerHTML = terms.join("");

                $('#mentionsHidden').val($('#mentionsHidden').val() + '@['+ui.item.value+':'+ui.item.label+']');
                mentionsString = $('#mentionsHidden').val();
                return false;
            }

        });

这是HTML代码

<div name="Descripcion" id="Conversacion" class="Conversacion" contenteditable="true"></div>

这是Chrome's Inspector的图片:

http://dev.frinki.com/question/2.png

我在facebook上查看它是如何完成的,这是在facebook上的chrome检查员的照片:

3.png(其他两张图片的域名和文件夹相同,对不起,这是一个新用户,我不能发布超过2个链接)

当您选择要提及的人时,Facebook似乎会在其使用的范围中创建一个新行,以便将文本与<b></b>分开。

我希望有人可以帮助我。

顺便说一下,我很害怕我的英语,我来自南美洲。

2 个答案:

答案 0 :(得分:1)

我只是在解释/总结您的想法:您的div是可编辑的,用户可以输入它。在某些情况下,您将显示用户可以选择的列表。选择完成后,div的内容将被修改为包含某个包装元素中的选定值。之后用户输入但是字符被插入到包装器中而不是在包装器之后,对吧?

不需要的行为的原因是焦点位置。从技术上讲,有一系列选定的内容会被用户输入的字符所取代。此范围通常是不可见的,因为它跨越0个字符。当使用鼠标选择文本时,它会覆盖一些内容(可能还有多个元素)。

现在让我们假设以下HTML:<b>One</b><b>Two</>。可能是空范围介于b标签之间。当输入下一个字符 A 时,需要将其插入文档中 - 但即使已知范围/插入符/光标位置,也有3个可能的目的地:

  • 在第一个b<b>OneA</b><b>Two</b>
  • 的末尾
  • 在代码之间:<b>One</b>A<b>Two</b>
  • 在第二个b的开头:<b>One</b><b>ATwo</b>

您的浏览器会选择第一种方法:粗略地说,要继续添加最近添加的元素。

现在,有a pretty complex range system指定用于放置新内容的方法。有关示例,请参阅Introduction to Rangea question on setting the range

解决问题的一般想法是在新插入的内容之后创建一些空内容。因此最终得到<b style="background: #somecolor">Mention</b>|。请注意,您需要放置一个空文本节点,而不是管道字符 | ,而不能在HTML代码中显示。 DOM能够处理超过序列化的HTML / HTML代码!然后,您需要选择空文本节点的内容。然后,下一个字符将替换空文本节点(选择文本节点内的空内容的范围将被替换,因此字符将被插入到文本节点中以更精确)。假设输入的字符为 A ,则效果最终为<b style="background: #somecolor">Mention</b>A

为了实现我建议的想法:

  • 避免使用innerHTML并使用DOM方法。 innerHTML无法创建空文本节点。
  • 仔细阅读Document Object Model Range上的文档。

我准备了一个jsFiddle来展示一个基本的实现。请注意,这是一个快速入侵,既不支持跨多个元素也不支持空父项。该示例将所有大写字母包装在b标记中,该标记应足以让您入门。

// Get the current selection and range
var selection = window.getSelection();
var range = selection.getRangeAt(0);

var textNode = range.startContainer;    
var text = textNode.data;
// Create a new node to hold the text in front of the range
var textBeforeAddedContent = document.createTextNode(text.substr(0, range.startOffset));
// Update current node to remove everything which goes into the just create node and the content to be replaced
textNode.data = text.substr(range.endOffset);
// Put stuff in front of the range into the document. Unless a non-empty selection was made the content looks now exactly as before calling this code
textNode.parentNode.insertBefore(textBeforeAddedContent, textNode);

// Place the new content in between the text nodes
var wrapper = document.createElement('b');
wrapper.appendChild(document.createTextNode(letter));
textNode.parentNode.insertBefore(wrapper, textNode);

// You might want to set the new range explicitly here and add an empty text node wherever newly typed letters shall go
// Set the new selection explicitly
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
selection.addRange(range);

Chrome无法从偏移量0开始进行零长度选择,而是将零长度选择设置为前一个孩子的结尾。支持Chrome的丑陋黑客可能是插入一些不可见的字符,并在插入下一个字符后将其删除。

对于原始问题,Chrome的问题不应该太重要,因为在包装元素之后插入空格可能是有效的。然后,范围的起始偏移量将设置为1,以在空格后插入新内容。

答案 1 :(得分:0)

我已经迟了几年了,但是考虑到它并没有像你找到一个强大的,跨浏览器兼容的解决方案来解决你的问题,我想我会发出声音英寸

简单地说,您不需要偏离textarea实施,以实现您的目标

您可以简单地使用div,尺寸和位置相同,并放置在textarea下,其中包含textarea内容的版本,每个内容的字符串都包含在内在span元素中。您可以通过这些跨度格式化提及字符串。为了给这个元素对提供单个元素的外观,textarea的背景,以及&#34; shadow div&#34;的文本,边框和轮廓。应该透明。

如果您想了解如何在现有插件中完成此操作,请查看Mentionator,该插件提供您尝试模拟的功能。它结构合理,易于理解,并且有很多评论,所以你应该很少理解如何采用这种技术。它也是你的真正维护:)。