There are lots of libraries that implement auto complete, including the 2 most popular, autocomplete and type-ahead.
However, obviously every library is extra overhead so if you want to implement it yourself, you certainly can.
Some things to consider:
- The URL of the request -- given that in this case you don't care about hte books, and really just want a list of all potentially matching categories, best to call to a categories controller and place your code there accordingly.
- You'll want to return a list so users can choose, but you don't need to pass that list to the controller, so a hidden field for the category_id is a good idea, this way you can ignore everything that strong params already filters out and just keep the good stuff.
- jBuilder is your friend. It lets you lay out JSON anyway you want without clouding your controllers. In this case it's not strictly necessary, because all you need is a label field and an id field, but I'll throw it in for good measure.
- Since you are using
@categories in your standard controller call, I would be hesitant to use the same instance variable to return your search results, thus I named it differently.
- The drop down box you have in your original example is unnecessary as far as I see it because an unordered list is just as good with less code.
Given that, you could do something like this in your view (form)
View
<div id="book_create">
<%= simple_form_for :book, url: books_path do |f| %>
<%= f.input :name %>
<%= f.input :category_id, as: :hidden %>
<%= f.input :category_search, as: :string, input_html: {class: "book_search"} , autocomplete: false %>
<div id="cat_search"></div>
<%= f.submit %>
<% end %>
</div>
<style>
#categories { width: 200px; max-height: 220px; overflow: scroll; box-shadow: 1px 1px 4px rgba(158, 158, 158, 0.43); list-style: none; padding: 0; }
#categories li:nth-of-type(odd) { background-color: rgb(238, 247, 255); }
#categories li { cursor: pointer; }
</style>
Simple css included for good measure.
Javascript
$('.book_search').keyup(function(){
var q = $(this).val();
$.ajax({
type: "GET",
url: "/categories/search",
dataType: "json",
data: {'keyword': q},
success: function(result){
$("#categories").remove();
$("#cat_search").after('<ul id="categories"></ul>');
render = true;
$("#categories").on("click", "li", function(){
$("#book_category_id").val($(this).data('id'));
$(".book_search").val($(this).text());
$("#categories").remove();
});
for(term in result){
render = false;
$("#categories").append("<li data-id="+result[term].id+">" + result[term].keyword + "</li>");
}
}
})
});
There's really no rails magic in this JS, it's just straightforward jQuery. You grab the users input and make a call to a url with the user input as the query, if the query is returned, remove any previous display list then build a display a list (ul) for the user with all the possible results. Clicking on the result of your choice does 2 things, it a.) adds the id from the data attribute we injected from the result set and b.) clears the input field the user typed in replacing it with the full name of their selected result.
Controller
class CategoriesController < ApplicationController
before_action :set_category, only: [:show, :edit, :update, :destroy]
respond_to :json, :html
def search
@cat_search_results = Category.where("keyword LIKE LOWER(?)", "%#{params[:keyword].downcase}%")
respond_with(@cat_search_results, :include => :status)
end
end
Jbuilder
#/categories/search.json.jbuilder
json.array!(@cat_search_results) do |category|
json.extract! category, :id, :keyword
end
Routes
resources categories do
collection do
get :search
end
end