Я недавно написал в блоге об этом;для краткости мой метод позволяет вам иметь (необязательно) context -фильтрованные теги (например, по модели и по атрибуту в модели), тогда как решение @Tim Santeford даст вам все теги для модели(не фильтруется по полю).Ниже приведено дословное сообщение.
Я опробовал Решение Тима Сэнтефорда , но проблема была в результатах тегов.В его решении вы получаете всех существующих тегов, возвращаемых через автозаполнение, а не в пределах ваших моделей и полей тегов !Итак, я нашел решение, которое, на мой взгляд, намного лучше;он автоматически расширяется на любую модель, которую вы хотите пометить, он эффективен, а главное - очень прост.Он использует гем cts-as-taggable-on и библиотеку JavaScript select2 .
Установите гем Acts-As-Taggable-On
- Добавьте актов-as-taggable-on в ваш Gemfile:
gem 'acts-as-taggable-on', '~> 3.5'
- Запустите
bundle install
для его установки - Создайте необходимые миграции:
rake acts_as_taggable_on_engine:install:migrations
- Запустите миграцию с помощью
rake db:migrate
Готово!
Настройте нормальное тегирование в вашем MVC
Допустим, у нас есть модель Film
(потому чтоЯ делаю).Просто добавьте следующие две строки в вашу модель:
class Film < ActiveRecord::Base
acts_as_taggable
acts_as_taggable_on :genres
end
Вот и все для модели.Теперь на контроллере.Вы должны принять списки тегов в ваших параметрах.Поэтому в моем FilmsController
есть следующее:
class FilmsController < ApplicationController
def index
...
end
...
private
def films_params
params[:film].permit(..., :genre_list)
end
end
Обратите внимание, что параметр не genres
, как мы указали в модели.Не спрашивайте меня, почему, но действует, как taggable-on ожидает единственное число * _list , и это то, что требуется в представлениях.
Напосмотреть слой!Я использую гем SimpleForm и шаблонизатор Slim для представлений, поэтому моя форма может немного отличаться от вашей, если вы не используете гем.Но это просто обычное поле ввода текста :
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
Вам необходим этот атрибут input_html
с этим значением, установленным для того, чтобы отобразить его в виде строки, разделенной запятыми (которая являетсячто действует в качестве контроллера тегов в контроллере).Маркировка теперь должна работать, когда вы отправляете форму!Если это не сработает, я рекомендую посмотреть (потрясающий) эпизод с Райканом Бейтсом в Railscast по тегированию .
Интеграция select2 в ваши формы
Прежде всего, нам нужновключить библиотеку select2;вы можете либо включить его вручную, либо использовать (мой выбор) гем select2-rails , который упаковывает select2 для конвейера ресурсов Rails.
Добавьте гем в свой Gemfile: gem 'select2-rails', '~> 4.0'
,затем запустите bundle install
.
Включите JavaScript и CSS в свой конвейер ресурсов:
В application.js : //= require select2-full
.В application.css : *= require select2
.
Теперь вам нужно немного изменить свои формы, чтобы включить в них то, что select2 ожидает для тегирования.Это может показаться немного запутанным, но я все объясню.Измените ваш предыдущий ввод формы:
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
на:
= 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 возвращает массив , где действует-как-taggable-on ожидает разделенную запятыми строку .Форма ввода select2 преобразуется в эту строку при изменении ее значения и устанавливается в скрытое поле.Мы скоро к этому вернемся.
Атрибуты id
и name
для f.input
на самом деле не имеют значения.Они просто не могут пересекаться с вашим hidden
вводом.Хэш data
действительно важен здесь.Поле taggable
позволяет нам использовать JavaScript для инициализации всех входов select2 за один раз вместо ручной инициализации по id для каждого.Поле taggable_type
используется для фильтрации тегов для вашей конкретной модели, а поле context
предназначено для фильтрации тегов, которые использовались ранее в этом поле.Наконец, поле collection
просто устанавливает соответствующие значения во входных данных.
Следующая часть - JavaScript.Нам нужно инициализировать все элементы select2 в приложении.Для этого я просто добавил следующую функцию в свой файл application.js
, чтобы она работала для каждого маршрута:
// 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']")
просто получает каждый элемент HTML, в котором мы установили taggable: true
в данных.Мы просто добавили это в форму, и именно поэтому - мы хотим иметь возможность инициализировать select2 для всех полей taggable .
Остальное - просто код, связанный с AJAX.По сути, мы делаем AJAX-вызов /tags
с параметрами name
, taggable_type
и context
.Звучит знакомо?Это атрибуты данных, которые мы устанавливаем при вводе формы.Когда результаты возвращаются, мы просто даем select2 имя тега.
Теперь вы, вероятно, думаете: У меня нет /tags
маршрута! .Ты прав!Но вы собираетесь:)
Добавление маршрута / tags
Перейдите в файл routes.rb
и добавьте следующее: resources :tags
.Вам не нужно добавлять все маршруты для тегов, но я сделал так, чтобы у меня был простой способ CRUD-тегов.Вы также можете просто сделать: get '/tags' => 'tags#index'
Это действительно единственный маршрут, который нам нужен на данный момент.Теперь, когда у нас есть маршрут, мы должны создать контроллер с именем TagsController
:
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
Это довольно просто.Мы можем отправить запрос на /tags
с параметрами name
(текст тега), tags_chosen
(существующие выбранные теги), taggable_type
(модель , которая помечена) инеобязательно context
(поле , которое помечено).Если у нас есть жанровые теги для «комедии» и «заговора», то введите в нашей форме co , рендеринг JSON должен выглядеть примерно так:
[
{
"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 :
/*
* 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"
И этовсе, что вам нужно, чтобы делать автозаполнение тегов в Rails, используя акты-как-теги-включения и select2!