Completely stuck on getting a basic a JSON select2 working - ruby-on-rails

I am close on this but being a javascript / json newbie I am sure I am missing something obvious here. The JSON select2 example is a bit over the top so I am lost trying to convert it to my simple implementation.
I have a Model (City) with a big list of cities with other associated data etc. I am aiming for a basic typeahead input where the city / province gets displayed and the id from the City model gets passed in the form.
Here is my JS:
$(document).ready ->
$('#e6').select2
placeholder: 'Select a City...'
minimumInputLength: 3
ajax:
url: '/cities.json'
dataType: 'json'
quietMillis: 250
data: (query) ->
{ query: query }
results: (data) ->
{ results: $.map(data, (item) ->
{
id: item.id
text: item.name + ', ' + item.province
}
) }
# formatResult: formatResult
# formatSelection: formatSelection
# initSelection: initSelection
cache: true
return
My JSON is triggering as I can confirm from my logs. For example http://localhost:3000/cities.json?query=cal yields:
[[1714,"Calais","AB"],[1716,"Calder","SK"],[1717,"Calderbank","SK"],[1731,"Calgary","AB"],[1738,"Calling Lake","AB"],[1739,"Callingwood North","AB"],[1740,"Callingwood South","AB"],[1743,"Calmar","AB"]]
Now right off the bat I think this is my issue. Should this not be:
[ id: 1731, name: "Calgary", province: "AB"], etc..?
Here is my controller:
respond_to do |format|
format.json {
render json: #cities
}
end
Looking ahead I can see that I could probably have my controller spit out the 'text' values in the "City, Province" format I want too.
So my question(s) are: am I missing something obvious in my JS and / or do I need to fix my JSON and if so how?

Related

How to respond to ajax call in Rails

I know there are many questions about that already on stackoverflow but none of them has been useful for me. Here is my ajax code:
function update_schedule($task){
var taskId = $task.data("taskId"),
startHour, endHour,
userId = $('#users').val();
if( !$task.hasClass('daily-task')){
startHour = getStartHour($task),
endHour = startHour + (+$task.data('time'));
}
console.log(startHour, endHour)
$.ajax({
url: '/heimdall/update_scheduled_task',
method: 'POST',
data: { "task_id": taskId, "start_hour": startHour, "end_hour": endHour, "user_id": userId },
success: function (){
console.log("SUCESS!!", data['head'])
},
error: function () {
console.log("FAILURE");
},
async: true
});
}
The controller code:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
end
I want to return the id of found or created task. I don't know how to send/receive this information. I've read about respond to but still I don't know how to use it in this case.
You may do render json: ... to return the required info to ajax in JSON format:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
render json: {scheduled_task_id: scheduled_task.id}
end
And in the ajax function's success, use it like:
success: function (data){
var data = JSON.parse(data);
console.log(data["scheduled_task_id"]);
},
First, you need to improve your controller code structure (You can rely on the default scaffold generator methods). Then, you must indicate that your method will respond to json format only, with the answer you want to return, something like this:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
if (scheduled_task && scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id]))
render json: { scheduled_task_id: scheduled_task.id }
else
render json: { error: l18n.t("error.messages.request_error") }
end
end
Then, you must modify the success and failure response methods of the jquery ajax request. Here's an example of how it might look:
$.ajax({
url: "/heimdall/update_scheduled_task",
method: "post",
data: { task_id: taskId, start_hour: startHour, end_hour: endHour, user_id: userId },
success: function(result) {
if (result["scheduled_task_id"] != null) {
console.log("Schedule record => " + result["scheduled_task_id"])
} else {
console.log("Error: " + result["error"])
}
},
error: function() {
console.log("Ajax request error");
},
// async: true => By default JQuery set true to async param.
});
Do not forget that you need to add a rule to access this method in the file config/ruotes.rb, something like this:
post update_scheduled_task, :defaults => { :format => 'json' }
I hope this helped you, regards!
jQuery's success and error callbacks rely on the status code returned by the controller. Make sure that you return the right status code when you are unable to create/read/update the object. On success, simply render a JSON with the id property set. I beleive update_attributes returns true on success:
if scheduled_task && scheduled_task.update_attributes(...)
render json: { id: scheduled_task.id }
else
head :unprocessable_entity
end

getting user input from a form in backend (rails 4) that uses ajax 'get' call when search button is pressed

So, I have a React-Component (search_alerts.es6.jsx) that looks like this
class SearchAlerts extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
this.state = {
alertsArray: this.props.alerts,
zipCode: null,
bloodReq: null,
minCompensation: null,
maxCompensation: null,
requiredTime: null,
location: null,
bloodType: null,
keyWord: null
}
console.log(this.state.alertsArray);
}
........
and the ajax call that is responsible to call the get method looks like this
$.ajax({
data: JSON.stringify(stringit),
type: "GET",
url: `/findalerts/`,
contentType: "application/json",
dataType: "json",
success: function(data) {
console.log("Ajax Passed ");
console.log(data);
this.setState({
alertsArray: data
});
}.bind(this),
error: function(data) {
console.log("Ajax failed: ");
console.log(data);
}.bind(this)
});
in my routes.rb, I'm doing this
get '/findalerts', to: 'alerts#index' #Search Alerts
and in my alerts_controller
def index
#alerts = Alert.search(params[:keyword])
end
for the search, I'm doing this in alert.rb
def self.search(search)
#alerts = Alert.where("ZipCode LIKE :search ", search: "%#{search}%")
#alerts += Alert.where("compensation LIKE :search ", search: "%#{search}%")
#alerts += User.search(search)
end
Now if I pass the query in the url,like this,
http://localhost:3000/findalerts?keyword=117
it is returning for example all the alerts that has the zipcode starts from 117 as json, and the react component is fetching it and displaying it properly.
My question is, how to get the search working when user types the keyword in the search form? I've tried other examples from stack overflow,it didn't work, I know I'm missing something, but don't know what. Thanks!

How to send params from nested forms?

I'm making a POST request from a nested form which is written in reactjs such that it is making an ajax request to create method of the products controller.
React Code:
I have an empty object in the getInitialState like this
getInitialState: function() {
return {
products: [{name: '', price: '', quantity: ''}],
count: 1
};
},
When i submit the form,
handleSubmit: function(e) {
e.preventDefault();
var productsArray = this.state.products;
$.ajax({
data: {
product: productsArray
},
url: '',
type: "POST",
dataType: "json",
success: function ( data ) {
console.log(data);
// this.setState({ comments: data });
}.bind(this)
});
},
the object gets populated and the parameter hash becomes like this
Parameters: {"product"=>{"0"=>{"name"=>"", "price"=>"", "quantity"=>""}}, "shop_id"=>"gulshop"}
So i'm getting
ActiveRecord::UnknownAttributeError (unknown attribute '0' for Product.):
How can i get the parameter hash like this:
Parameters: {"product"=>[{"name"=>"", "price"=>"", "quantity"=>""}], "shop_id"=>"gulshop"}
What can be done for it ?
Your original error 'unknown attribute '0' for Product.' is because the Product class does not have an attribute '0'. I'm not sure where the '0' is coming from as you haven't posted your react code that makes the request.
You can easily make a request from your component using jQuery's .ajax method. e.g
$.ajax({
type: 'POST',
url: '/your_url',
data: {
course: {
name: 'Hello World',
price: 120
}
}
});
You would then have something like the following in your controller..
class ProductController < ApplicationController
def create
#product = Product.create(product_params)
end
private
def product_params
params.require(:product).permit(:name, :price)
end
end

Select2 Find existing or type new always show first option as blank

I added a select2 and it always show first option as blank.
just below the blank option, it always shows what i type in select2 textfield. Any Idea, how i can fix it.
= hidden_field_tag :tags // present in HAML form
// javascript
$('#tags').select2
createSearchChoice: (term, data) ->
if $(data).filter((->
#text.localeCompare(term) == 0
)).length == 0
return {
id: term
text: term
}
return
multiple: false
data: [
{
id: 0
text: 'story'
}
{
id: 1
text: 'bug'
}
{
id: 2
text: 'task'
}`enter code here`
]
Try this .....
Your input field should be like this
%input#tags{:style => "width: 300px", :type => "hidden"}
Your js code should be like this
$("#tags").select2({
placeholder:"Select an option",
createSearchChoice: function(term, data) {
if ($(data).filter((function() {
return this.text.localeCompare(term) === 0;
})).length === 0) {
return {
id: term,
text: term
};
}
},
multiple: false,
data: [
{
id: 0,
text: 'story'
}, {
id: 1,
text: 'bug'
}, {
id: 2,
text: 'task'
}
]
});
You no longer need to use createSearchChoice if you want to do this with Select2, you can just use the tags option. Right now you shouldn't be seeing the blank option, as you aren't passing one to Select2 at all, so this is lacking a bit of information that would explain that issue.
Select2 will automatically hide blank options though if you have a placeholder set, which I would always recommend. So the hacky solution is to set the placeholder option to whatever you want ("Select a few tags..."), but that only works if this is actually a blank option and not just a CSS issue. You can tell if it's a blank option if Select2 highlights it whenever you start searching, as it will automatically highlight the first option.
It would make more sense for this to be a CSS issue, maybe a bit too much padding somewhere, as the tag (or choice from createSearchChoice) should be inserted automatically at the top of the search results. So even if there was a spare blank option sitting around, it would show under the newly created search choice.

Programming language for 'dynamic' search?

I am a complete beginner, so please excuse if this question is maybe phrased incorrectly or I am using the wrong terms for certain things.
I have got an MS Word Document in which there are, say, four-hundred diary entries. The software I want to write enables the user to search for specific terms within one entry. So I might want to have all the diary entries that contain both the words "happy" and "sad". Another option I want to include is to search for all diary entries written between e.g. 2008 and 2012 etc.
I would like the search to be 'dynamic', in the sense that the user might type in one word, and while that word is being typed the results-table already filters all the diary entries. So the table changes while the user is typing the word, according to what is currently being typed in the search box.
Is this possible & what exactly is this feature called? What programming language would you recommend me? I would like to have all of this online, so maybe php or ruby would be useful?
Your choice of Ruby-on-Rails is apt for this issue:
Store each diary entry as a database entry
Search these entries using "full text search" - either on the db, or third party
Return the "live" functionality with JS
MVC
Rails uses MVC programming pattern to give you ability to save into the database. This is important because if you're going to develop in rails, you'll need to keep to the MVC pattern:
Basically, you keep your Diary entries in the database, use the controller to manipulate the data & use the view to show the data:
#app/models/entry.rb
Class Entry < ActiveRecord::Base
#-> stores & brings back the entry data
end
#app/controllers/entries_controller.rb
Class EntriesController < ApplicationController
respond_to :js, :html, :json, only: :search
def index
#entries = Entry.all
end
def search
#entries = Entry.search params[:query]
respond_with #entries
end
end
#config/routes.rb
resources :entries do
collection do
match :search, via [:get, :post]
end
end
--
Full Text Search
When you send the request to your controller, the way you'll handle the search will be with a class method in your Entry model (that's where you get your data from), either referencing -
#app/models/entry.rb
Class Entry < ActiveRecord::Base
def self.search(query)
where("title LIKE '%#{query}%'")
end
end
You can either use full text search for the SQL variant you're using, or use a third party search, like Solr or something
--
"Live" Search
You can get "live" search working with Javascript (example here):
There are a number of tutorials on how to do this online - just Google live search rails or autocomplete rails. However, the principle is basically the same for all implementations:
JS will capture the text entered into a search box
JS sends Ajax request to your Rails controller
Controller sends response to ajax
JS takes response & shows on screen
Notice how this is primarily focused on the JS element? Here's the code we use:
#app/assets/javascripts/application.js
//Livesearch
$(document).ready( function() {
var base_url = window.location.protocol + "//" + window.location.host;
$('#SearchSearch').searchbox({
url: base_url + '/search/',
param: 'search',
dom_id: '#livesearch',
loading_css: '#livesearch_loading'
})
});
#app/assets/javascripts/livesearch.js
// Author: Ryan Heath
// http://rpheath.com
(function($) {
$.searchbox = {}
$.extend(true, $.searchbox, {
settings: {
url: 'search',
param: 'search',
dom_id: '#livesearch',
minChars: 2,
loading_css: '#livesearch_loading',
del_id: '#livesearch_del'
},
loading: function() {
$($.searchbox.settings.loading_css).show()
},
idle: function() {
$($.searchbox.settings.loading_css).hide()
},
start: function() {
$.searchbox.loading()
$(document).trigger('before.searchbox')
},
stop: function() {
$.searchbox.idle()
$(document).trigger('after.searchbox')
},
kill: function() {
$($.searchbox.settings.dom_id).fadeOut(50)
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.del_id).fadeOut(100)
},
reset: function() {
$($.searchbox.settings.dom_id).html('')
$($.searchbox.settings.dom_id).fadeOut(50)
$('#SearchSearch').val('')
$($.searchbox.settings.del_id).fadeOut(100)
},
process: function(terms) {
if(/\S/.test(terms)) {
$.ajax({
type: 'GET',
url: $.searchbox.settings.url,
data: {search: terms.trim()},
complete: function(data) {
$($.searchbox.settings.del_id).fadeIn(50)
$($.searchbox.settings.dom_id).html(data.responseText)
if (!$($.searchbox.settings.dom_id).is(':empty')) {
$($.searchbox.settings.dom_id).fadeIn(100)
}
$.searchbox.stop();
}
});
return false;
}else{
$.searchbox.kill();
}
}
});
$.fn.searchbox = function(config) {
var settings = $.extend(true, $.searchbox.settings, config || {})
$(document).trigger('init.searchbox')
$.searchbox.idle()
return this.each(function() {
var $input = $(this)
$input
.keyup(function() {
if ($input.val() != this.previousValue) {
if(/\S/.test($input.val().trim()) && $input.val().trim().length > $.searchbox.settings.minChars){
$.searchbox.start()
$.searchbox.process($input.val())
}else{
$.searchbox.kill()
}
this.previousValue = $input.val()
}
})
})
}
})(jQuery);

Resources