Rails How To Update Index View Via Ajax After Dropdown Option Selected - javascript

I have a products model with a 'dept' attribute. the model includes a list of departments. In my view is a drop down list with all the department types. I am trying to get the page to refresh after a department is selected from the dropdown. I would prefer to have it updated via AJAX but at this point ill take a normal page referesh.
My Model
class Product < ActiveRecord::Base
attr_accessible :dept, :price, :title
DEPT_TYPES = ["Baby","Beauty"]
end
My controller:
class StoreController < ApplicationController
def index
#title= "Home"
#products = Product.order(:premium)
#baby , #beauty = [], []
#products.each do |product|
#baby << product if product.dept == 'Baby'
#beauty << product if product.dept == 'Beauty'
end
respond_to do |format|
format.html # index.html.erb
format.js # index.js.erb
format.json { render json: #products }
end
end
My View
<div class ="filter">
<select>
<option value="<%=#baby%>">baby</option>
<option value="<%=#beauty%>">beauty</option>
</select>
</div>
<div id="products_list">
<% #products.each do |product|%>
...
<%end%>

You can use simple ajax jQuery function:
$(".filter").change(function(){
var value = $(this).val()
$.ajax({
url: <%= stores_path(:json) %>,
type: 'GET',
data: value,
success: function(data){
$("#products_list").html(data.products)
}
})
})
You have two way to do it:
The first one is to send from the server for example JSON with all products. Or you can create ".js.erb" file in the views and handle the actions there.

Related

Div is not updated after selecting value on a dropdown list

I'm trying to display a chart upon selecting a state and a city.
The cities dropdown list is populated based on the selection of the state in the first dropdown.
The problem is when I click on a city no chart is displayed, nor updated if I change the selection, and I can't seem to figure out the problem.
I'm using ChartKick to display a pie chart with the values given by a query in the controller.
My code is as follows:
controllers/city_stats_controller.rb:
def city_stats
#state = params[:state]
#cities = City.where(:state => #state).select(:id, :display_name).uniq.order('display_name ASC')
# #city_id = City.select('id').where(:state => params[:city_state]).where(:name => params[:city_name])
#city_id = City.select('id').find_by_id(params[:city])
dynamic_query = ActiveRecord::Base.send(:sanitize_sql_array, ['SELECT COALESCE(name) as name, count(*) FROM (SELECT name, regs.segment_id, count(*) FROM regs
INNER JOIN segments ON regs.segment_id = segments.id
WHERE regs.city_id = ?
GROUP BY segment_id, name
ORDER BY count(*) DESC) as tabe
GROUP BY name;', #city_id])
#spatial_ratio = ActiveRecord::Base.connection.select_all(dynamic_query)
puts #city_id.inspect #I can see the value printed in the terminal
puts #spatial_ratio.inspect #Same here, even after changing the selection
respond_to do |format|
format.json { render :json => #cities }
format.js {
render json: {
html: render_to_string(
partial: 'city_stats',
locals: {
city_id: #city_id,
spatial_ratio: #spatial_ratio
})
}
}
format.html
end
end
views/city_stats/city_stats.html.erb :
<div class="row">
<label>State <span>:</span></label>
<%= collection_select :city, :state, City.select(:state).uniq.order('state ASC'), :state, :state, {:prompt => 'Select a State'}, {id: 'city_state', multiple: false} %>
<label>City <span>:</span></label>
<%= collection_select :city, :name, [], :name, :name, {:prompt => 'Select a City'}, {id: 'city_name', multiple: false} %>
</div>
<div id="cities">
<!-- Here I render the cities list upon selecting a state-->
<%= render :partial => 'city_stats', :object => #cities %>
</div>
<div id="stats">
</div>
<script type="text/javascript">
$(document).ready(function() {
$("#city_state").on('change', function(){
$.ajax({
url: "/admin/city_stats",
type: "GET",
data: {state: $(this).val()},
success: function(data) {
$("#city_name").children().remove();
// Create options and append to the list
var listitems = [];
$.each(data,function(key, value) {
listitems += '<option value="' + value.id + '">' + value.display_name + '</option>';
});
$("#city_name").append(listitems);
//console.log(listitems);
$("#city_name").on('click', function(){
$.ajax({
url: "/admin/city_stats",
type: "GET",
data: {city: $(this).val()},
success: function(data) {
var content = $("#city_name").val()
console.log(content);
$('#stats').replaceWith("<%= j render(partial: 'city_stats') %>");
return content;
}
})
})
}
})
});
});
And in the partial : views/city_stats/_city_stats.html.erb :
<%= pie_chart #spatial_ratio.rows %>
N.B : If I replace the city_id in the dynamic query in the controller by a random id manually, a chart is displayed of the corresponded city when I refresh the page, but I don't know why it's not working dynamically
1.update your city_stats method like below
def city_stats
...
...
respond_to do |format|
format.json { render :json => #cities }
format.js
end
end
2. create city_stats.js.erb file with below code
$("#stats").html("<%= escape_javascript(pie_chart #spatial_ratio.rows) %>");
3. change your city_click event like this
$("#city_name").on('click', function(){
$.ajax({
url: "/admin/city_stats",
type: "GET",
data: {city: $(this).val()},
success: function(data) {
}
})
});
This should solve your problem. If any problem/doubt comment
#city_id = City.select('id').find_by_id(params[:city])
This line doesn't return you a number. It returns an instance of your model with only one field (id) loaded. Try to change it to
#city_id = City.select('id').find_by_id(params[:city]).id
That will give you a number.
Also, as I see, in your handler of AJAX in $("#city_name").on('click') you don't actually update your view with generated chart.
<%= j render(partial: 'city_stats') %>
This line gets evaluated only once, on initial rendering of the html page (that's why everything works when city_id is set manually), so on each AJAX request, that doesn't reload your page entirely, you replace $('#stats') with the same content.
By the way, once you replace $('#stats'), you will not access it the next time without total rerendering of the page, because it gets removed from the DOM. See reference of (replaceWith).
I think that in case of AJAX it's better to use .empty() on $('#stats') container and then .append() new chart inside it.
The idea is that if you want to update chart without reloading of the page, you should use the fresh data from the the server.
success: function(data) {
In your case it will be inside the data variable.
I see that in your controller you have this:
format.js {
render json: {
html: render_to_string(
partial: 'city_stats',
locals: {
city_id: #city_id,
spatial_ratio: #spatial_ratio
})
}
}
So you should use this data in your handler. It might be like this:
success: function(data) {
$('#stats').empty().append(data.html);
}
However, I'm not sure that it's reachable in current implementation. I guess that every time you make an AJAX request, it will end with
format.json { render :json => #cities }
So you should either return both #cities and rendered partial here, and use them in relevant success callbacks, or somehow separate responses for each request.
One more thing about the code - it's not good to bind event handler for $("#city_name") inside change handler of $("#city_state"), because on every change of $("#city_state") you add the same handler for $("#city_name") click, and on each click this handler will be executed as many times as it has been bound. You can use .off before binding to prevent this, but maybe it's worth to do some refactoring.

Trying to create a form that automatically submits and reloads page upon change

I would like a dropdown box and as a user selects a new option from it, it should automatically save (without a submit button) and the page should reload.
As it is, however, selecting a value from the dropdown box does not do anything: it does not save nor reload the page. Anyone got an idea how to get the code below to work?
Form:
<%= form_for #user, method: :patch, url: set_preference_path do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.collection_select :default_relationship_id, #organizations, :id, :name, {onchange: "this.form.submit();"}, {class: 'form-control input-md'} %>
# default_relationship_id is the column in users table to save to, #organizations are the selectable options, id is the value of the options to save, and name is the name to be displayed.
<% end %>
Does onchange: "this.form.submit();" perhaps not work when using collection_select? The examples from which I adapted this implementation (stackoverflow.com/a/24315219 and stackoverflow.com/a/17663373) use it in combination with select instead of collection_select.
Controller:
def overview
#user = current_user
#organizations = #user.organizations.where('fish = ?', true)
end
def set_preference
#user = current_user
#rel = Relationship.where('user_id = ? and organization_id = ? and fish = ?', #user.id, params[:user][:default_relationship_id], true).first
#user.update_attributes(default_relationship_id: #rel.id)
respond_to do |format|
format.js { render inline: "location.reload();" }
format.html { redirect_to overview_path(#user) }
end
end
The html code this produces:
<form class="edit_user" id="edit_user_1" action="/set_preference/test1" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2*3;" /><input type="hidden" name="_method" value="patch" /><input type="hidden" name="authenticity_token" value="m/9ECijmnYQ==" />
<select class="form-control input-md" name="user[default_relationship_id]" id="user_default_relationship_id">
<option value="1">Example1 business</option>
<option value="56">Example2</option>
</form>
You're specifying :onclick in the options hash instead of the html_options hash (where :class is specified), so move it to the correct hash, but keep the options hash blank:
f.collection_select :default_relationship_id, #organizations, :id, :name, {}, {class: 'form-control input-md', onchange: "this.form.submit();"}
See the documentation for the the full arguments list.
ETA The chosen value of #user.default_relationship_id isn't selected by collection_select because it refers to a relationship, but you've given the collection #organizations. Since collection_select can't find the associated organization from a relationship ID, it selects the first option by default. You should refactor so collection_select receives a #relationships collection. The refactoring should be such that you can update directly from strong params:
set_preference
…
#user.update user_relationship_params
…
end
private
def user_relationship_params
params.require(:user).permit(:default_relationship_id)
end
Read up on form helpers for more information.
With jQuery, try to handle onchange event and try ajax post
Javascript snippet:
/**
* Assume jQuery script on head
*/
<script type="text/javascript">
/**
* Handle onchange on user_default_relationship_id
*/
$("#user_default_relationship_id").change(function () {
var data = $( this ).form.serialize();
$.ajax({
type: "POST",
url: <%= set_preference_path %>,
data: data,
dataType: "application/json"
})
.done(function() {
// handle done event ...
})
.fail(function() {
// handle fail event ...
})
.always(function() {
// handle event ...
});
</script>

Update ajax content with will_paginate

How can I update my view keeping all existing ajax and will_paginate functionality in place?
I have a page rehome.html.erb
<div id="options">Option Select Here</>
<div class="all_animals">
<%= render #animals %>
</div>
<% unless #animals.current_page == #animals.total_pages %>
<div id="infinite-scrolling">
<%= will_paginate #animals %>
</div>
<% end %>
// WILL_PAGINATE
<script type="text/javascript">
$(function(){
if($('#infinite-scrolling').size() > 0) {
$(window).on('scroll', function(){
//Bail out right away if we're busy loading the next chunk
if(window.pagination_loading){
return;
}
more_url = $('.pagination a.next_page').attr('href');
if(more_url && $(window).scrollTop() > $(document).height() - $(window).height() - 50){
//Make a note that we're busy loading the next chunk.
window.pagination_loading = true;
$('.pagination').text('Loading.....');
$.getScript(more_url).always(function(){
window.pagination_loading = false;
});
}
});
}
});
</script>
This will load all the #animals collection, paginating it to 6 per page, and when I scroll down the page another 6 are loaded etc etc.
Corresponding controller
class PublicController < ApplicationController
before_filter :default_animal, only: [:rehome]
def rehome
respond_to do |format|
format.html
format.js
end
end
private
def default_animal
#animals = Animal.animals_rehome.paginate(:page => params[:page], :per_page => 6)
end
end
rehome.js.erb
$('.all_animals').append('<%= j render #animals %>');
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
<% end %>
So when an option is selected from the dropdown an ajax post is made to create a new query which will return a new collection of #animals
$.ajax({
type: 'POST',
url: '/public/rehomed',
data: data_send,
success: function(data) {
//console.log(data);
}
});
Controller
def rehomed
# conditions logic
#animals = Animal.joins(:user).where(conditions).paginate(:page => params[:page], :per_page => 6)
respond_to do |format|
format.js {render json: #animals }
end
end
What I want to do is have the new collection loaded (paginated to 6 per page again) and when I scroll down only show the objects belonging to the new collection of #animals (if there are any).
At the moment the pagination links are not updated as when I scroll down the page the original collection is loaded.
Edit
So I have created a rehomed.js.erb file which is pretty much the same as my rehome.js.erb:
$('.all_animals').empty();
$('.all_animals').append('<%= j render #animals %>');
<% if #animals.next_page %>
$('.pagination').replaceWith('<%= j will_paginate #animals %>');
<% else %>
$(window).off('scroll');
$('.pagination').remove();
<% end %>
and within my rehomed action
respond_to do |format|
format.js
end
So the new collection of animals is loaded, the pagination links are recreated but using the rehomed url, example being:
Before
<a class="next_page" href="/public/rehome?page=2" rel="next">Next →</a>
After
<a class="next_page" href="/public/rehomed?page=2" rel="next">Next →</a>
So when I scroll down I just get the following as the links don't exist and getScript fails
$('.pagination').text('Loading.....');
Edit 2
I have implemented #japed's answer but now after the new collection is rendered the pagination will continue to render the whole collection of the db including repeating the ones that where selected for the new collection, it's repeating itself.
How can I ensure that the correct url is generated for my links?
Will paginate allows you to set the params hash so you should be able to change it to use your other controller action like this:
will_paginate(#animals, :params => { :controller => "public", :action => "rehomed" })

Issue with will_paginate page links

I currently have a comment model that posts under a micropost and both are displayed on the same page. The issue is that both are displayed on the same page and both are paginated and I am trying to go for the facebook approach to microposting. Here is the issue below:
The links for both pagination turns into this href="/users/2?page=2" rather than href="/users/2/micropost?page=2" or href="/users/2/comment?page=2". I am unsure how to go about solving this problem. Here are some of my code. All suggestions are much appreciated!
Micropost Render HTML
<table class="microposts">
<% if microposts.any? %>
<%= render microposts %>
<%= will_paginate microposts, :page_links => false %>
<% else %>
<div class="EmptyContainer"><span class='Empty'>Add a thread!</span></div>
<% end %>
</table>
Comment Section HTML
<div id='CommentContainer-<%= micropost.id%>' class='CommentContainer Condensed2'>
<div class='Comment'>
<%= render :partial => "comments/form", :locals => { :micropost => micropost } %>
</div>
<div id='comments'>
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<%= render comments %>
<%= will_paginate comments, :class =>"pagination" %>
</div>
</div>
User Controller for the Show Page
def show
#user = User.find(params[:id])
#comment = Comment.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#comments = #micropost.comments.paginate(:page => params[:page], :per_page => 5)
#microposts = #user.microposts.order('created_at DESC').paginate(:per_page => 10, :page => params[:page])
respond_to do |format|
format.html
format.js
end
end
Problem lies within will_paginate way of creating urls for each page (it doesn't have anything to do with jQuery).
By design, will_paginate try its best to guess what's the base url for the page user is on (internally it's using controller/action to do that). That base url is then combined with any extra params passed to will_paginate helper using :params and succesive page numbers.
For now (will_paginate 3.0.3), in order to overwrite this default behavior, you need to write your custom LinkRenderer class. Below there's example of such class - it makes use of new, extra option :base_link_url that can be passed to will_paginate view helper. Passed string is then used as a base when creating pagination links. If :base_link_url option is not passed, it will fallback to default behavior.
Put following class somewhere rails can find it on load (/lib for example, provided you've added /lib to your autoload paths in application.rb):
# custom_link_renderer.rb
class CustomLinkRenderer < WillPaginate::ActionView::LinkRenderer
def prepare(collection, options, template)
#base_link_url = options.delete :base_link_url
#base_link_url_has_qs = #base_link_url.index('?') != nil if #base_link_url
super
end
protected
def url(page)
if #base_link_url.blank?
super
else
#base_url_params ||= begin
merge_optional_params(default_url_params)
end
url_params = #base_url_params.dup
add_current_page_param(url_params, page)
query_s = []
url_params.each_pair {|key,val| query_s.push("#{key}=#{val}")}
if query_s.size > 0
#base_link_url+(#base_link_url_has_qs ? '&' : '?')+query_s.join('&')
else
#base_link_url
end
end
end
end
Usage:
# in your view
will_paginate collection, :renderer => CustomLinkRenderer, :base_link_url => '/anything/you/want'
And now back to your case. By this time you probably see the solution - you can have two will_paginate widgets on one page with different base urls by passing different :base_link_url options for those two.

How do you call a controller action with jQuery from your application.js file?

I'm using the jQuery autocomplete plugin, and I want to customize this event:
select: function(event, ui) {
$('.topic_field').val(ui.item.topic.name);
return false;
Essentially, it triggers callbacks when an element from the dropdown list is selected. As of now, it only adds the selected element to the text field. I want both the field to be populated and for my application to send a POST request to the video update controller action, so that the user does not need to explicitly press the button. How can I do this?
UPDATE:
Here is the form in the show view of the video controller:
<%= form_for #video, :url => {:action => "update"}, :remote => true do |f| %>
<div class="field">
<%= f.text_field :topic_names, :class => "topic_field" %>
</div>
<%= f.submit "Add Topic" %>
<% end %>
Here is my jQuery code:
var url = $('.edit_video').attr('action');
var val = ui.item.topic.name;
$.post(url, {data:val});
This is in my routes.rb:
resources :videos
resources :video_votes
resources :users
resources :profiles
resources :genres
resources :topics
resources :topicables
resource :session
Here's my update action:
def update
#video = current_user.videos.find(params[:id])
respond_to do |format|
if #video.update_attributes(params[:video])
format.html { redirect_to(#video) }
format.js
else
format.html { render :action => "edit" }
end
end
end
Check out the jQuery.post documentation on how to post the data from your select box to the relevant controller action.
I'm not familiar with the autocomplete plugin, but I presume it follows an onChange event on your select box. When the user has made a selection, you should execute the following (untested):
var url = $('myForm').attr('action');
var val = $('mySelectBox').val();
$.post(url, {data:val})
In your controller, you could access this using:
params[:data]
which would return the value of your select box. You can then use this as normal in your controller code.
use the jquery ajax function to fetch the data from your action url ("/topics/list") or something
$.post('video/update',
data: {yourdata},
function(data) {
//callback function
}
);
in your video#update be sure to return something for your js request:
def update
blah
respond_to do |format|
format.js { return somethinghere }
end
end

Categories