I'm trying to ajaxify my will_pagniate pagination in rails. I want to have the old page fade out and the new one fade in.
Here's the relevant part of my controller:
respond_to do |format|
format.html # new.html.erb
format.js {
render :update do |page|
page.replace 'page', :partial => 'cur_page'
end
}
format.xml { render :xml => #branch }
end
The aforementioned partial:
<div id="page">
<%= will_paginate %>
<div id="posts">
<%= render #posts %>
</div>
<%= will_paginate %>
</div>
And the relevant part of application.js:
document.observe("dom:loaded", function() {
// the element in which we will observe all clicks and capture
// ones originating from pagination links
var container = $(document.body)
if (container) {
var img = new Image
img.src = '/images/spinner.gif'
function createSpinner() {
return new Element('img', { src: img.src, 'class': 'spinner' })
}
container.observe('click', function(e) {
var el = e.element()
if (el.match('.pagination a')) {
el.up('.pagination').insert(createSpinner())
target = $('posts')
new Effect.fade(target, { duration: 0.3, afterFinish: function()
{
new Ajax.Request(el.href,
{
method: 'get',
onSuccess: function(){ new Effect.Appear(target, {duration:0.3})}
})
}})
e.stop()
}
})
}
})
The script seems to get killed on this line,
new Effect.fade(target, { duration: 0.3, afterFinish: function()
because I see the spinner.gif start, then no fading and the page is refreshed normally. I have got the ajax working before I tried to add Effect.Fade and Effect.Appear.
Is this the right way to go about this? Should I put the effects in the controller instead?
Here is what I did using jQuery and working well too :)
Put your will_paginate helper view call in a div
#tickets_pagination
= will_paginate #tickets
In application.js
$("#tickets_pagination .pagination a").live("click", function() {
$.get("/users/?"+this.href.split("?")[1], null, null, "script");
return false
});
The javascript above will convert the pagination links in #tickets_pagination to ajax links
In your controller as usual
def index
#tickets = Ticket.all.paginate({:page => params[:page], :per_page => 10 })
respond_to do |format|
format.js
format.html
end
end
Now finally in index.js.erb
$("#tickets_list_table").fadeOut('slow');
$("#tickets_list_table").html("<%= escape_javascript(render :partial =>'tickets/tickets_table', :locals => {:tickets => #tickets}) %>");
$("#tickets_list_table").fadeIn('slow');
Here tickets/ticket_table has a table that lists all tickets. The partial is rendered in a div #ticket_list_table
Hope this will work for you as well.
I tried putting more of the work into the javascript helpers:
respond_to do |format|
format.html # new.html.erb
format.js {
render :update do |page|
page.visual_effect :fade, 'posts', :afterFinsh => "function(){" +
page.replace 'page', :partial => 'cur_page' +
page.visual_effect(:appear, 'branches') + "}"
end
}
format.xml { render :xml => #branch }
end
Then removed this part of the javascript:
new Effect.fade(target, { duration: 0.3, afterFinish: function()
I get the effect I want, but all out of order. The request completes and the html is replaced, then the div fades out and then reappears!
Not very familiar with RoR, does it generate its own client-side JS that may possibly be battling your code?
If not, I would say the problem is somewhere in your own client-side code. For testing, get rid of the HREF attribute from the anchor tag and place the URL as a string literal in the Ajax request. If nothing happens, there is a problem with the Ajax request itself. If the page loads as expected, then the event in the original scenario is not being completely stopped.
Also, clean up your JS a bit just to be sure, line-ending semi-colons where needed.
You seem to mix up things a bit.
Either you write $('posts').fade or new Effect.fade('posts').
Secondly, i can't seem to find the afterFinish option in the documentation.
So i would suggest something along the following lines:
container.observe('click', function(e) {
var el = e.element()
if (el.match('.pagination a')) {
el.up('.pagination').insert(createSpinner())
target = $('posts')
new Effect.fade('posts', { duration: 0.3});
setTimeout("new Ajax.Request(el.href, { method: 'get',
onSuccess: function(){
new Effect.Appear('posts', {duration:0.3})
} })", 1000);
e.stop();
}
})
Hope this helps.
Related
From file soul.html.erb , partial view called at the time of page load
<%= render 'contact_options_soul_d' %>
In file contact_options_soul_d.html.erb partial view looks like this -
<div id="contact-option">
<div id= 'un-authenticated' data-access-api-result = <%= #valid_ticket %> %></div>
</div>
On click of a button a Rest call happens in controller and partial view is called from partial_create.js.erb
def partial_create
respond_to do |format|
format.js
end
return
end
File partial_create.js.erb
$('#contact-option').html('<%= escape_javascript(render :partial => 'rhythm/contact_options_soul_d') %>')
My Question is - partial view is called , breakpoint hits and new value of #valid_ticket also gets updated, but view is not getting updated as webpage still looks same even after updation.(View remains same when checked in browser inspect)
Please help!!!
This is not an answer to exactly the way you're doing it, but you could always render it in the controller and return that on "respond_to", then on Ajax success use the returned value to substitute the html:
def partial_create
html = (render_to_string partial: 'rhythm/contact_options_soul_d', locals: { if_you_need: variables_in_partial })
respond_to do |format|
format.json { render json: { new_html: html, success: true } }
end
return
end
then on your JS code (this with jQuery):
function soul_searching(parameters) {
$.ajax({
url: "YOUR/POST/ROUTE",
type: "POST",
dataType: "json",
data: {
any_parameters: 'you_need_to_pass_to_controller'
},
success: function(data) {
$('#contact-option').html(data['new_html']);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
//DO SOMETHING
}
});
}
You need to prepend partials with an underscore. For example:
_contact_options_soul_d.html.erb
Note: You only need to do this to the file name. You can still call the partial with
<%= render partial: 'contact_options_soul_d.html.erb' %>
Also make sure you're referencing the path if the partial is in a separate folder.
Within a rails 4 app, I am using a link_to to send an upvote on posts via json.
Here is what I have in my posts controller:
def upvote
#post = Post.find(params[:id])
#post.liked_by current_user
respond_to do |format|
format.html {redirect_to :back }
format.json { render json: { count: #post.get_upvotes.size } }
end
end
Here is what I have in my view
<%= link_to like_post_path(post), method: :put, class: 'vote', remote: true, data: { type: :json } do %>
<%= image_tag('vote.png') %>
<%= content_tag :span, post.get_upvotes.size %>
<% end %>
<script>
$(document)
.on('ajax:send', '.vote', function () { $(this).addClass('loading'); })
.on('ajax:complete', '.vote', function () { $(this).removeClass('loading'); })
.on('ajax:error', '.vote', function(e, xhr, status, error) { console.log(status); console.log(error); })
.on('ajax:success', '.vote', function (e, data, status, xhr) {
$(this).find("span").html(data.count);
$(this).find("img").attr("src", '<%= asset_path 'voted.png' %>');
});
</script>
When I click on the link, the vote goes through as a JSON request, I see this in my log:
Processing by PostsController#upvote as JSON
But for some reason, my snipped of javascript is not working. Neither the counter or the icon update. How can I fix this? Does this have to do with turbolinks, does it have to do with where I am placing the javascript?
In Rails you can perform a similar task by having a JavaScript response. Add in your respond_to a format.js similar to format.html then have a view upvote.js.erb that looks like:
(function() {
var postId = "#post-<%= #post.id %>";
$(postId).find(".vote").find("span").text("<%= #post.get_upvotes.size %>");
$(postId).find(".vote").find("img").attr("src", "<%= asset_path "voted.png" %>");
})();
I changed your call to .html to .text since you're not actually setting any HTML inside the element, there is no reason to call .html.
This post also assumes there is some mechanism to identify the post the vote link belongs to (in the example the parent post element has an ID of "post-#" where # is the ID of the post object).
EDIT
Two changes I'd make if I were working on this project. First I would attach the voted.png path to the .vote element as a data attribute. data-voted-image-src="<%= asset_path "voted.png" %>". Next, I would never pass a number in the return as there is no reason to do so. When the vote is clicked you can handle everything on the front end by assuming the request is successful. Which saves all this potential nastiness. While I realize that changing from what you current have to adding the data attribute isn't a huge leap I just find it more semantic than having it in the JavaScript.
The click action on the vote link then becomes:
// Assume all posts have a class 'post'
// I'm also using 'one' because once they vote they don't need to anymore
$(".post").find(".vote").one("click", function(e) {
var count = parseInt($(this).find("span").text()),
votedSrc = $(this).data("voted-image-src");
$(this).find("img").attr("src", votedSrc);
$(this).find("span").text(count + 1);
});
Now no response from the server is necessary, and you can change your JSON response to {success: true} or something simple.
jQuery is the default rails javascript library. The default rails javascript library used to be prototype, so old tutorials/docs use it. This is what the ajax looks like with jQuery:
app/controllers/static_pages_controller.rb:
class StaticPagesController < ApplicationController
def show_link
end
def upvote
respond_to do |format|
format.json {render json: {"count" => "10"} }
end
end
end
app/views/static_pages/show_link.html:
<div>Here is an ajax link:</div>
<%= link_to(
"Click me",
'/static_pages/upvote',
'remote' => true, #Submit request with ajax, and put text/javascript on front of Accept header
data: { type: :json }) #Put application/json on front of Accept header
%>
<div>Upvotes:</div>
<div id="upvotes">3</div>
<script>
$(document).ready( function() {
$(this).ajaxSuccess( function(event, jqXHR, ajaxInfo, data) {
//var js_obj = JSON.parse(jqXHR.responseText);
//$('#upvotes').html(js_obj["count"]);
//Apparently, the fourth argument to the handler, data,
//already contains the js_obj created from parsing the
//json string contained in the response.
$('#upvotes').html(data["count"]);
});
});
</script>
config/routes.rb:
Test1::Application.routes.draw do
get 'static_pages/show_link'
get 'static_pages/upvote'
...
end
url to enter in browser:
http://localhost:3000/static_pages/show_link
See jquery docs here:
http://api.jquery.com/ajaxSuccess/
Response to comment:
You could also do the following in your controller:
def upvote
#upvotes = 2 #Set an #variable to the number of upvotes
respond_to do |format|
format.js {} #By default renders app/views/static_pages/upvote.js.erb
end
end
Then:
app/views/static_pages/upvote.js.erb:
$('#upvotes').html(<%= #upvotes %>)
I am trying to create a add/edit credit card form within my edit user page. To do so I am trying to implement an ajax call to the edit and create functions in my customers controller.
This is the code I have for the update button within the modal window:
<%= button_tag "Update", :class =>"btn submit-button", :type => 'button', :onclick => "onUpdateCard('#{current_user.id}');"%>
This is the function that it calls:
function onUpdateCard(id) {
this.id = id;
// disable the submit button to prevent repeated clicks
$('.submit-button').attr("disabled", "disabled");
var card_number = document.getElementById('card_number').value;
var card_code = document.getElementById('card_code').value;
var card_month = document.getElementById('card_month').value;
var card_year = document.getElementById('card_year').value;
var response = Stripe.createToken({
number: $('#card_number').val(),
cvc: $('#card_code').val(),
exp_month: $('#card_month').val(),
exp_year: $('#card_year').val()
}, stripeResponseHandler);
// allow the form to submit with the default action
return false;
};
function stripeResponseHandler(status, response) {
if (response.error) {
$(".payment-errors").text(response.error.message);
$(".submit-button").removeAttr("disabled");
} else {
var token = response['id'];
var new_url = "/users/" + this.id + "/customers/new";
var edit_url = "/users/" + this.id + "/customers/1/edit";
$.ajax({
type:'GET',
url: edit_url,
data: {'stripe_card_token': token}
});
}
return false;
};
And in the controller there is the edit function:
def edit
#user = current_user
#customer = #user.customer
stripe_customer = Stripe::Customer.retrieve(#customer.stripe_customer_token)
stripe_customer.card = params[:stripe_card_token]
stripe_customer.save
end
Can you help me figure out how to handle the ajax correctly? I'm not sure how to debug this properly...
Here I'm suggesting the alternative to handle update request using AJAX.
I'm not improving or correcting your code but giving you a way to handle AJAX requests in Rails 3.
a. view
Whatever information you wants to update in Database using AJAX call you will pass through a form. So for making a AJAX request you need to add :remote => true in your form. Rails provides this helper.
<%= form_for #customer, :url => admin_customers_path, :method => :post, :remote => true, :html => { :id => "customer-form" } do |form|-%>
<%= render :partial => 'admin/customers/form', :object => form %>
<%= form.submit 'Update' %>
<% end %>
In the _form.html.erb you can add textfield or other this whatever you wants to add in your edit form
b. controller
Because of " :remote => true " you form submission will make a JS request so in update action after saving the data of customer
control will for to format.js and then it will look for update.js.erb in views.
def update
if #customer.update_attributes(params[:customer])
respond_to do |format|
format.html {
flash[:success] = "customer's info was updated Successfully."
redirect_to customers_path
}
format.js
end
else
respond_to do |format|
format.html {
flash[:error] = #customer.errors.present? ? #customer.errors.full_messages.join('<br />') : "Oops! There is some problem with category update."
render :edit
}
format.js
end
end
end
c. update.js.erb
You can do stuffs after successful update. Suppose you want to highlight some div then you can do like this.
$('.target-div').effect("highlight", {}, 2500);
I'm trying to implementing an endless scroll on my project. I'm using a mix of the Railscast #114 Endless Page and this.
Everything works fine besides a weird behavior when I try to stop sending requests when the page hits its end.
So far I have:
Controller:
def show
#title = Photoset.find(params[:id]).name
#photos = Photoset.find(params[:id]).photo.paginate(:page => params[:page], :per_page => 20)
respond_to do |format|
format.js
format.html
end
end
Show.html.erb:
<% content_for :body_class, '' %>
<%= render 'shared/header' %>
<div id="photos_container">
<div id="photos_header">
<h2><%= #title %></h2>
</div>
<%= render :partial => 'photo', :collection => #photos %>
</div>
<%= render :partial => 'endless_scroll' %>
Javascript (loaded via partial):
<script type="text/javascript">
(function() {
var page = 1,
loading = false,
finish = false;
function nearBottomOfPage() {
return $(window).scrollTop() > $(document).height() - $(window).height() - 200;
}
function finish() {
finish = true;
}
$(window).scroll(function(){
if (loading) {
return;
}
if(nearBottomOfPage() && !finish) {
loading=true;
page++;
$.ajax({
url: '/photosets/<%= params[:id] %>?page=' + page,
type: 'get',
dataType: 'script',
success: function() {
loading=false;
}
});
}
});
}());
</script>
show.js.erb
$("#photos_container").append("<%= escape_javascript(render :partial => 'photo', :collection => #photos) %>");
<% if #photos.total_pages == params[:page].to_i() %>
page.call 'finish'
<% end %>
As you can see, on my show.js.erb I have a page.call that assigns true to the finish variable. This stops the requests.
The wired thing is that it never loads the last page. When #photos.total_pages == params[:page].to_i() instead of just calling the finish function and setting the variable to true, it's also preventing the $("#photos_container").append("<%= escape_javascript(render :partial => 'photo', :collection => #photos) %>"); from running.
It sends the request to the controller, runs the SQL but doesn't append the last page.
If I change the condition to #photos.total_pages < params[:page].to_i() it works, but send an extra request to a page that doesn't exist.
I'd appreciate any help on my implementation. I'm not sure if there's a more adequate (Rails) way to accomplish this.
First of all you can render html from the partial when request is xhr type:
def show
photoset = Photoset.includes(:photos).find(params[:id])
#title = photoset.name
#photos = photoset.photo.paginate(:page => params[:page], :per_page => 20)
if request.xhr?
render '_photo', :layout => false
end
end
Then use ajax call:
$.ajax({
url: '/photosets/<%= params[:id] %>?page=' + page,
type: 'get',
dataType: 'script',
success: function(response) {
$("#photos_container").append(response);
if (response == "") {
//stop calling endless scroll
}
});
});
I'm in the process of migrating from Prototype to jQuery and moving all JS outside of the view files. All is going fairly well with one exception. Here's what I'm trying to do, and the problem I'm having. I have a diary where users can update records in-line in the page like so:
user clicks 'edit' link to edit an entry in the diary
a get request is performed via jQuery and an edit form is displayed allowing the user to modify the record
user updates the record, the form disappears and the updated record is shown in place of the form
All of that works so far. The problem arises when:
user updates a record
user clicks 'edit' to update another record
in this case, the edit form is shown twice!
In firebug I get a status code 200 when the form shows, and then moments later, another edit form shows again with a status code of 304
I only want the form to show once, not twice. The form shows twice only after I update a record, otherwise everything works fine. Here's the code, any ideas? I think this might have to do with the fact that in food_item_update.js I call the editDiaryEntry() after a record is updated, but if I don't call that function and try and update the record after it's been modified, then it just spits up the .js.erb response on the screen. That's also why I have the editDiaryEntry() in the add_food.js.erb file. Any help would be greatly appreciated.
diary.js
jQuery(document).ready(function() {
postFoodEntry();
editDiaryEntry();
initDatePicker();
});
function postFoodEntry() {
jQuery('form#add_entry').submit(function(e) {
e.preventDefault();
jQuery.post(this.action, jQuery(this).serialize(), null, "script");
// return this
});
}
function editDiaryEntry() {
jQuery('.edit_link').click(function(e) {
e.preventDefault();
// This should look to see if one version of this is open...
if (jQuery('#edit_container_' + this.id).length == 0 ) {
jQuery.get('/diary/entry/edit', {id: this.id}, null, "script");
}
});
}
function closeEdit () {
jQuery('.close_edit').click(function(e) {
e.preventDefault();
jQuery('.entry_edit_container').remove();
jQuery("#entry_" + this.id).show();
});
}
function updateDiaryEntry() {
jQuery('.edit_entry_form').submit(function(e) {
e.preventDefault();
jQuery.post(this.action, $(this).serialize(), null, "script");
});
}
function initDatePicker() {
jQuery("#date, #edit_date").datepicker();
};
add_food.js.erb
jQuery("#entry_alert").show();
jQuery('#add_entry')[ 0 ].reset();
jQuery('#diary_entries').html("<%= escape_javascript(render :partial => 'members/diary/diary_entries', :object => #diary, :locals => {:record_counter => 0, :date_header => 0, :edit_mode => #diary_edit}, :layout => false ) %>");
jQuery('#entry_alert').html("<%= escape_javascript(render :partial => 'members/diary/entry_alert', :locals => {:type => #type, :message => #alert_message}) %>");
jQuery('#entry_alert').show();
setTimeout(function() { jQuery('#entry_alert').fadeOut('slow'); }, 5000);
editDiaryEntry();
food_item_edit.js.erb
jQuery("#entry_<%= #entry.id %>").hide();
jQuery("#entry_<%= #entry.id %>").after("<%= escape_javascript(render :partial => 'members/diary/food_item_edit', :locals => {:user_food_profile => #entry}) %>");
closeEdit();
updateDiaryEntry();
initDatePicker();
food_item_update.js
jQuery("#entry_<%= #entry.id %>").replaceWith("<%= escape_javascript(render :partial => 'members/diary/food_item', :locals => {:entry => #entry, :total_calories => 0}) %>");
jQuery('.entry_edit_container').remove();
editDiaryEntry();
Maybe you should be using the live function instead of binding to the click once. I am not sure if it will help but it will sure alleviate the need to bind the click event again after updating the page.