Searchkick autocomplete with multiple attributes - ruby-on-rails

Novice here, having some difficulties implementing an autocomplete using Searchkick and Typeahead/bloodhound.
First of all I have a index/welcome home page where I have my input field for my search box. This then searches over multiple models for names of different Cells and different cellular markers (not important for this).
First of all in my routes file, im unsure wether to have my autocomplete 'attached' to my "welcome" route or my "Cells" route, I've tried both but doesn't seem to make a difference, currently it is set as this:
resources :cells do
collection do
match 'autocomplete' => 'welcome#autocomplete', via: [:get], as: :autocomplete
end
end
resources :welcome #index / home page
my form input is:
<%= form_tag(#Search, :action => 'index', :method => "get", id: "search-form") do %>
<%= text_field_tag(:q, nil, placeholder: "Search...", class: "form-control rounded-left typeahead", autocomplete: "off") %>
<% end %>
My model is as follows:
searchkick text_start: [:name], callbacks: :async, suggest: [:name], word_start: [:name]
and my controller action:
def autocomplete
render json: Cell.search(params[:q], {
fields: ["name^5"],
autocomplete: true,
limit: 5,
load: false,
misspellings: false
}).map(&:name)
end
finally the javascript:
<script src="typeahead.bundle.js"></script>
<script>
var Cells = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/welcome/autocomplete?query=%QUERY',
wildcard: '%QUERY'
}
});
$('#typeahead').typeahead(null, {
source: Cells
});
</script>
I guess I am just very confused on how to properly set this up, I'm finding the Searchkick docs to be difficult to extrapolate to my purposes as I'm pretty novice at all this :) If anyone is able to help or point me to a good guide (that isn't years old) that would be amazing, as I'm pretty sure I'm just going around in circles. Is this the best way to go about implementing an autocomplete or is there a better way to do this?
Thanks for reading, and thanks for any help :)

A quick update for any future readers:
Im certain in the routes, the code for your autocomplete must point towards your controller that has your autocomplete method, for me it is my welcome controller.
resources :welcome do
collection do
match 'autocomplete' => 'welcome#autocomplete', via: [:get], as: :autocomplete
end
end
Im happy with my autocomplete method in my controller and am satisfied that I need the current javascript (currently in tags in my html
<script>
var autocomplete = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/welcome/autocomplete?q=%QUERY',
wildcard: '%QUERY'
}
});
$('#typeahead').typeahead(null, {
source: autocomplete
});
</script>
var autocomplete variable needs to be used below for the source: (I didn't realise this before)
still having difficulties as the autocomplete is still not working but I think I'm making progress

Related

Searchkick, typeahead.js and Elasticsearch issues

I am trying to implement an autocomplete search bar that searches posts in my Rails 4.2 app. I have elasticsearch installed, searchkick gem, and typeahead.js for autocomplete on the frontend.
To that end, I followed a tutorial (https://shellycloud.com/blog/2013/10/adding-search-and-autocomplete-to-a-rails-app-with-elasticsearch) and have the following things set up:
In my routes.rb I have set up the collection route like so:
resources :posts do
collection do
get :autocomplete
end
resources :attachments
end
In my Post.rb model:
searchkick autocomplete: ['title', 'excerpt']
In my posts_controller.rb
def index
if params[:query].present?
#posts = Post.search(params[:query])
end
end
def autocomplete
render json: Post.search(params[:query], autocomplete: true, limit: 10).map do |post|
{title:post.title, excerpt: post.excerpt}
end
end
In my posts.js
$(document).ready(function() {
var posts = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: "/posts/autocomplete?query=%QUERY",
remote: {
url: "/posts/autocomplete?query=%QUERY"
}
});
$('#query').typeahead({
name: 'posts',
display: 'title',
source: posts
});
})
Finally, in my view index.html.haml I have:
=form_tag(posts_path, method: :get) do
=text_field_tag(:query, params[:query], autocomplete: 'off', class: 'typeahead')
%input{:type=>"submit", :value=> t('search')}
When I type something in the search box, I get this:
GET XHR http://localhost/posts/autocomplete [HTTP/1.1 400 Bad Request 177ms]
I can't tell what is going on. For what its worth, I have tried hitting http://localhost/posts/autocomplete.json?query=something and it works in that it will return the matching result. I have also tried replacing autocomplete with autocomplete.json in the JS I posted above.
Still, I get the same error in the console (with autocomplete.json in place of autocomplete)
Can you help?

url_for and remote: true

I've got an issue with Rails. I want a select form which render a partial view when clicked. I want the partial views to be effective without refreshing the whole document (Ajax) but it seems that remote: true does not work for me...
What I have currently and refresh the whole document:
= form_tag url_for(action: :index), method: 'get' do
//Storing some informations in html fields for further use
- shops_array = #shops.collect.with_index { |shop, i| [shop.title, shop.id, {'address' => [shop.address, shop.zipcode, shop.city].join(" ")}, {'id' => 'shop'+String(i)}] }
= select_tag :shop_id, options_for_select(shops_array, #shop_id), prompt: "Your shop :", data: {submit_on_change: true}
What I've tried :
= form_tag url_for(action: :index), remote: true, method: 'get' do
//Storing some informations in html fields for further use
- shops_array = #shops.collect.with_index { |shop, i| [shop.title, shop.id, {'address' => [shop.address, shop.zipcode, shop.city].join(" ")}, {'id' => 'shop'+String(i)}] }
= select_tag :shop_id, options_for_select(shops_array, #shop_id), prompt: "Your shop :", data: {submit_on_change: true}
How do I apply the remote: true option with url_for? If not possible, I could use some help finding a workaround.
As per the docs you can specify the remote option to the form_tag method, like so:
form_tag(url_for(controller: :charmander, action: :ember), remote: true, method: :get, class: 'pokemon-form') do
# form logic
end
However, this will only trigger the ajax:success event when the whole form is submitted.
From your question it seems to me that you want to re-render some partial when your select value changes. I'm not sure how you use the submit_on_change data attribute, but if I'm correct in assuming that you make it submit the entire form when the select vaule changes then all you need to do is listen for the ajax:success event for the entire form, like so:
$('.pokemon-form').on('ajax:success', function (event, data, status, xhr) {
// Do something with data, which would be the rendered partial returned
// from the server call.
});
Ended up using an onclick and a JavaScript function. Though it may not be the best solution nor the prettier, it works like a charm for me.
= select_tag :shop_id, options_for_select(shops_array, #shop_id), prompt: "Your shop :", onclick: 'ajaxSelectedShop('+String(8)+')'

Ruby on Rails Previous Searches Display when Typing in Search Bar

Ok so what I have is a library database system where a user can type in books in a search bar and then the books that match their search are displayed when they click enter. What I want to do is get it so that when the user types "a" into the search bar all records will be displayed in a drop down below the menu with the letter "a", and then when they input the next letter "l" all records with "al" will be displayed in a drop down, and then "ali", and so on. I do not have much knowledge of gems, but want to learn. Is their a way I can do this? I am using rails 4.0.1 and at the minute have a fuzzy search method.
Or if you want to roll your own...
This is the gist of it, might require a bit of tweaking:
routes.rb
get '/search' => 'search#autocomplete', as: :search_autocomplete
search_controller.rb
def autocomplete
search_term = params[:search]
#results = YourModel.where( "your_field LIKE search_term" )
respond_to do |format|
format.json #results
end
end
your_view.html.erb
<%= form_tag( search_autocomplete_path, method: "get" ) do %>
<%= text_field_tag( :search, params[:search], placeholder: 'Enter a search term...', :id => 'autocomplete_search' ) %>
<%= submit_tag( "Go" ) %>
<div id="autocomplete_search_results">
<% end %>
some_coffeescript_file.js.coffee
$( document ).ready ->
$.ajax '/search',
type: 'GET'
parameters: $( "#autocomplete_search" ).val()
dataType: 'json'
success: ( data ) ->
# here you'll have to append the results to whichever div/container you have in place
$( '#autocomplete_search_results' ).append "#{ data }"
Checkout Twitter typeahead.js jquery plugin https://github.com/yourabi/twitter-typeahead-rails.

Creating associated records inside parent's new view/form. Associated records are done through Ajax/jquery-file-upload

So i have a "Style" model, which has_many "Images". In the new style page, i want to be able to add images all in the same page. However, since we did not create the style record yet, it does not have an ID. So how would i go about collecting all the images that are built/uploaded by ajax, and updating their style_id attributes after saving the new Style?
I am using jquery-file-upload to add images to the Image table, and upload my files to Rackspace cloud files. All of this is working, i just am not able to set style_id other than just manually setting it.
Is there a best practices/proper way to go about this, since jquery-file-upload uses Ajax, i am not sure the best approach to saving my parent. I am thinking the best approach would be to use ajax to submit the parent form as well, rather than use the dom, like adding hidden inputs/elements to the parent form?
thank you
Style: form partial
<%= form_for #style, :html => {:multipart => true} do |f| %>
//NORMAL RAILS FORM CODE HERE
<% end %>
<%= form_for Image.new, :html => { :multipart => true, :id => "fileupload" } do |f| %>
<%= f.file_field :file, :multiple => true %>
<% end %>
<script>
$(function () {
$('#fileupload').fileupload({
dataType: 'json',
url: '<%= style_images_path(1) %>',
add: function (e, data) {
data.context = $('<div class="img uploading"/>').text('Uploading...').appendTo(document.body);
data.submit();
},
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<img src="'+file.thumbnail_url+'">').appendTo(data.context);
});
console.log(data);
data.context.append("<span>done</span>");
},
option: {
autoUpload: true,
}
});
});
</script>
In the above code you can see I set the style_id manually... style_images_path(1)
-
MY PROPOSED SOLUTION:
My idea is to pass an array of the id's of all children (images) to the create/update method of style. and in the controller, update all the style_id attributes of the matching id's to the newly created style's id... I think this is possible?
That's tricky.
You can instantiate the #style from within the new method of your controller.
Instead than:
#style = Style.new
put
#style = Style.create
If validation complains, than use the following workaround:
#style = Style.new
#style.save :validate => false
Now in your view you have a fully qualified #style with an ID you can pass over to js:
url: '<%= style_images_path(#style) %>',
At this point when (if ever) the user clicks on the form button, the control reaches the update method, not the create (this is because the _form.html.erb automatically changes the HTTP verb from POST (create) to PUT (update). So make sure your controller is ready for this).
You should also consider some sort of "garbage collection" in case Style objects get created and never saved. This might not be straight forward because the user can always close the window and you are left with an incomplete Style in the db. Maybe some js function that triggers at window close and calls a garbage_collect_if_not_saved(#style) method? Still not perfect (what if the browser hangs?) but better than nothig. Otherwise a good-old cron based script that cleans the db up.
Cheers,

Bootstrap Typeahead in Rails

I'm using Bootstrap-sass and formtastic in my Rails application, and I'm not sure why the bootstrap-typeahead feature isn't working.
Form partial:
<%= f.input :tag, :input_html => { :'data-provide' => "typeahead", :'data-source' => '["hello", "hellow", "heaven", "helo", "herr"]' } %>
application.js manifest:
//= require bootstrap-typeahead //typeahead is correctly loaded, checked with firebug
result HTML source code:
<input :data-minLength="2" data-provide="typeahead" data-source="["hello", "hellow", "heaven", "helo", "herr"]"
In the end, I will need to customize typeahead to get the performance I want, but even this simple javascript isn't working for some reason. I cant find anything wrong with the code. Could anyone help me?
UPDATE:
I tried it in the javascript way as follows:
<script> //call typeahead
$(function() {
$('input#type_ahead').typeahead({
'source' : ["hello", "heaven", "heythere"]
});
})
</script>
<%= f.input :tag_list, :input_html => { :id => "type_ahead" }, %> //input tag
And still it seems like typeahead fails to work. i.e) typing in "he" does not get me a dropdown of those three items in the above array. Does anyone have an idea?
I think you need to set html_safe for:
{ :'data-source' => '["hello", "hellow", "heaven", "helo", "herr"]'.html_safe }
Have you called the typeahead() method when your page loads? You need to do something like this;
$(function() {
$('input').typeahead();
})
You'll need to assign a class or id to your input to bind the typeahead to it specifically (Rails probably has assigned an id to it automatically, I'm presuming you've omitted it specifically)
$(function() {
$('input#my-tag-field-with-a-cool-typeahead').typeahead();
})
edit:
The following worked for me. It'll take a bit of modification to the rails part to fit your situation, but this definitely works.
<script>
$(function() {
$('input#type_ahead').typeahead()
}
</script>
<%= text_field_tag :test_type, '', data: {provide: 'typeahead', source: "['hello','heythere','heaven']"} %>

Resources