How to use jquery-Tokeninput and Acts-as-taggable-on
Asked Answered
R

6

14

This is how you use autocomplete with jQuery Tokeninput and ActsAsTaggableOn.

In my situation i am using a nested form but it shouldnt matter. Everything below is code that works.

Code

Product Model:

attr_accessible :tag_list # i am using the regular :tag_list
acts_as_taggable_on :tags # Tagging products

Products Controller:

  #1. Define the tags path
  #2. Searches ActsAsTaggable::Tag Model look for :name in the created table.
  #3. it finds the tags.json path and whats on my form.
  #4. it is detecting the attribute which is :name for your tags.

def tags 
  @tags = ActsAsTaggableOn::Tag.where("tags.name LIKE ?", "%#{params[:q]}%") 
  respond_to do |format|
    format.json { render :json => @tags.map{|t| {:id => t.name, :name => t.name }}}
  end
end

Routes:

# It has to find the tags.json or in my case /products/tags.json
get "products/tags" => "products#tags", :as => :tags

Application.js:

$(function() {
  $("#product_tags").tokenInput("/products/tags.json", {
    prePopulate:       $("#product_tags").data("pre"),
    preventDuplicates: true,
    noResultsText:     "No results, needs to be created.",
    animateDropdown:   false
  });
});

Form:

<%= p.text_field :tag_list,
                 :id => "product_tags",
                 "data-pre" => @product.tags.map(&:attributes).to_json %>

Issue 1(SOLVED)


Must have the line:

format.json { render :json => @tags.collect{|t| {:id => t.name, :name => t.name }}}

Note - You can use @tags.map here as well and you dont have to change the form either.

Below are the 2 issues on why you needed to do this:

I have the following Tag: {"id":1,"name":"Food"}. When I save a Product, tagged "Food", it should save as ID: 1 when it searches and finds the name "Food". Currently, it saves a new Tag with a new ID that references the "Food" ID, i.e. {"id":19,"name":"1"}. Instead, it should be finding the ID, showing the name, and doing a find_or_create_by so it doesn't create a new Tag.


Issue 2(SOLVED)


When I go to products/show to see the tags by doing <%= @product.tag_list %>. The name appears as "Tags: 1", when it really should be "Tags: Food".

How can I fix these issues?

Raybin answered 13/7, 2011 at 4:34 Comment(7)
can you add relevant section from output of rake routes. Seems like there are some problems you are facing because of routes.Donaldson
The only relevant section that i can think of about my routes is new_product GET /products/new(.:format) {:action=>"new", :controller=>"products"},i edited my answer and included all of my product routes.Raybin
can you post your controller method which accepts data from tag form?Donaldson
I don't have a tag form though? Do you mean my ProductsController new and create methods, if so i will post those to. The tag method is already up.Raybin
Problem is taken-input submits id list back to the server and not names, and looks like act-as-taggable works with names directly. Take a look at edited tags action in answer.Donaldson
This has helped me so much. I've gotten this to work with tags, but how can I get this to work with tags on other models? I am struggling to get this to work with tags that have other contexts. I posted my question about it here: #18272686Younglove
In the form I also had to change @product.tags.map(&:attributes).to_json to @product.tags.map{|t| {:id => t.name, :name => t.name }}.to_json to prevent the existing tags to be sent by id rather than by name when updating a recordIncredulity
D
5

You should define a route in your routes.rb which should handle products/tags path. You can define it like:

get "products/tags" => "products#tags", :as => :tags

Thus should give you a tags_path helper which should evaluate to /products/tags. This should get rid of the errors you mentioned in the question. Be sure to add this route before defining resources :product in your routes.rb

Now onto acts-as-taggable-on, I haven't used this gem, but you should look at method all_tag_counts documentation. Your ProductsController#tags method will need some changes on the following lines. I am not sure if its exactly what would be required, as I use Mongoid and can't test it out.

def tags
  @tags = Product.all_tag_counts.(:conditions => ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{params[:q]}%"])
  respond_to do |format|
    format.json { render :json => @tags.collect{|t| {:id => t.name, :name => t.name } }
  end  
end
Donaldson answered 18/7, 2011 at 13:3 Comment(5)
Take a look at my 2.Note, the link gist.github.com/988968, 3rd gist box. What exactly do i route to? <%= example_get_all_tags_path %> has to route to my def tags but i don't know how to and what i call the path.Raybin
Take a look at gist.github.com/1090097, After defining routes as shown there(4th file) you should be able to replace example_get_all_tags_path with products_tags_pathDonaldson
Thank you, to get this working is almost complete but theirs still 2 issues i am having. Check out my question now, i edited the whole thing.Raybin
OK, you got it working, I appreciate all of the help, i would have been at this for days. I learned a lot here because i am a rookie at Ruby and Rails, Thank you.Raybin
not sure if i was supposed to hijack this thread, so didn't. I'm having difficulty implementing this solution as well and could use some help! #8214655Buonarroti
S
4

little add-on:

If you want to create the tags on the fly, you could do this in your controller:

 def tags
    query = params[:q]
    if query[-1,1] == " "
      query = query.gsub(" ", "")
      Tag.find_or_create_by_name(query)
    end

    #Do the search in memory for better performance

    @tags = ActsAsTaggableOn::Tag.all
    @tags = @tags.select { |v| v.name =~ /#{query}/i }
    respond_to do |format|
      format.json{ render :json => @tags.map(&:attributes) }
    end
  end

This will create the tag, whenever the space bar is hit.

You could then add this search setting in the jquery script:

noResultsText: 'No result, hit space to create a new tag',

It's a little dirty but it works for me.

Superelevation answered 13/7, 2011 at 4:34 Comment(2)
I like the noResultsText too. Thanks for the input.Raybin
A little late, but instead of Tag.find... it should be ActsAsTaggableOn::Tag.find_or_create..., anyways, it worked for me, thanks!Withoutdoors
A
4

There is a bug in Application.js code. There is an extra ) after "/products/tags.json". Remove the extra ). The code should be:

$("#product_tags").tokenInput("/products/tags.json", {
    prePopulate:       $("#product_tags").data("pre"),
    preventDuplicates: true,
    noResultsText:     "No results, needs to be created.",
    animateDropdown:   false
});
Ashcan answered 13/7, 2011 at 4:34 Comment(0)
S
1

Two notes: if you're getting the tags changed by numbers on the POST request, use:

tokenValue:        "name"

And if you're trying to add non-existent tags, use (undocumented):

allowFreeTagging:  true
Shu answered 13/7, 2011 at 4:34 Comment(0)
S
1

I had problems with editing the tags if for example the model failed to validate,

I changed

<%= p.text_field :tag_list,
             :id => "product_tags",
             "data-pre" => @product.tags.map(&:attributes).to_json %>

to

<%= p.text_field :tag_list, 
             :id => "product_tags", 
             "data-pre" => @product.tag_list.map {|tag| {:id => tag, :name => tag } }.to_json %>

If the form failed to validate on first submission, it was creating tags as the ID's of the tags it had created on subsequent submissions.

Strongarm answered 13/7, 2011 at 4:34 Comment(0)
W
1

I don't know if this is the entirety of your error, but you are not hitting the proper URL with the tokenInput plugin.

This

$("#product_tag_list").tokenInput("/products/tags.json"), {

should be

$("#product_tag_list").tokenInput("/products.json"), {

As I said, I don't know if this is the only problem you are having, but if you change this, does it work?

EDIT:

I have never used ActsAsTaggableOn. Does it create a Tag model for you to use?

From the looks of it on github, if you wanted to query all tags, you might have to use its namespace as opposed to just Tag, meaning ActsAsTaggableOn::Tag. For example, you can see how they access Tags directly in some of the specs.

Wilburwilburn answered 13/7, 2011 at 17:16 Comment(3)
Yeah that's the problem I'm having. I'm certain if can get this page to work then i have the Tokeninput mapping to the correct place. I believe what you typed here would work but i think my controller code is wrong so its not working.Raybin
Can you post the output of products.json?Wilburwilburn
Edited my answer a little further based on the information you provided.Wilburwilburn

© 2022 - 2024 — McMap. All rights reserved.