关于jquery:如何在Rails中将具有自动完成功能的标记添加到现有模型中?

How to add tagging with autocomplete to an existing model in Rails?

我正在尝试在Rails 3应用程序中为Article模型添加"标签"。

我想知道是否有一个gem或插件在模型中添加了"标记"功能以及视图的自动完成助手。

我找到了acts_as_taggable,但是我不确定这是否应该使用。有什么新东西吗?当我在Google上访问acts_as_taggable

时,我得到了2007年的结果


acts_as_taggable_on和rails3-jquery-autocomplete可以很好地协同工作,以创建类似于SO的标记系统,请参见下面的示例。我认为对于Rails来说,还没有合适的多合一选项。

请按照以下步骤安装所有组件:

1。备份您的Rails应用程序!
2。安装jquery-rails

注意:您可以使用jquery-rails安装jQuery UI,但我选择不安装。

3。下载并安装jQuery UI

选择一个适合您网页设计的主题(请务必使用您选择的主题测试自动完成演示,默认主题对我而言不起作用)。下载自定义zip并将[zipfile]/js/jquery-ui-#.#.#.custom.min.js文件放入应用程序的/public/javascripts/文件夹中。将[zipfile]/css/custom-theme/文件夹和所有文件放入应用程序的public/stylesheets/custom-theme/文件夹中。

4。将以下内容添加到您的Gemfile中,然后运行"捆绑安装"

gem 'acts-as-taggable-on'
gem 'rails3-jquery-autocomplete'

5。从控制台运行以下命令:

rails generate acts_as_taggable_on:migration
rake db:migrate
rails generate autocomplete:install

在您的应用中进行这些更改

在应用程序布局中包括必要的javascript和css文件:

1
2
<%= stylesheet_link_tag"application","custom-theme/jquery-ui-1.8.9.custom" %>  
<%= javascript_include_tag :defaults,"jquery-ui-#.#.#.custom.min","autocomplete-rails" %>

控制器示例

编辑:根据塞思·佩莱格里诺(Seth Pellegrino)的评论进行更改。

1
2
3
4
class ArticlesController < Admin::BaseController  
  #autocomplete :tag, :name  <- Old  
  autocomplete :tag, :name, :class_name => 'ActsAsTaggableOn::Tag' # <- New
end

模型示例

1
2
3
class Article < ActiveRecord::Base
   acts_as_taggable_on :tags
end

Route.rb

1
2
3
resources :articles do
  get :autocomplete_tag_name, :on => :collection    
end

查看示例

1
2
3
4
<%= form_for(@article) do |f| %>
  <%= f.autocomplete_field :tag_list, autocomplete_tag_name_articles_path, :"data-delimiter" => ', ' %>
  # note tag_list above is a virtual column created by acts_as_taggable_on
<% end %>

注意:此示例假定您仅在整个应用程序中标记一个模型,并且仅使用默认标记类型:tags。基本上,上面的代码将搜索所有标签,而不是将它们限制为" Article"标签。


acts_as_taggable_on_steroidsruby可能是您最好的选择。我发现许多标记ruby更像是"开始的好地方",但是需要大量的自定义才能获得想要的结果。


我最近写了一篇关于这个的博客文章;为了简洁起见,我的方法允许您具有(可选)上下文过滤的标签(例如,按模型和模型上的属性),而@Tim Santeford的解决方案将为您提供模型的所有标签(不按字段过滤) 。以下是逐字记录。

我尝试了蒂姆·桑特福德(Tim Santeford)的解决方案,但问题出在标签结果上。在他的解决方案中,您将通过自动完成功能返回所有现有标签,而不是将其限制在模型和可标记字段中!因此,我想出了一个解决方案,我认为它要好得多。它可以自动扩展到您要标记的任何模型,它高效,最重要的是,它非常简单。它使用行为上可标记的gem和select2 JavaScript库。

安装Acts-As-Taggable-On gem

  • 将行为作为标记添加到您的Gemfile中:gem 'acts-as-taggable-on', '~> 3.5'
  • 运行bundle install进行安装
  • 生成必要的迁移:rake acts_as_taggable_on_engine:install:migrations
  • 使用rake db:migrate运行迁移
  • 完成!

    在您的MVC中设置常规标记

    假设我们有一个Film模型(因为我有)。只需将以下两行添加到模型中:

    1
    2
    3
    4
    class Film < ActiveRecord::Base
        acts_as_taggable
        acts_as_taggable_on :genres
    end

    就是这个模型。现在到控制器上。您需要接受参数中的标签列表。所以我的FilmsController中有以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class FilmsController < ApplicationController
        def index
            ...
        end
        ...

        private

        def films_params
            params[:film].permit(..., :genre_list)
        end
    end

    请注意,该参数不同于我们在模型中指定的genres。不要问我为什么,但是按可标记的行为期望单数_list,这就是视图中所需要的。

    到视图层!我将SimpleForm gem和Slim模板引擎用于视图,因此如果您不使用gem,我的表单可能会与您的表单有些不同。但是,这只是一个普通的文本输入字段:

    1
    = f.input :genre_list, input_html: {value: @film.genre_list.to_s}

    您需要将此input_html属性设置为该值,以便将其呈现为逗号分隔的字符串(这是控制器中期望的行为)。提交表单后,标记现在应该可以工作了!如果不起作用,建议您观看(惊人的)Ryan Bates的Railscast标签。

    将select2集成到表单中

    首先,我们需要包括select2库;您可以手动添加它,也可以(根据我的喜好)使用将select2打包到Rails资产管道中的select2-rails gem。

    将gem添加到您的Gemfile中:gem 'select2-rails', '~> 4.0',然后运行bundle install

    在资产管道中包含JavaScript和CSS:

    在application.js中://= require select2-full。在application.css中:*= require select2

    现在,您需要对表单进行一些修改,以包括select2期望进行标记的内容。这似乎有点令人困惑,但是我将解释所有事情。更改您以前的表单输入:

    1
    = f.input :genre_list, input_html: {value: @film.genre_list.to_s}

    至:

    1
    2
    3
    4
    5
    6
    7
    = f.hidden_field :genre_list, value: @film.genre_list.to_s
    = f.input :genre_list,
        input_html: { id:"genre_list_select2",
                    name:"genre_list_select2",
                    multiple: true,
                    data: { taggable: true, taggable_type:"Film", context:"genres" } },
        collection: @film.genre_list

    我们添加了一个隐藏的输入,它将作为发送给控制器的实际值。 Select2返回一个数组,其中acts-as-taggable-on需要一个逗号分隔的字符串。当select2表单输入的值更改时,会将其转换为该字符串,并将其设置为隐藏字段。我们会尽快解决。

    f.inputidname属性实际上无关紧要。它们只是不能与您的hidden输入重叠。 data哈希在这里非常重要。 taggable字段允许我们使用JavaScript一次性初始化所有select2输入,而不是通过id手动为每个输入进行初始化。 taggable_type字段用于过滤特定型号的标签,而context字段用于过滤以前在该字段中使用过的标签。最后,collection字段仅在输入中适当地设置值。

    下一部分是JavaScript。我们需要在整个应用程序中初始化所有select2元素。为此,我只需将以下函数添加到我的application.js文件中,以使其适用于所有路由:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    // Initialize all acts-as-taggable-on + select2 tag inputs
    $("*[data-taggable='true']").each(function() {
        console.log("Taggable:" + $(this).attr('id') +"; initializing select2");
        $(this).select2({
            tags: true,
            theme:"bootstrap",
            width:"100%",
            tokenSeparators: [','],
            minimumInputLength: 2,
            ajax: {
                url:"/tags",
                dataType: 'json',
                delay: 100,
                data: function (params) {
                    console.log("Using AJAX to get tags...");
                    console.log("Tag name:" + params.term);
                    console.log("Existing tags:" + $(this).val());
                    console.log("Taggable type:" + $(this).data("taggable-type"));
                    console.log("Tag context:" + $(this).data("context"));
                    return {
                        name: params.term,
                        tags_chosen: $(this).val(),
                        taggable_type: $(this).data("taggable-type"),
                        context: $(this).data("context"),
                        page: params.page
                    }
                },
                processResults: function (data, params) {
                    console.log("Got tags from AJAX:" + JSON.stringify(data, null, '\\t'));
                    params.page = params.page || 1;

                    return {
                        results: $.map(data, function (item) {
                            return {
                                text: item.name,
                                // id has to be the tag name, because acts_as_taggable expects it!
                                id: item.name
                            }
                        })
                    };
                },
                cache: true
            }
        });
    });

    这可能看起来很复杂,但并不难。基本上,$("*[data-taggable='true']")选择器仅获取在数据中设置了taggable: true的每个HTML元素。我们只是将其添加到表单中,这就是原因-我们希望能够为所有可标记字段初始化select2。

    其余的只是与AJAX相关的代码。本质上,我们使用参数nametaggable_typecontext/tags进行AJAX调用。听起来有点熟?这些是我们在表单输入中设置的数据属性。返回结果后,我们只需为select2赋予标签名称即可。

    现在您可能在想:我没有/tags路线!。你是对的!但是你将要:)

    添加/ tags路由

    进入您的routes.rb文件并添加以下内容:resources :tags。您不必为标签添加所有路由,但是我这样做是为了获得CRUD标签的简便方法。您也可以简单地执行以下操作:get '/tags' => 'tags#index'

    那实际上是我们目前唯一需要的路线。现在有了路线,我们必须创建一个名为TagsController

    的控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class TagsController < ApplicationController
        def index
            @tags = ActsAsTaggableOn::Tag
                    .where("name ILIKE ?","%#{params[:name]}%")
                    .where.not(name: params[:tags_chosen])
                    .includes(:taggings)
                    .where(taggings: {taggable_type: params[:taggable_type]})
            @tags = @tags.where(taggings: {context: params[:context] }) if params[:context]
            @tags.order!(name: :asc)
            render json: @tags
        end
    end

    这很简单。我们可以使用参数name(标签文本),tags_chosen(现有选定标签),taggable_type(带有标签的模型)和可选的context(标签为被标记的字段)。如果我们具有"喜剧"和"阴谋"的类型标记,然后在我们的表单中键入co,则呈现的JSON应该如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [
        {
           "id": 12,
           "name":"comedy",
           "taggings_count": 1
        },
        {
           "id": 11,
           "name":"conspiracy",
           "taggings_count": 1
        }
    ]

    现在在select2输入中,您应该将"喜剧"和"阴谋"视为自动完成的标签选项!

    我的标签无法保存!

    还有最后一步。我们需要将select2值设置到我们先前创建的hidden字段中。

    根据您构造表单的方式,此代码可能与您有所不同,但是您本质上想获取select2输入,将字符串数组转换为CSV字符串(例如["comedy","conspiracy"]-> "comedy, conspiracy"),并将该CSV字符串设置为隐藏字段。幸运的是,这并不太困难。

    您可以捕获select2输入更改事件,或者其他适合您的事件。这是您的选择,但必须执行此步骤以确保Rails控制器正确接收该值。同样,在application.js中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*
    * When any taggable input changes, get the value from the select2 input and
    * convert it to a comma-separated string. Assign this value to the nearest hidden
    * input, which is the input for the acts-on-taggable field. Select2 submits an array,
    * but acts-as-taggable-on expects a CSV string; it is why this conversion exists.
    */
    $(document).on('select2:select select2:unselect',"*[data-taggable='true']", function() {

        var taggable_id = $(this).attr('id')
        // genre_list_select2 --> genre_list
        var hidden_id = taggable_id.replace("_select2","");
        // film_*genre_list* ($= jQuery selectors ends with)
        var hidden = $("[id$=" + hidden_id +"]")
        // Select2 either has elements selected or it doesn't, in which case use []
        var joined = ($(this).val() || []).join(",");
        hidden.val(joined);
    });

    成功转换值后,您应该在控制器操作中看到以下内容:"genre_list"=>"comedy,conspiracy"

    这就是您需要使用acts-as-taggable-on和select2在Rails中完成自动完成标记的全部!