文本区域中的 Twitter 样式自动完成

Posted

技术标签:

【中文标题】文本区域中的 Twitter 样式自动完成【英文标题】:Twitter-style autocomplete in textarea 【发布时间】:2011-09-03 17:16:07 【问题描述】:

我正在寻找一个 javascript 自动完成实现,其中包括以下内容:

可用于 html 文本区域 允许在不调用自动完成的情况下输入常规文本 检测@ 字符并在键入时开始自动完成 通过 AJAX 加载选项列表

我相信这类似于 Twitter 在推文中添加标签时所做的事情,但我找不到一个好的、可重用的实现。 使用 jQuery 的解决方案将是完美的。

谢谢。

【问题讨论】:

github.com/jakiestfu/Mention.js 可能是另一种选择。 【参考方案1】:

THIS 应该可以工作。关于启动搜索的 @,只需将符号(动态或非动态)添加到每个可能的搜索词的开头即可。

【讨论】:

这行不通,因为它假定整个文本都被标记化了。也就是说,如果你写了一些文本,然后是一个@,它不会启动,因为它从行首读取。【参考方案2】:

我找不到任何完全符合我要求的解决方案,所以我最终得到了以下结果:

我使用 jQuery keypress() 事件来检查用户是否按下了@ 字符。 如果是这种情况,将使用 jQuery UI 显示一个模式对话框。此对话框包含一个自动完成文本字段(此处可以使用许多选项,但我推荐 jQuery Tokeninput) 当用户在对话框中选择一个选项时,会在文本字段中添加一个标签并关闭对话框。

这不是最优雅的解决方案,但与我的原始设计相比,它可以工作并且不需要额外的按键。

编辑 所以基本上,我们有一个大文本框,用户可以在其中输入文本。他应该能够“标记”用户(这只是意味着在文本中插入#<userid>)。我附加到 jQuery keyup 事件并使用 (e.which == 64) 检测 @ 字符以显示带有文本字段的模式,用于选择要标记的用户。

解决方案的核心就是这个带有jQuery Tokeninput 文本框的模态对话框。当用户在此处键入时,用户列表通过 AJAX 加载。请参阅网站上的示例以了解如何正确使用它。当用户关闭对话框时,我将选定的 ID 插入到大文本框中。

【讨论】:

嘿,您的解决方案有链接或 jsfiddle 吗?我一直在尝试所有可用的库,但没有一个完全符合要求。 我添加了进一步的解释:)【参考方案3】:

试试这个:

(function($)
    
        $.widget("ui.tagging", 
            // default options
            options: 
                source: [],
                maxItemDisplay: 3,
                autosize: true,
                animateResize: false,
                animateDuration: 50
            ,
            _create: function() 
                var self = this;
                
                this.activeSearch = false;
                this.searchTerm = "";
                this.beginFrom = 0;
    
                this.wrapper = $("<div>")
                    .addClass("ui-tagging-wrap");
                
                this.highlight = $("<div></div>");
                
                this.highlightWrapper = $("<span></span>")
                    .addClass("ui-corner-all");
    
                this.highlightContainer = $("<div>")
                    .addClass("ui-tagging-highlight")
                    .append(this.highlight);
    
                this.meta = $("<input>")
                    .attr("type", "hidden")
                    .addClass("ui-tagging-meta");
    
                this.container = $("<div></div>")
                    .width(this.element.width())
                    .insertBefore(this.element)
                    .addClass("ui-tagging")
                    .append(
                        this.highlightContainer, 
                        this.element.wrap(this.wrapper).parent(), 
                        this.meta
                    );
                
                var initialHeight = this.element.height();
                
                this.element.height(this.element.css('lineHeight'));
                
                this.element.keypress(function(e) 
                    // activate on @
                    if (e.which == 64 && !self.activeSearch) 
                        self.activeSearch = true;
                        self.beginFrom = e.target.selectionStart + 1;
                    
                    // deactivate on space
                    if (e.which == 32 && self.activeSearch) 
                        self.activeSearch = false;
                    
                ).bind("expand keyup keydown change", function(e) 
                    var cur = self.highlight.find("span"),
                        val = self.element.val(),
                        prevHeight = self.element.height(),
                        rowHeight = self.element.css('lineHeight'),
                        newHeight = 0;
                    cur.each(function(i) 
                        var s = $(this);
                        val = val.replace(s.text(), $("<div>").append(s).html());
                    );
                    self.highlight.html(val);
                    newHeight = self.element.height(rowHeight)[0].scrollHeight;
                    self.element.height(prevHeight);
                    if (newHeight < initialHeight) 
                        newHeight = initialHeight;
                    
                    if (!$.browser.mozilla) 
                        if (self.element.css('paddingBottom') || self.element.css('paddingTop')) 
                            var padInt =
                                parseInt(self.element.css('paddingBottom').replace('px', '')) + 
                                parseInt(self.element.css('paddingTop').replace('px', ''));
                            newHeight -= padInt;
                        
                    
                    self.options.animateResize ?
                        self.element.stop(true, true).animate(
                                height: newHeight
                            , self.options.animateDuration) : 
                        self.element.height(newHeight);
                    
                    var widget = self.element.autocomplete("widget");
                        widget.position(
                            my: "left top",
                            at: "left bottom",
                            of: self.container
                        ).width(self.container.width()-4);
                    
                ).autocomplete(
                    minLength: 0,
                    delay: 0,
                    maxDisplay: this.options.maxItemDisplay,
                    open: function(event, ui) 
                        var widget = $(this).autocomplete("widget");
                        widget.position(
                            my: "left top",
                            at: "left bottom",
                            of: self.container
                        ).width(self.container.width()-4);
                    ,
                    source: function(request, response) 
                        if (self.activeSearch) 
                            self.searchTerm = request.term.substring(self.beginFrom); 
                            if (request.term.substring(self.beginFrom - 1, self.beginFrom) != "@") 
                                self.activeSearch = false;
                                self.beginFrom = 0;
                                self.searchTerm = "";
                            
                            if (self.searchTerm != "") 
                                
                                if ($.type(self.options.source) == "function") 
                                    self.options.source(request, response);                   
                                 else 
                                    var re = new RegExp("^" + escape(self.searchTerm) + ".+", "i");
                                    var matches = [];
                                    $.each(self.options.source, function() 
                                        if (this.label.match(re)) 
                                            matches.push(this);
                                        
                                    );
                                    response(matches);
                                
                            
                        
                    ,
                    focus: function() 
                        // prevent value inserted on focus
                        return false;
                    ,
                    select: function(event, ui) 
                        self.activeSearch = false;
                        //console.log("@"+searchTerm, ui.item.label);
                        this.value = this.value.replace("@" + self.searchTerm, ui.item.label) + ' ';
                        self.highlight.html(
                            self.highlight.html()
                                .replace("@" + self.searchTerm,
                                         $("<div>").append(
                                             self.highlightWrapper
                                                 .text(ui.item.label)
                                                 .clone()
                                         ).html()+' ')
                        );
                            
                        self.meta.val((self.meta.val() + " @[" + ui.item.value + ":]").trim());
                        return false;
                    
                );
    
            
        );
body, html 
        font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
    
    
    .ui-tagging 
        position: relative;
        border: 1px solid #B4BBCD;
        height: auto;
    
    
    .ui-tagging .ui-tagging-highlight 
        position: absolute;
        padding: 5px;
        overflow: hidden;
    
    .ui-tagging .ui-tagging-highlight div 
        color: transparent;
        font-size: 13px;
        line-height: 18px;
        white-space: pre-wrap;
    
    
    .ui-tagging .ui-tagging-wrap 
        position: relative;
        padding: 5px;
        overflow: hidden;
        zoom: 1;
        border: 0;
    
    
    .ui-tagging div > span 
        background-color: #D8DFEA;
        font-weight: normal !important;
    
    
    .ui-tagging textarea 
        display: block;
        font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
        background: transparent;
        border-width: 0;
        font-size: 13px;
        height: 18px;
        outline: none;
        resize: none;
        vertical-align: top;
        width: 100%;
        line-height: 18px;
        overflow: hidden;
    
    
    .ui-autocomplete 
        font-size: 13px;
        background-color: white;
        border: 1px solid black;
        margin-bottom: -5px;
        width: 0;
    
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>

http://jsfiddle.net/mekwall/mcWnL/52/ 此链接将对您有所帮助

【讨论】:

起初似乎可以工作,但在我接受第一个自动完成并尝试第二个之后,第二个名称上的突出显示很快消失了。如果我在此之后尝试继续输入,选项卡将停止响应并最终崩溃。 (这是在 Chrome 中。)【参考方案4】:

最近我不得不面对这个问题,这就是我确定的方式......

    使用 selectionStart 获取 textarea 中光标位置的字符串索引 将字符串从索引 0 切到光标位置 将其插入到 span 中(因为 span 有多个边框) 使用 element.getClientRects() 获取边框相对于视口的尺寸。 (这里是MDN Reference) 计算顶部和左侧并将其提供给下拉列表

这适用于所有最新的浏览器。没有在旧的测试过

这里是Working bin

【讨论】:

【参考方案5】:

为此,我创建了一个Meteor 包。 Meteor 的数据模型允许使用自定义渲染列表进行快速多规则搜索。如果您没有将 Meteor 用于您的 Web 应用程序,(我相信)很遗憾,您不会发现任何用于自动完成的出色功能。

使用@自动完成用户,在线用户显示为绿色:

在同一行中,使用元数据和引导图标自动完成其他内容:

分叉、拉取和改进:

https://github.com/mizzao/meteor-autocomplete

【讨论】:

不久前看到你在 Meteor 开发商店展示了这个——也许是 10 月?干得好我的人【参考方案6】:

我确定您的问题早已解决,但 jquery-textcomplete 看起来可以解决问题。

【讨论】:

可能很难处理与此有关的提及,例如跟踪哪些用户已被提及,哪些已被删除,具体取决于您代表提及的用户的方式。【参考方案7】:

另一个解决这个问题的优秀库 At.js(已弃用)

Source

Demo

他们现在建议使用 Tribute 库

https://github.com/zurb/tribute

Example

【讨论】:

该库与 contentEditable 配合使用效果最佳,它不像 textarea 那样跨浏览器。如果希望模拟高亮/图标 with/on textarea 有更好的选择。 我遇到了 Ckeditor 的问题,At.js 解决了这个问题,谢谢!【参考方案8】:

你试过了吗

GITHUB:https://github.com/podio/jquery-mentions-input

演示/配置:http://podio.github.io/jquery-mentions-input/

实现起来非常简单。

【讨论】:

演示已损坏。下划线导入失败,出现 404。【参考方案9】:

这个小扩展似乎至少在演示中与所要求的内容最接近。因为它很小,所以很容易理解和修改。 http://lookapanda.github.io/jquery-hashtags/

【讨论】:

【参考方案10】:

另一个提供类似功能的插件:

AutoSuggest

您可以将它与自定义触发器一起使用,也可以在不使用任何触发器的情况下使用它。适用于输入字段、文本区域和 contenteditables。并且 jQuery 不是依赖项。

【讨论】:

以上是关于文本区域中的 Twitter 样式自动完成的主要内容,如果未能解决你的问题,请参考以下文章

获取文本区域中的光标位置

颜色:在 ie 中的文本上透明(使用覆盖技术突出显示文本区域中的文本)?

如何阻止 Chrome 自动完成文本区域?

多词预测/自动完成文本区域? [关闭]

限制文本区域中的行数

带有换行符的文本区域中的数据到数据库