I'm developing autocomplete for a particular form in my rails app; for this purpose I'm using typeahead.js with custom controller method. So far, it works but I need using those values within the form again so that I can press the submit button and the form will be posted and processed by rails normally. How can I do this? Here's the code right now
.page-header
%h1
= #org.name
%small= t('.title')
= form_for #org_admin, |
url: organization_organization_admins_path(#organization) do |f|
.form-group
= f.label t('.user')
= f.hidden_field :user, id: 'user_id'
%input.typeahead{ :type => "text", :autocomplete => "off"}
= f.submit t('.submit'), class: 'btn btn-primary'
= link_to t('.back'), organization_organization_admins_path(#organization)
:javascript
$(document).ready(function() {
$('input.typeahead').typeahead({
name: 'names',
remote: "#{search_organization_organization_admins_path(#organization)}?term=%QUERY",
engine: Hogan,
template: '<p><strong>{{username}}</strong></p>',
limit: 10
}).on('typeahead:selected', function(e, data){
$('#user_id').value = data.id
});
});
So, I would like to populate the :user attribute in the form with the json object returned by the controller
Nevermind, I figured out... the above code is in the right path, except for the call to this line
$('#user_id').value = data.id
Since I'm using jQuery to select the hidden element I had to use the jQuery val function instead
$('#user_id').val(data.id)
Related
I have a simple has_many and belongs_to relationship in my rails app. I'm using simple_form and want to dynamically change the dropdown options based on the value chosen by the user.
Models
class Processor < ApplicationRecord
has_many :processor_bank_accounts
end
class ProcessorBankAccount < ApplicationRecord
belongs_to :processor
end
Form inputs
<%= simple_form_for [#customer, #transaction] do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :status, :collection => ["payment request"], include_blank: false %>
<%= f.input :processor, collection: #processors ,label_method: :name,value_method: :id,label: "Processor" , include_blank: false %>
<%= f.input :processor_bank_account, collection: #bank_accounts , label_method: :bank_name, value_method: :id, label: "Processor Bank Account" , include_blank: true %>
<%= f.input :tcurrency, collection: #currencies, include_blank: false, label: 'currency' %>
<%= f.input :amount, as: :decimal, label: 'amount' %>
</div>
<div class="form-actions text-center">
<%= f.button :submit, "Add transaction", class: "form-button"%>
</div>
<% end %>
So essentially, I need the processor_bank_account dropdown to populate based on the processor chosen by the user. In the console, this would just be: ProcessorBankAccount.where(processor: processor).
Need to load options using JS and think I need to use JSON but not sure where to go from here
One way to do this would be to use jQuery to make an AJAX call to a controller action and then let Rails handle the rest through an erb template.
So on your page, with the form, invoke the action via AJAX using something like:
<script>
$(document).ready(function() {
$('#processor_id').on('change', function() {
$.ajax({
url: '/transactions/get_processor_bank_accounts',
type: 'GET',
data: {
processor_id: this.value
},
dataType: 'script',
error: function() {
alert('An error occurred retrieving bank accounts for the selected processor.');
}
});
});
});
</script>
NB, #processor_id is the id for your dropdown control.
Next, instantiate the bank accounts within your action in your controller:
def get_processor_bank_accounts
#processor_bank_accounts = ProcessorBankAccount.where(processor_id: params[:processor_id])
end
And finally simply create a view that will be responsible for repopulating your dropdown:
$select_list = $('#processor_id');
$select_list.empty();
<% #processor_bank_accounts.each do |pba| %>
$select_list.append($('<option value="<%= pba.id %>"><%= pba.name %></option>'));
<% end %>
I came up with the following solution:
1) Add a new method to my processors controller to render the inputs for the second (dynamic) dropdown in JSON format:
def processor_bank_accounts
render json: #processor.processor_bank_accounts.each do |bap|
{ id: bap.id, name: bap.name }
end
end
2) Assign this new function to a new route in config/routes:
get 'api/bankaccounts', to: 'processors#processor_bank_accounts', as: 'bankaccounts'
3) Create a JS function to access the route with the id of the processor selected in the first dropdown and populate the second dropdown with items from the JSON array:
// select first dropdown
const processor = document.getElementById("transaction_processor");
// select second dropdown
const bapSelect = document.getElementById("transaction_processor_bank_account");
function update_baps(processor_id) {
const url = `INSERTWEBURLHERE/api/bankaccounts?id=${processor_id}`;
fetch(url)
.then(response => response.json())
.then((data) => {
bapSelect.innerHTML = ""; // clear second dropdown
data.forEach((bap) => { // go through all the BAPs
const elem = `<option value="${bap.id}">${bap.bank_name}</option>`; // create option elements to insert into the second dropdown, bank_name is the chosen label method in the form
bapSelect.insertAdjacentHTML("beforeend", elem); // insert options into the dropdown
});
});
}
4) Trigger the JS when the first dropdown field is changed:
processor.addEventListener('change', function () {
update_baps(parseInt(processor.value));
});
You should add an id to the selects so you can identify them form the script.
$('select#processor').on('change', function() {
var processor_id = this.value;
var processor_bank_account = $('select#processor_bank_account')
$.ajax({
type: "GET",
url: <%= your_path %> ,
data: { processor_id: processor_id },
success: function(data, textStatus, jqXHR){
processor_bank_account.empty();
var option = new Option(data.bank_name, data.id, false, false);
processor_bank_account.append(option);
},
error: function(jqXHR, textStatus, errorThrown){...}
})
});
I have successfully integrated Jquery-Autocomplete into my rails app, currently users can search more than 5000+ airports and so far the integration works exactly as desired, however...
Any Airport has multiple columns, currently i am returning the airport name but would like to instead show
**
airport_name - airport_city - airport_iata - airport_icao
**
I have so far used Ryan Bates Railscast to get me started
Episode #102
But amended and tweaked thanks to other resources since his video. Sorry i can't reference them right now but will update my question once i've found links to their instructions.
Either way Autocomplete does work as designed and i have managed to populate a hidden field but i would really like to display more than just the Airport name when searching. I will continue to only save the airport ID.
Any help is appreciated, here is my code.
_form.html.erb
<div class="field">
<%= f.label :departure_airport %><br>
<%= text_field_tag nil, nil, :id => 'claim_departure_airport_name', data: {autocomplete_source: Airport.order(:name).map { |t| { :label => t.name, :value => t.id } } }, class: "form-control" %>
</div>
<%= f.hidden_field :d_airport_id, id: 'd_airport_id' %>
<%= f.hidden_field :a_airport_id, id: 'a_airport_id' %>
claims.coffee
jQuery ->
$('#claim_departure_airport_name').autocomplete
source: $('#claim_departure_airport_name').data('autocomplete-source')
select: (event, ui) ->
# necessary to prevent autocomplete from filling in
# with the value instead of the label
event.preventDefault()
$(this).val ui.item.label
$('#d_airport_id').val ui.item.value
$('#claim_arrival_airport_name').autocomplete
source: $('#claim_arrival_airport_name').data('autocomplete-source')
select: (event, ui) ->
# necessary to prevent autocomplete from filling in
# with the value instead of the label
event.preventDefault()
$(this).val ui.item.label
$('#a_airport_id').val ui.item.value
As you can see i am directly reaching the model data without the need for a dedicated controller although i realise this would be much more intelligent than my current solution as i wish to roll this out in other areas of the platform.
I don't fully understand everything that is happening in the jquery code in my coffee file this was obtained from another source although i forget who to give credit. As far as i know it's taking the ID of the airport and populating the hidden field?
If you can spot how to show other airport database columns to the user that would be great.
Thanks
edit
Also wish to restrict autocomplete from loading the data source into html due to seriously long page load times. A screen shot of what i mean is below.
screenshot of data loading
You can modify the autocomplete source to
<%= text_field_tag nil, nil, id: 'claim_departure_airport_name', data: { autocomplete_source: Airport.select(:id, :name, :city, :iata, :icao).order(:name).map { |t| { label: "#{t.name}-#{t.city}-#{t.iata}-#{t.icao}", value: t.id } } }, class: "form-control" %>
To refactor this a bit, you can move the active record query into a helper
def airport_autocomplete_data
Airport.select(:id, :name, :city, :iata, :icao).order(:name).map { |t| { label: "#{t.name}-#{t.city}-#{t.iata}-#{t.icao}", value: t.id } }
end
and your text field becomes
<%= text_field_tag nil, nil, id: 'claim_departure_airport_name', data: { autocomplete_source: airport_autocomplete_data }, class: "form-control" %>
The main point is here data: {autocomplete_source: Airport.order(:name).map { |t| { :label => t.name, :value => t.id } } }
It builds an array of objects, like [{'label': 'BKK', value: 1}, {'label': 'HAM', value: 2}]
So you either need to add something to the label key, or maybe add a different key and use it later in the select callback.
If you are using rails4-autocomlete Gem. You can easily override from controller.
def autocomplete_profile
term = params[:term]
profiles = Profile.where(
'LOWER(profiles.first_name) LIKE ? OR LOWER(profiles.last_name) LIKE ?',
"%#{term}%", "%#{term}%"
).order(:id).all
render :json => profiles.map { |profile| {:id => profile.id, :label => profile.id, :value => profile.id} }
end
I have a link_to with remote: set to true. What I want to do is add an extra attribute with the request so I tried it with this:
<script>
$(document).ready(
function(){
$("#part_btn").bind("ajax:beforeSend",
function(event, xhr, settings, data){
settings.data += "&user_id=5"
})
})
</script>
Now I don't even know if this is possible because the user_id is never sent to the controller and when I log the setting.data I get null&user_id=5.
If it's not possible like this, is there any other way to do it?
EDIT:
I didn't add an extra parameter to the link_to because the user_id actually comes from a select tag and I wanted to add the selected value as extra data like this:
settings.data += ("&user_id="+$("#register_user_id option:selected").val());
this is what my link_to looks like:
<%= text_field_tag :part_id, nil, :class => 'text_field', data: {source: autocomplete_parts_for_registers_registers_path} %>
<%= link_to('toevoegen', '#!', id: 'part_btn', class: 'btn btn-small btn-success addbtn', style: "margin-top: 5px", data: {source: add_part_registers_path}, remote: true) %>
I think the best and easiest way to update link_to when user change drop-down by javascript or an extra ajax call
JS
$("#user_select").change(function() {
user_id = $(this).val();
$("#user_link").data("params") = "user_id=" + user_id;
})
Extra Ajax
send user_id to server with user_id and make a method_name.js.erb and update link from there
like:
link_to "link", method_path(user_id: params[:user_id]), remote: true
Rather than taking the javascript approach, consider send the params with the route in the link_to:
link_to "My link", some_path(user_id: 5), remote: true, method: :post
This will add :user_id with a value of 5 to the params in the controller for some_path.
Additionally, you can use the data: attribute on the link to to add any params you want to XHR request.
link_to "My link", id: "user_link", some_path, remote: true, method: :post, data: { params: { user_id: 123 }.to_param }
If you need to change the value dynamically:
$("#user_select").change(function() {
user_id = $(this).val();
$("#user_link").data("params", "user_id=" + user_id);
})
In fact, it looks like this is exactly what Rails does to send AJAX values from jquery_ujs.
Obviously, be sure to check the value of user_id server-side.
I have migrated a :completed boolean, I then added it to my form like so (simple_form)
<%= f.input :completed, as: :boolean %>
This allows me to update completed, not completed in the edit view.
How can I edit the :completed method in my index view? I want to be able to update complete or not complete on the fly.
EDIT:
My Form
<%= f.input :completed, as: :boolean, input_html: { class: 'completed', id: #todo.id } %>
My View (label has to be after input as per materializecss)
<%= check_box_tag todo.id, 'completed', todo.completed?, class: 'completed' %>
<label class="strikethrough">
COMPLETE
</label>
The JS
// This could be other events as well, so add/change/remove the event trigger as needed
$(document).on('click', '.completed', function(){
updateCompleted($(this));
});
function updateCompleted($el) {
var id = $el.attr('id'),
val = $el.is(':checked');
$.PATCH({
url: '/todos/'+id,
data: { todo: { completed: val } },
success: function(){ whatever },
error: function() { whatever }
});
}
My Controller
params.require(:todo).permit(:title, :item, :image, :completed)
You are going to require some AJAX here. In my experience, doing this is a bit outside of the "rails way", but totally possible.
First, make sure you have the ID of the object somewhere in the dom:
<%= f.input :completed, as: :boolean, input_html: { class: 'completed', id: myobject.id %>
I used the simple_form syntax since that's what it appears you are using for your form builder. If not, comment back and I'll adjust the tag.
Secondly, add a jquery event listener:
// This could be other events as well, so add/change/remove the event trigger as needed
$(document).on('click', '.completed', function(){
updateCompleted($(this));
});
function updateCompleted($el) {
var id = $el.attr('id'),
val = $el.is(':checked');
$.PATCH({
url: '/your_endpoint/'+id,
data: { your_object_name: { completed: val } },
success: function(){ whatever },
error: function() { whatever }
});
}
And in your controller:
private
def your_object_params
params.require(:your_object).permit(..., :completed)
end
That's basically it.
Basically you will want to make a post to the update action from the index action. I would use JavaScript but you can also use a hard refresh if you want to stick with rails. Here is a good intro if you are not familiar with using JS in your rails app. http://railscasts.com/episodes/324-passing-data-to-javascript.
Environment Ruby 2.0.0, Rails 4.0.3, Windows 8.1, jQuery
EDIT: Just FYI, I was discussing the issue the other day and I was told that the common method to solve this problem would be just to pass the record ID. Certainly, I would recommend that solution in a general case. In this case, the record is being created and has not yet been stored, so it has no ID and cannot have one until all required fields are completed.
I need to pass the object instance from the view through jQuery to the controller so that the controller use it to render a partial using dependent selects. This process was generally working even though I was just passing a string that named the object. But, now I have to implement strong parameters to permit updates and that requires the actual instance and not just the string name of the instance.
In jQuery, I use the following to obtain the instance but it is obviously wrong because it only gets me the string name of the instance. I assume it needs to be serialized perhaps? But, I can only get the string name which cannot be serialized.
var car = $('select#car_year_id').attr("car");
The basic question is, how do I retrieve the actual instance of car within jQuery? Alternatively, I guess, the question would be that, given the string name of an instance within Ruby on Rails, how do I address the actual instance? Either one would probably suffice. Of course, other alternatives will be welcomed. Thanks.
The form is:
<div class="span8">
<%= simple_form_for #car,
defaults: {label: false},
html: {class: 'form-vertical'},
wrapper: :vertical_form,
wrapper_mappings: {
check_boxes: :vertical_radio_and_checkboxes,
radio_buttons: :vertical_radio_and_checkboxes,
file: :vertical_file_input,
boolean: :vertical_boolean
} do |f| %>
<%= f.input(:stock_number, {input_html: {form: 'new_car', car: #car}, autocomplete: :off, placeholder: 'Stock number?'}) %>
<%= f.input(:year_id, {input_html: {form: 'new_car', car: #car}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
<%= render partial: "makes", locals: {form: 'new_car', car: #car} %>
<%= render partial: "models", locals: {form: 'new_car', car: #car} %>
<input type="submit" form="new_car" value="Create Car" class="btn btn-default btn btn-primary">
<% end %>
</div>
The "makes" partial is:
<%= simple_form_for car,
defaults: {label: false},
remote: true do |f| %>
<% makes ||= "" %>
<% if !makes.blank? %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: makes.collect { |s| [s.make, s.id] }, prompt: "Make?"} %>
<% else %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: [], prompt: "Make?"} %>
<% end %>
<% end %>
The jQuery is:
$(document).ready(function () {
// when the #year field changes
$("#car_year_id").change(function () {
// make a GET call and replace the content
var year = $('select#car_year_id :selected').val();
if (year == "") year = "invalid";
var form = $('select#car_year_id').attr("form");
if (form == "") form = "invalid";
var car = $('select#car_year_id').attr("car");
if (car == "") car = "invalid";
$.post('/cars/make_list/',
{
form: form,
year: year,
car: car
},
function (data) {
$("#car_make_id").html(data);
});
return false;
});
});
The controller action is:
def make_list
makes = params[:year].blank? ? "" : Make.where(year_id: params[:year]).order(:make)
render partial: "makes", locals: { car: params[:car], form: params[:form], makes: makes }
end
I found the answer! So excited!
There is a new HTML construct that allows you to using an arbitrary attribute to an HTML element as long as the name is preceded by "data-". For example:
<%= f.input(:year_id, {input_html: {form: 'new_car', data-car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
This is problematic in Rails, because Rails doesn't like hyphens in symbols. However, there is an optional helper using the data: symbol to pass a hash as in:
<%= f.input(:year_id, {input_html: {form: 'new_car', data: { car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
See: Best way to use html5 data attributes with rails content_tag helper?
Then, within JavaScript, you can use the dataset property to retrieve a DOMStringMap object as follows:
var element = document.getElementById('car_year_id');
var car = element.dataset.car;
See: HTML5 Custom Data Attributes (data-*)
This returns car as a hash object, which is really just what I needed!
Overall reference that helped a lot: Rails 3 Remote Links and Forms: A Definitive Guide
Just for completeness, I used to following code to convert the hash into an object back in the controller:
car_hash = params[:car].gsub!(/":/, '" => ')
null = nil
#car = Car.new(eval(car_hash))