Мне интересно, есть ли драгоценный камень или плагин, который добавляет как функциональность "тегов" в модели, так и автозаполнение помощников для представлений.
Ответ 1
act_as_taggable_on и rails3-jquery-autocomplete работать красиво вместе, чтобы сделать SO-подобную систему мечения, см. пример ниже. Я не думаю, что все подходящие все в одном варианте существуют еще для рельсов.
Выполните следующие шаги, чтобы установить все:
1. Резервное копирование вашего рельса!
2. Установите jquery-rails
Примечание. Пользовательский интерфейс jQuery можно установить с помощью jquery-rails
, но я не выбрал.
3. Загрузите и установите jQuery UI
Выберите тему, которая будет дополнять ваш веб-дизайн (обязательно проверьте демоверсию автозаполнения с выбранной вами темой, тема по умолчанию для меня не работает). Загрузите пользовательский почтовый индекс и поместите файл [zipfile]/js/jquery-ui-#.#.#.custom.min.js
в папку приложения /public/javascripts/
. поместите папку [zipfile]/css/custom-theme/
и все файлы в папку приложения public/stylesheets/custom-theme/
.
4. Добавьте в свой Gemfile следующее, а затем запустите "bundle install"
gem 'act-as-taggable-on'
gem 'rails3-jquery-autocomplete'
5. В консоли запустите следующие команды:
рельсы генерируют act_as_taggable_on: migration
rake db: migrate
рельсы создают автозаполнение: install
Внесите эти изменения в ваше приложение
Включите необходимые файлы javascript и css в макет приложения:
<%= stylesheet_link_tag "application", "custom-theme/jquery-ui-1.8.9.custom" %>
<%= javascript_include_tag :defaults, "jquery-ui-#.#.#.custom.min", "autocomplete-rails" %>
Пример контроллера
EDIT: Сделаны изменения, основанные на комментариях Сета Пеллегрино.
class ArticlesController < Admin::BaseController
#autocomplete :tag, :name <- Old
autocomplete :tag, :name, :class_name => 'ActsAsTaggableOn::Tag' # <- New
end
Пример модели
class Article < ActiveRecord::Base
acts_as_taggable_on :tags
end
Route.rb
resources :articles do
get :autocomplete_tag_name, :on => :collection
end
Пример просмотра
<%= 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 %>
Примечание. В этом примере предполагается, что вы всего лишь помещаете одну модель во все приложение и используете только теги тегов по умолчанию: теги. В основном код выше будет искать все теги, а не ограничивать их тегами "Article".
Ответ 3
Я написал сообщение об ошибке об этом недавно; для краткости мой метод позволяет вам иметь (необязательные) теги, отфильтрованные по полю (например, по модели и по атрибуту на модели), Ниже приведен стенографический пост.
Я опробовал решение Тима Сантфорда, но проблема была связана с результатами тегов. В его решении вы получаете все существующие теги, возвращаемые с помощью автозаполнения, и не привязаны к вашим моделям и помеченным полям! Итак, я придумал решение, которое, на мой взгляд, намного лучше; он автоматически расширяется до любой модели, которую вы хотите пометить, это эффективно, и, прежде всего, это очень просто. Он использует acts-as-taggable-on gem и select2 Библиотека JavaScript.
Установите Драйв Acts-As-Taggable-On
- Добавить act-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
, как мы указывали в модели. Не спрашивайте меня, почему, но action-as-taggable-on ожидает единственного + _list, и это то, что требуется в представлениях.
На уровень просмотра! Я использую SimpleForm и Slim шаблон для просмотров, поэтому моя форма может выглядеть немного иначе, чем ваша, если вы не используете драгоценный камень. Но это просто нормальное поле text input:
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
Вам нужен этот атрибут input_html
с этим значением, установленным для рендеринга его как строки с разделителями-запятыми (что и подразумевается в контроллере). Пометка должна теперь работать, когда вы отправляете форму! Если это не сработает, я рекомендую посмотреть (удивительный) Ryan Bates 'Railscast эпизод с тегами.
Интеграция select2 в ваши формы
Во-первых, нам нужно включить библиотеку select2; вы можете включить его вручную или использовать (мои предпочтения) select2-rails gem, которые выбирают 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 возвращает массив , где action-as-taggable-on ожидает строку , разделенную запятыми. Ввод формы select2 преобразуется в эту строку, когда ее значение изменяется и устанавливается в скрытое поле. Мы скоро доберемся до этого.
Атрибуты id
и name
для f.input
фактически не имеют значения. Они просто не могут пересекаться с вашим входом hidden
. Хэш data
действительно важен здесь. Поле taggable
позволяет нам использовать JavaScript для инициализации всех входов select2 за один раз вместо ручной инициализации по идентификатору для каждого из них. Поле 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, используя act-as-taggable-on и select2!