jquery - instalar - ¿Cómo agregar etiquetado con autocompletado a un modelo existente en Rails?
rails jquery ajax (3)
Estoy intentando agregar "etiquetas" a un modelo de Article
en una aplicación de Rails 3.
Me pregunto si hay una gema o un complemento que agregue la funcionalidad de "etiquetado" en el modelo y también los ayudantes de autocompletar para las vistas.
He encontrado acts_as_taggable
pero no estoy seguro de si eso es lo que debería estar usando. ¿Hay algo más nuevo? Estoy obteniendo resultados de 2007 cuando google actes_as_taggable
Escribí una publicación de blog sobre esto recientemente; por brevedad, mi método le permite tener etiquetas filtradas por contexto (opcionales) (por ejemplo, por modelo y por atributo en el modelo), mientras que la solución de @Tim Santeford le proporcionará todas las etiquetas para un modelo (no se filtra por campo) . A continuación se muestra la publicación textual.
Probé la solución de Tim Santeford , pero el problema estaba en los resultados de la etiqueta. En su solución, usted obtiene todas las etiquetas existentes devueltas a través de autocompletar, ¡y no dentro del alcance de sus modelos y campos etiquetables ! Entonces, se me ocurrió una solución que, en mi opinión, es mucho mejor; es extensible automáticamente a cualquier modelo que desee etiquetar, es eficiente y, sobre todo, es muy simple. Utiliza la gema acts_as_taggable_on y la biblioteca de JavaScript select2 .
Instala la gema Acts-As-Taggable-On
- Agregue actos-como-etiquetables-en su Gemfile:
gem ''acts-as-taggable-on'', ''~> 3.5''
- Ejecutar
bundle install
para instalarlo - Genere las migraciones necesarias:
rake acts_as_taggable_on_engine:install:migrations
- Ejecuta las migraciones con
rake db:migrate
¡Hecho!
Configure el etiquetado normal en su MVC
Digamos que tenemos un modelo de Film
(porque yo sí). Simplemente agregue las siguientes dos líneas a su modelo:
class Film < ActiveRecord::Base
acts_as_taggable
acts_as_taggable_on :genres
end
Eso es todo para el modelo. Ahora en el controlador. Necesitas aceptar las listas de etiquetas en tus parámetros. Así que tengo lo siguiente en mi FilmsController
:
class FilmsController < ApplicationController
def index
...
end
...
private
def films_params
params[:film].permit(..., :genre_list)
end
end
Observe que el parámetro no es un genres
como el que especificamos en el modelo. No me preguntes por qué, sino que actúa como taggable-on espera singular + _list , y esto es lo que se requiere en las vistas.
En la capa de vista! Utilizo la gema SimpleForm y el motor de plantillas Slim para las vistas, por lo que mi forma puede verse un poco diferente a la tuya si no usas la gema. Pero, es solo un campo de entrada de texto normal:
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
Necesita este atributo input_html
con ese valor establecido para representarlo como una cadena separada por comas (que es lo que actúa como taggable-on espera en el controlador). ¡El etiquetado debería funcionar ahora cuando envía el formulario! Si no funciona, recomiendo ver (el asombroso) el episodio de Ryan Bates Railscast en el etiquetado .
Integrando select2 en tus formularios
En primer lugar, necesitamos incluir la biblioteca select2; puede incluirlo manualmente o usar (mi preferencia) la gema select2-rails que paquetes select2 para el canal de activos de Rails.
Agregue la gema a su Gemfile: gem ''select2-rails'', ''~> 4.0''
, luego ejecute bundle install
.
Incluya el JavaScript y CSS en su flujo de activos:
En application.js : //= require select2-full
. En application.css : *= require select2
.
Ahora necesita modificar sus formularios un poco para incluir lo que Select2 espera para el etiquetado. Esto puede parecer un poco confuso, pero lo explicaré todo. Cambie su entrada de formulario anterior:
= f.input :genre_list, input_html: {value: @film.genre_list.to_s}
a:
= 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
Añadimos una entrada oculta que actuará como el valor real enviado al controlador. Select2 devuelve una matriz , donde actúa como taggable-on espera una cadena separada por comas . La entrada de formulario select2 se convierte a esa cadena cuando cambia su valor y se establece en el campo oculto. Llegaremos a eso pronto.
Los atributos id
y name
para el f.input
realmente no importan. Simplemente no pueden superponerse con su entrada hidden
. El hash de data
es realmente importante aquí. El campo taggable
nos permite usar JavaScript para inicializar todas las entradas select2 de una sola vez, en lugar de inicializar manualmente por ID para cada una. El campo taggable_type
se usa para filtrar las etiquetas de su modelo en particular, y el campo de context
es filtrar las etiquetas que se han usado antes en ese campo. Finalmente, el campo de collection
simplemente establece los valores apropiadamente en la entrada.
La siguiente parte es JavaScript. Necesitamos inicializar todos los elementos select2 en toda la aplicación. Para hacer esto, simplemente agregué la siguiente función a mi archivo application.js
, para que funcione para cada ruta:
// 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
}
});
});
Esto puede parecer complejo, pero no es demasiado difícil. Básicamente, el selector $("*[data-taggable=''true'']")
solo obtiene cada elemento HTML donde tenemos taggable: true
establecido en los datos. Acabamos de agregar eso al formulario, y es por esto que queremos poder inicializar select2 para todos los campos etiquetados .
El resto es solo código relacionado con AJAX. Esencialmente, hacemos una llamada AJAX a /tags
con el name
los parámetros, taggable_type
y context
. ¿Suena familiar? Esos son los atributos de datos que establecemos en nuestra entrada de formulario. Cuando se devuelven los resultados, simplemente le damos a select2 el nombre de la etiqueta.
Ahora probablemente estás pensando: ¡No tengo una ruta /tags
! . ¡Tienes razón! Pero estás a punto de hacerlo :)
Añadiendo la ruta / tags
Vaya a su archivo routes.rb
y agregue lo siguiente: resources :tags
. No tiene que agregar todas las rutas para las etiquetas, pero lo hice para poder tener una manera fácil de usar las etiquetas CRUD. También puede simplemente hacer: get ''/tags'' => ''tags#index''
Esa es realmente la única ruta que necesitamos en este momento. Ahora que tenemos la ruta, tenemos que crear un controlador llamado 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
Esto es bastante simple. Podemos enviar una solicitud a /tags
, con el name
los parámetros (el texto de la etiqueta), tags_chosen
(las etiquetas seleccionadas existentes), taggable_type
(el modelo que está etiquetado) y el context
opcional (el campo que está etiquetado). Si tenemos etiquetas de género para "comedia" y "conspiración", entonces escriba co en nuestra forma, el JSON renderizado debería tener este aspecto:
[
{
"id": 12,
"name": "comedy",
"taggings_count": 1
},
{
"id": 11,
"name": "conspiracy",
"taggings_count": 1
}
]
Ahora, en la entrada select2, deberías ver "comedia" y "conspiración" como opciones de etiqueta completadas automáticamente.
Mis etiquetas no se guardan!
Hay un último paso. Necesitamos establecer los valores select2 en nuestro campo hidden
que creamos anteriormente.
Este código puede ser diferente para usted dependiendo de cómo estructure su formulario, pero esencialmente desea obtener la entrada select2, convertir el conjunto de cadenas en una cadena CSV (por ejemplo, ["comedy", "conspiracy"]
-> "comedy, conspiracy"
) y establezca esa cadena CSV en el campo oculto. Eso no es demasiado difícil, afortunadamente.
Puede capturar el evento de entrada de select2 cambiado, o cualquier otra cosa que le convenga. Es su elección, pero este paso debe realizarse para garantizar que el controlador de Rails reciba el valor correctamente. De nuevo, en 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);
});
Debería ver lo siguiente en la acción de su controlador una vez que haya convertido exitosamente sus valores: "genre_list"=>"comedy,conspiracy"
¡Y eso es todo lo que necesita para autocompletar etiquetas en Rails usando acts as-tag-taggable-on y select2!
La gema acts_as_taggable_on_steroids es probablemente su mejor apuesta. Descubrí que muchas de las gemas de etiquetado son más un "buen lugar para comenzar", pero luego requieren una buena cantidad de personalización para obtener el resultado que desea.
acts_as_taggable_on y rails3-jquery-autocomplete trabajan muy bien juntos para hacer un sistema de etiquetado similar al SO, vea el ejemplo a continuación. No creo que exista una opción adecuada todo en una para rieles.
Siga estos pasos para obtener todo esto instalado:
1. Copia de seguridad de su aplicación rieles!
2. Instalar jquery-rails
Nota: Puedes instalar jQuery UI con jquery-rails
pero elegí no hacerlo.
3. Descarga e instala jQuery UI
Elija un tema que complementará su diseño web (asegúrese de probar la demostración autocompletada con el tema que elija, el tema predeterminado no funcionó para mí). Descargue el [zipfile]/js/jquery-ui-#.#.#.custom.min.js
zip personalizado y coloque el [zipfile]/js/jquery-ui-#.#.#.custom.min.js
en la carpeta /public/javascripts/
su aplicación. coloque la [zipfile]/css/custom-theme/
y todos los archivos en la carpeta public/stylesheets/custom-theme/
su aplicación.
4. Agregue lo siguiente a su Gemfile y luego ejecute "bundle install"
gema ''actúa-como-etiquetable-en''
gema ''rails3-jquery-autocomplete''
5. Desde la consola ejecuta los siguientes comandos:
rieles generan act_as_taggable_on: migración
rastrillo db: migrar
carriles generar autocompletar: instalar
Realiza estos cambios en tu aplicación.
Incluya los archivos javascript y css necesarios en el diseño de su aplicación:
<%= stylesheet_link_tag "application", "custom-theme/jquery-ui-1.8.9.custom" %>
<%= javascript_include_tag :defaults, "jquery-ui-#.#.#.custom.min", "autocomplete-rails" %>
Ejemplo de controlador
EDITAR: hizo cambios basados en los comentarios de Seth Pellegrino.
class ArticlesController < Admin::BaseController
#autocomplete :tag, :name <- Old
autocomplete :tag, :name, :class_name => ''ActsAsTaggableOn::Tag'' # <- New
end
Ejemplo de modelo
class Article < ActiveRecord::Base
acts_as_taggable_on :tags
end
Route.rb
resources :articles do
get :autocomplete_tag_name, :on => :collection
end
Ver ejemplo
<%= 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 %>
Nota: en este ejemplo se supone que solo está etiquetando un modelo en toda la aplicación y solo está utilizando el tipo de etiqueta predeterminado: etiquetas. Básicamente, el código anterior buscará todas las etiquetas y no las limitará a las etiquetas de "Artículo".