how to process form_with using GET request as XHR - ruby-on-rails

I'm working on a Rails 6 app, and want to update page view based on a dropdown value selected using XHR. Dropdown must use GET method coz I am calling index action.
I am using form_with which by default uses remote: true.
I am not using local: true.
I tried onchange: "Rails.fire(this.form, 'submit')" - this does send XHR request and receives a response but does not update view.
I tried onchange: "this.form.submit();" - this does a full page reload not utilizing XHR.
Code from app/views/users/index.html.erb
<%= form_with url: station_users_path(station_id: Current.user.home_station), method: :get do |form| %>
<%= form.select :user_status, options_for_select( { "Active users" => "unlocked", "Inactive users" => "locked"}, #user_status ), {}, { :onchange => "Rails.fire(this.form, 'submit')" } %>
<% end %>
Code from app/controllers/users_controller.rb
def index
#user_status = params[:user_status] || "unlocked"
#users = #station.users.send(#user_status) || []
#user_status == "unlocked" ? seperate_managers_from_users : #managers = []
end

In onchange just write one get_station_users() function. Inside that you can use ajax calling.
In forms
<%= form_with url: station_users_path(station_id: Current.user.home_station), id: “form_id”, method: :get do |form| %>
<%= form.select :user_status, options_for_select( { "Active users" => "unlocked", "Inactive users" => "locked"}, #user_status ), {}, { :onchange => "get_station_users()" } %>
<% end %>
Add Script
function get_station_users(){
$.ajax({
type: "GET",
url: "/station_users",
data: { $('#form_id').serialize(), },
dataType: 'script'
});
}
Your response will be as JS. So you can use index.js.erb

Related

Using js.erb in rails

In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.
I've tried to use respond_to in controller as below,
registrations-controller.rb
def new
super
#cities = CS.get(:us,params[:state])
respond_to do |format|
format.js { render '/new.js.erb' }# layout: false }
format.html
end
end
new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
<div class="signup-input-container">
<div class="field">
<%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
</div>
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
</div>
<div class="field">
<%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %>
</div>
</div>
<% end %>
new.js.erb
var city = document.getElementById("city-picker");
while (city.firstChild) city.removeChild(city.firstChild);
var placeholder = document.createElement("option");
placeholder.text = "Choose a city";
placeholder.value = "";
city.appendChild(placeholder);
<% #cities.each do |c| %>
city.options[city.options.length] = new Option('<%= c %>');
<% end %>
main.js
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
$.ajax({
url: "/states?state=" + state.value,
type: "GET"
})
})
I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?
To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.
To setup a separate controller you can do it by:
# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'
# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
# GET
def index
#cities = CS.get(:us, params[:state_id])
respond_to do |f|
f.json { render json: #cities }
end
end
end
I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.
// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks,
$(document).on('change', '#state-picker', function(){
$.getJSON("/states/" + $(this).value() + "/cities", function(data){
// using a fragment avoids updating the DOM for every iteration.
var $frag = $('<select>');
$.each(data, function(city){
$frag.append$('<option>' + data + '</option>');
});
$('#city-picker').empty()
.append($('frag').children('option'));
});
});

How to display a selection based on user input using ajax and jquery

Think of the below as a bike rental. Someone fills out a form and gets a bike assigned to them which they can rent and borrow for a certain amount of time.
The problem I am having is I am trying to show the person who wants to rent the bikes what bikes are available before they submit the form. Below is my attempt using ajax. I have no errors but also my select is not updating.
request controller methods below
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
create method below (with a temporary workaround, that reloads the form with a warning about availability.)
def create
#request = Request.new(request_params)
available_bikes = #request.new_request(current_user.id)
if (available_bikes >= #request.number_of_bikes_wanted) && #request.save
redirect_to root_path
else
flash[:warning] = "You have requested more bikes than available. There are only #{available_bikes} bikes available"
redirect_to new_request_url
end
end
params in request controller
def request_params
params.require(:request).permit(:Borrow_time, :Borrow_date,
:Return_date, :Return_time,
:number_of_bikes_wanted, bike_ids: [])
end
new.html.erb view
<div class="form" align = "center">
<%= render 'form.js.erb' %>
</div>
_form.js.erb below
<script type="text/javascript">
$(document).ready(function() {
$('.my-date').on('change', function() {
var data = {}
$('.my-date').each(function() {
if($(this).val()) {
data[$(this).attr("id")] = $(this).val();
}
});
if(Object.keys(data).length > 1) {
$.ajax({
type: "POST",
url: <%= new_request_path %>,
data: data
});
}
});
});
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
</script>
<div class="block-it" align=center>
<br>
<%= form_for #new_request do |request| %>
<%= request.label :Borrow_date, 'Borrow on' %>
<%= request.date_field :Borrow_date, id: 'Borrow_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Borrow_time, 'Borrow at' %>
<%= request.time_field :Borrow_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<%= request.label :Return_date, 'Return On' %>
<%= request.date_field :Return_date, id: 'Return_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Return_time, 'Return at' %>
<%= request.time_field :Return_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<br><br>
<%= request.label :NumberOfBikesWanted, 'Number of bikes' %>
<%= request.select :number_of_bikes_wanted, %w(select_bike), :required => true %>
<br>
<%= request.submit 'Submit' %>
<%= request.submit 'Reset', :type => 'reset' %>
<% end %>
<br>
</div>
There are a two main problems with your code:
Controller
Use a different action to set the endpoint that you will call with ajax, so instead of this:
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
Try this:
def bikes
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
def new
#new_request = Request.new
end
If you want to keep REST routes, then create a new controller and use the index action within that controller.
Form
This code:
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
doesn't belong here, it must be deleted from your file and instead put it on a new file called bikes.js.erb; also rename your form to _form.html.erb.
And update your ajax call to use your new route:
$.ajax({
type: "POST",
url: <%= bikes_path %>,
data: data
});
What you want to setup is a new endpoint but instead of returning html, it will return a js. But you must treat it as an independent action, just as any other action in rails. The only difference is how you call that action (ajax) and how you respond to it (js).

Ajax call on a <select> in a formtastic form

I need in my app to select from a list of provider a provider, and then, via ajax, I could see below a list of categories, that belongs to a specific provider. I have a form in activeadmin:
<%= semantic_form_for [:admin, #game], builder: ActiveAdmin::FormBuilder do |f| %>
<%= f.semantic_errors :state %>
<%= f.inputs do %>
<%= f.input :categorization_id, label: 'Provider', as: :select,
collection: Provider.all.map { |provider| ["#{provider.name}", provider.id] },
input_html: { class: (:provider_select), 'data-url': category_select_path(provider: 4) } %>
<%= f.input :categorization_id, label: 'Category',input_html: { class: ('category_dropdown') }, as: :select,
collection: Category.all.map { |category| ["#{category.name}", category.id]}%>
...
<% end %>
<%= f.actions %>
<% end %>
In activeadmin controller I have:
controller do
def ajax_call
#provider = Provider.find(params[:provider])
#categories = #provider.categories
respond_to do |format|
format.json { render json: #categories }
end
end
end
JS:
$(document).on('ready page:load', function () {
$('.select.input.optional').last().addClass('hidden_row');
$('#game_categorization_id').change(function () {
var id_value = this.value;
$('.hidden_row').removeClass('hidden_row');
$.ajax({
type: 'GET',
url: '/admin/games/category_select'
// data: id_value
})
});
});
And the routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'
I don't have an idea, how can I pass providers id from collection into url. Currently, I have there category_select_path(provider: 4), but actually it has to be smth. like this - category_select_path(provider: provider.id) In the browser, in a Network tab of devtools I can see my category_select, but there is an error: Couldn't find Game with 'id'=category_select. I can't figure out from where does it come. Any suggestions? Thanks.
The problem was in routes: match '/admin/games/category_select' => 'admin/games#ajax_call', via: :get, as: 'category_select'. It is reserved by a show action of Activeadmin controller. So I changed my routes to: get '/admin/select_category' => 'admin/games#get_providers_categories', as: 'select_category', and added to ajax call data: {provider: provider}, so I could fire in params id of provider:
$.ajax({
type: 'GET',
url: '/admin/select_category',
data: {
provider: provider
},
success: (function (data) {
$('#select_category').children('option').remove();
$('#select_category').prepend('<option value=""></option>');
$.each(data.categories, function () {
$('#select_category').append('<option value="' + this.id + '">' + this.name + '</option>')
})
})
})

Providing AJAX With Rails-Generated URL

Can I provide AJAX with a Rails URL/path?
For example, what I need is url: articles/1/comments/1.
Since I'm experiencing difficulties for some time now making AJAX execute this URL, I wonder if there's a way to use the Rails route I'm familiar with [comment.article, comment].
Note:
I'm loading a DIV using AJAX:
#welcome/index.haml
- #articles.each do |article|
= article.title
- article.comments.each do |comment|
%comment-content{ :id => "comment-#{ comment.id } %>", :class => "comment-content", "data-comment-id" => comment.id }
AJAX:
var loadComment = function() {
return $('.comment-content').each(function() {
var comment_id = $(this).data('comment-id');
return $.ajax({
url: "" ,
type: 'GET',
dataType: 'script',
});
});
};
Rails provide data-remote attribute in form. It works like AJAX and it uses url as you added in form
you can use it like below:
<%= form_for([comment.article, comment], remote: true) do |f| %>
...
<% end %>
you can use like
<%= form_for([comment.article, comment], remote: true) do |f| %>
...
<% end %>
if you are using form_for or if you want to send ajax like:
$.ajax({
})
then you can use
$.ajax({
url : "<%= url_for article_comment_path(article, comment)%>"
})

Rails check_box_tag how to pass value when checked ajaxily

On my index page for my Task model, I want to show a checkbox for every row that corresponds to the boolean field "complete" in my Task database table.
Currently my code gets into the method "Complete", but it does not contain the value of the checkbox that the user just did (i.e. if they just checked the box, it does not pass true to my "Complete" method).
How can i pass the value that the user just performed - either checked or un checked?
/views/tasks/index.html.erb
<% #tasks.each_with_index do |task, i| %>
<tr>
<td><%= check_box_tag 'Complete', task.complete, task.complete, :data => {:remote => true, :url => url_for( :action => 'complete', :id => task.id, :complete => task.complete ), :method => :put}, :class => 'input-large' %></td>
</tr>
<% end %>
/controllers/tasks_controller#complete
# PUT /complete/1
def complete
#task = Task.find(params[:id])
p "inside complete"
p "complete = #{params[:complete]}"
#task.complete =
if #task.update_attributes(params[:task])
p "inside update"
render :text => "success"
else
p "inside error"
end
end
The suggestion from this issue in rails/jquery-ujs github repo worked for me: https://github.com/rails/jquery-ujs/issues/440#issuecomment-197029277
For you it would be:
<%= check_box_tag 'complete', '1', task.complete, {
onchange: "$(this).data('params', 'complete=' + this.checked)",
data: { url: url_for(action: 'complete', id: task.id,), remote: true, method: :patch },
} %>
If you are using jQuery, you can write a click event.
$('.input-large').click(function() {
var checked;
if ($(this).is(':checked')) {
checked = true;
} else {
checked = false;
}
$.ajax({
type: "POST",
url: "/tasks/complete",
data: { id: $(this).data('post-id'), checked: checked }
});
});
As of Rails 4, you should be able to ditch all the JS from the original answer. The code in your question should just work due to jQuery UJS magic.
It turns out that adding remote: true to an input causes jquery-ujs to make it ajax-y in all the nice ways. Thoughtbot's "A Tour of Rails jQuery UJS" briefly touches this (and many other good things available); the "Unobtrusive scripting support for jQuery" page in the jQuery UJS wiki does a thorough job on this as well.
check_box_tag 'complete', task.complete ? 'false' : 'true', task.complete, ...
:url => url_for( :action => 'complete', :id => task.id )
This way in your controller you can get params[:complete].
And you should implement complete.js.erb to rerender checkbox, so next click will send inverse value
Or you can implement js on click event
$('.input-large').on('click', function() {
$.ajax({
type: "PUT",
url: "/tasks/complete/" + $(this).data('post-id')
data: { complete: $(this).is(':checked') }
});
});
and don't forget to place data-post-id param to your checkbox

Resources