Rails 3, JRuby
I recently took part in a quick crash course in jQuery that included a bit of ajax partial rendering. This got me thinking, could I use this to poll the Rails server using setInterval(), every x seconds to refresh a specific part of my page constantly?
The problem I'm having is how I could use the $.get() method to grab the url of the partial and reload it using load(). This is where the confusion starts- using Rails 3, I have a partial called "_microposts", rendered within a div with an 'id="gf" ' (gf meaning global feed). This happens on my Rails app homepage, so the url in this case would be "//localhost:8080/home" and not the url of the partial.
Here is my initial javascript/ jQuery
<script>
$(document).ready(function() {
setInterval(function (e) {
var url = $.get("<%= escape_javascript render :partial =>
'microposts/micropost', :locals => {:microposts => #microposts }%>");
$('#gf').html('loading...').load(url);
},10000);
});
</script>
This looks wrong, and so far, just blanks out my _microposts partial after 10 seconds (so the setInterval is working, and it's definitely updating the correct area, just with a blank space!)
Edit:
Thinking about my problem, I realised that this is similar to updating a partial from an event, such as clicking a button or something. The only real difference is the "event" that should trigger this the setInterval() function. So, my revised jQuery code is as follows:
<script>
$(document).ready(function() {
setInterval(function (e) {
$('#gf').html("<%= escape_javascript render :partial =>
'microposts/micropost', :locals => {:microposts => #microposts } %>")},
10000);
});
</script>
Unfortunately now, nothing seems to be happening from a user point of view, but the server is showing an ajax request every 10 seconds.
So why can't I poll for updates using ajax, and apply the changes to my _microposts partial? Is $.get the correct function to use in this case? What would the url for the load() method be when trying to re-load a partial?
Thanks,
Hopefully this will help anybody who wants to refresh a partial using ajax- especially if you're a beginner following Michael Hartl's tutorials to learn Ruby on Rails. Here's how I managed to solve my problem.
Firstly, I created a separate .js.erb file in the micropost view folder called 'polling.js.erb' that will refresh the global feed partial.
$('#gf').html("<%= escape_javascript render :partial =>
'microposts/micropost', :locals => {:microposts => #mps} %>");
I needed to write a method in the micropost controller that will correspond with the above javascript- this essentially supplies the information needed to refresh the partial. It's basically a simplified version of my index method in the micropost controller and avoids executing the additional code that's not needed for the partial I want to refresh.
def polling
#mps = Micropost.all #add some paginate code if you wish
#micropost = current_user.microposts.build(params[:micropost])
end
I then revised my javascript code, as I wanted to call the polling method every 5 seconds, loading the information specific to the current_user of my webapp.
$(document).ready(function() {
setInterval(function () {
$.ajax('microposts/<%= current_user.id %>/polling');
} , 5000);
});
Finally, I updated my routes.rb file to allow a web browser to call my polling method, using a get request, without causing a routing error. I'm using a member do block because the request is passing the current_user id via the request.
resources :microposts do
member do
post :polling
end
end
Related
I've been reading a lot about Rails and AJAX and 5.1 Unobtrusive javascript. It explains a lot about responding to Rails version of AJAX calls with a .js file for example.
However what im wanting to do isn't serving up an entire .js file, it's simply updating an element after a <% link_to %> POST request. From my understanding setting remote: true submits it as a AJAX request.
Essentially I have a "Post" which a user can like via a linked Like button. This sends a POST request to the "Post" controller which updates a post to liked and adds a like to the post.
Unfortunately to see the effects of the post being liked (Which is simply that the link changes color as well as the font-awesome icon) you need to refresh the page. I basically want it to update without needing refresh.
I "think" based off what i've read I need to make a respond do and respond via .js to the request with a .js file in the view I want to update (for instance if the controller action is called "like", maybe a like.js.erb file in the view im updating?). But I don't want to serve an entire new page..or would this simply just run the .js?
Then I could do something like $('i.fa-icon#id').style.color = "blue" or something? (Im assuming I can send data from the controller to the .js.erb file?). Not sure the best way to do this, don't rails elements a lot of times have some sort of data-attribute or something (Im still a beginner at this).
Your description is quite correct!
Opposed to the other answer, you don't even need a event listener but as you said you want to have a respond_to in the controller.
So starting from the html:
# post/index.html.erb
<div id="like-button">
<%= button_to "Like this post", post_path(#post), remote: true %>
</div>
Note, that when you use a button_to helper it'll be a POST request by default.
If you click it, it'll go to the controller#update, which you want to change to this:
#posts_controller.rb
...
def update
#post.save
respond_to do |format|
format.html { redirect_to post_path(#post) }
format.js # <-- will render `app/views/posts/update.js.erb`
end
end
Note: the format.html is rendered when JS is disabled.
Now in the scenario that JS is enabled, it executes the app/views/posts/update.js.erb file. It can look like this:
const likeButton = document.getElementById('like-button');
likeButton.innerHTML = '<%= j render "posts/liked-link", post: #post %>';
What is the last line doing? Of course, you can change the style directly with the JavaScript, but you can also render a new partial - and this you will create in a new html file:
# app/views/posts/liked_link.html.erb
<div id="like-button ">
<p>"You liked this post!" </p>
</div>
I just changed the link/button to ap now, but of course you can do whatever you want.
Hope that makes sense :)
Not sure if I understand the question, but if you want to update like button:
What you want to do is to add an event listener to the button, and when clicked it makes a POST request to whatever route handles the likes(with the correct parameters) and your controller should respond with the like object (or whatever in the database gets stored). Have your post request on success method to grab the like button and change it to whatever you want it to look like
$(“#like-btn”).click(function(){
Rails.ajax({
url: "/some/url/to/like/controller",
type: "post",
data: [your post data],
success: function(data) { $(`#${ data[“btn-name”] }`).attr(“color”, “blue”; }
})
}
You can stick this script right in the bottom of the html page
You don’t have to do it exactly like this, just giving you an idea of how to set up the pattern of having JavaScript and Ajax handle the post request and updating of the frontend instead of using html buttons
Here's what I'm trying to do:
The user pastes a URL.
The input box that the user pastes in has an :onpaste that triggers urlPasted() function.
urlPasted() function submits the form that input box is in, which does an AJAX call to a custom function named lookup_profile.
In the controller, lookup_profile function does some web requests, and then updates some instance variables.
Once those variables are updated (takes ~5 seconds), the view has a function that waits 20 seconds and updates textboxes on the modal with the results of those instance variables.
Here's what I have thus far in the view:
<%= form_tag url_for(:controller => 'users', :action => 'lookup_profile'), id: "profileLookupForm", :method => 'post', :remote => true, :authenticity_token => true do %>
<div class="form-group row">
<div class="col-sm-12">
<%= text_field_tag "paste_data", nil, onpaste: "profileURLPasted();", class: "form-control"%>
</div>
</div>
<% end %>
<script type="text/javascript">
function profileURLPasted() {
// Once the user pastes data, this is going to submit a POST request to the controller.
setTimeout(function () {
document.getElementById("profileLookupForm").submit();
}, 100);
setTimeout(function () {
prefillForm();
}, 20000);
};
function prefillForm() {
// Replace company details.
$('#companyNameTextBox').val("<%= #company_name %>");
};
</script>
Here's what the controller looks like:
def lookup_profile
# bunch of code here
#company_name = "Random"
end
Now here's the problem I have. When the user pastes the data, it submits perfectly to the custom_action lookupProfile. However, after lookupProfile runs its code, rails doesn't know what to do afterwards. By that, I mean it gives me this error:
Users#lookup_profile is missing a template for this request format and
variant. request.formats: ["text/html"] request.variant: []
When in fact, I actually have a file at views/users/lookup_profile.js.erb. For some reason, it's trying to render the HTML version. I don't know why.
Secondly, I've tried putting this in the controller towards the end:
respond_to do |format|
format.js { render 'users/lookup_profile'}
end
but that results in this error:
ActionController::UnknownFormat
Any help would be greatly appreciated. I just want the custom function to run, update the instance variables, and let me update the current form with that data.
Here's another stackoverflow reference of something similar I'm trying to do: Rails submitting a form through ajax and updating the view but this method doesn't work (getting the actioncontroller error)
* EDIT 1 *
Ok, so I fixed the ActionController error by replacing my form_tag with:
<%= form_tag(lookup_profile_users_path(format: :js), method: :post, :authenticity_token => true, id: 'profileLookupForm', remote: true) do %>
But now it's actually rendering the actual javascript into the view, and I don't want that. I simply want to be able to access the instance variables that were updated in the lookup_profile action, not display the view.
* EDIT 2 *
So I think my problem comes down to this: Placing a button in the form and submitting from IT is different than my javascript code that submits the form. If I can figure out what's up with that, then I think I may be in good shape.
You are mixing a few things there. First of all, instead of doing document.getElementById("profileLookupForm").submit() you should do an ajax request, I guess the submit() method ignores the remote: true directive from rails.
So, change the submission to:
form = getElementById("profileLookupForm");
$.post(form.action, {paste_data: this.value}, 'script')
// form.action is the url, `this` is the input field, 'script' tells rails it should render a js script
That way the request is done async and the response does not replace the current page.
Now, what I think you are mixing is that #company_name won't change with that ajax request. When you render the form and everything else, #company_name is replaced with the actual value IN THAT MOMENT and will not change after your post request since the reference is lost. So this line:
$('#companyNameTextBox').val("<%= #company_name %>");
will be
$('#companyNameTextBox').val("");
al the time.
What you want is to respond with a script that updates the field with the value that you set to #company_name (also, waiting arbitrarilly X seconds is a really bad practice).
So, instead of responding with:
format.js { render 'users/lookup_profile'}
create a view lookup_profile.js with the code that you want to execute
$('#companyNameTextBox').val("<%= #company_name %>");
here, #company_name will actually be the value obtained with those requests you told before, the script is generated at the moment and excecuted as a response of the request.
I have a Rails 3.2.14 legacy app where I have a view that I'd like to refresh the partials in without page reload. I currently have this working, but am wanting to see if this is the best way to go about it.
Here are come code excerpts:
index.html.erb:
<div id="active">
<%= render "assigned_calls" %>
</div>
<div id="inactive">
<%= render "unassigned_calls" %>
</div>
<script>
$(function() {
setInterval(function(){
$.getScript("/calls").fail(function(jqxhr, settings, exception) {
window.location = "/users/sign_in?duplicate_session=true";
});
}, 10000);
});
</script>
index.js.erb
$("#active").html("<%= escape_javascript render("assigned_calls") %>");
$("#inactive").html("<%= escape_javascript render("unassigned_calls") %>");
So far I have a JS call to get /calls every 10 seconds. This works fine but I'm wondering if there's a better way to do this. In the view we have a timer (evaluates date of call versus Time.zone.now) and it updates ever 10 seconds since the partials reload. I'd really like this to be truly realtime and wondering if setting the interval in the JS to 1 second would be advised. This seems like an awful lot of partial refreshes and tons of queries/log entries.
If there's a better way to refresh the divs in realtime, I'm all ears. Right now what I have works, but my ultimate goal is to get the data to update in realtime somehow.
Maybe you can use new feature :
ActionController::Live
his module was developed by Aaron Patterson to enable data to be streamed live from Server to Client. Essentially, instead of returning html or JSON as a response to an html request, ActionController::Live enables an i/o stream to be returned as a response. This means we can define an action in a controller that will set up a stream of live data to any client that requests it, until the connection is closed again.
class SensorController < ApplicationController
include ActionController::Live
def temperature
response.headers['Content-Type'] = ‘text/event-stream’
100.times {
response.stream.write(“The temperature is #{ 100*rand} degrees Celcius\n”)
sleep 0.5
}
response.stream.write(“That’s all for now!”)
ensure
response.stream.close
end
end
You may find at this link: http://api.rubyonrails.org/classes/ActionController/Live/SSE.html
Instead of creating AJAX requests every 10 seconds, you can use websockets combined with a Redis pubsub mechanism to dynamically update your Rails partials. Here's a gem that can help you out with refreshing the divs in realtime:
https://github.com/llawlor/meteorite
I currently have the following code to load a partial when I scroll to the bottom of a div with a table:
$(document).ready(function () {
$("#pastGigs").scroll(function () {
if (isScrollBottom()) {
$('#pastGigs tr:last').after('<%= render "layouts/pastGigs" %>');
$(this).unbind("scroll");
}
});
function isScrollBottom() {
var elementHeight = $("#pastGigs")[0].scrollHeight;
var scrollPosition = $("#pastGigs").height() + $("#pastGigs").scrollTop();
return (elementHeight == scrollPosition);
};
});
It works fine, but the reason I'm using the partial is because I don't want everything to load immediately, as it really slows the page down. Should I not be using a partial at all, or is there another way to execute the script and load the content only after scrolling? I've had a look around similar questions on stackoverflow, they all explain how to render the partial (I didn't know about escape_javascript and deleted all whitespace in the partial manually...), but none seem to solve my current speed issue. There is a difference of about 15 seconds (!) due to the amount of data in the partial, which is why I don't want to load it synchronously.
EDIT: Current config/routes.rb:
root "resnate_pages#home"
resources :users, :path => ''
EDIT 2: Current error in Terminal:
Processing by UsersController#show as */*
Parameters: {"id"=>"pastGigs"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", "pastGigs"]]
Completed 404 Not Found in 3ms
ActiveRecord::RecordNotFound (Couldn't find User with id=pastGigs):
app/controllers/users_controller.rb:5:in `show'
After looking over everything, it looks like you have the right idea with rendering the partial except that, as we discussed, it is not actually being loaded asynchronously. Since you have the ERB tags in the body of the javascript, the ERB is actually being rendered server-side before being delivered to the browser (as you noticed, with the huge blob of HTML text). The reason this appeared to work (meaning avoiding the huge 15-second load times you mentioned) is because the HTML text isn't actually interpreted as HTML yet by the browser when the page loads (its just a plain 'ol string at this point). It will of course be parsed and evaluated when you do add it to the DOM with the .after function.
So basically we just need to ajax this stuff up. Good that you're looking at the jQuery ajax documentation; definitely worth the time and its pretty informative. There is also a $.get function which you might want to see as well. Its a convenience method for sending a GET request; you have less control, but if that doesn't matter, it can help keep things clean. I'll demonstrate with the $.get function, and you can choose to use the $.ajax function instead if you need the control (or prefer the other).
Put this in your scroll function:
$("#pastGigs").scroll(function () {
if (isScrollBottom()) {
$.get("/pastGigs", function(result){
$('#pastGigs tr:last').after(result);
});
$(this).unbind("scroll");
}
});
You can name the pastGigs route however you like, its only for demonstration purposes. Ensure you set up the route in your routes.rb
get "/pastGigs" => "users#pastGigs"
OR
get "/pastGigs", to: "users#pastGigs"
Again, I don't know what your controller is called, so replace it and the action name with the correct ones in your system.
And finally, the controller action:
def pastGigs
render :partial => "layouts/pastGigs"
# OR
# render "layouts/pastGigs", :layout => false
end
The end result will be that when the scroll reaches the bottom, an ajax call is fired off to that URL and your partial is rendered and returned to the success function (the second parameter of $.get). The result variable will contain the rendered HTML partial.
Let me know if that works.
Quickly adapted Paul's code to populate a modal for anyone who's interested:
$('#seeAll').click(function(){
$.get("/pastGigs", function(result){
$('#pastGigsBody').html(result);});
});
When the user clicks on "See All" in the Past Gigs div, it opens the modal with the all of the past gigs. Thanks to Paul for teaching me about the $.get request.
I have been experiencing an intermittent issue in my Rails app, and I'm having trouble figuring out what is going on. When the user logs in, they see a dashboard that contains some JavaScript code which performs an AJAX call to an action. Occasionally, instead of seeing the dashboard, when the user logs in, they see the JSON response text from the action instead of the dashboard (in Chrome) or they download a .json file (Firefox). It's intermittent and doesn't usually happen, but it's really annoying when it does occur.
Here's a dumbed down version of some of the code:
JS running in the template head:
$(function () {
var remoteLink = $('#remoteLink');
remoteLink.live("ajax:complete", function () {
setTimeout(function () {
loadCount();
}, 30000);
});
loadCount();
function loadCount() {
remoteLink.click();
}
});
And the link in the template:
<%= link_to 'get count (hidden)', {:controller => 'something', :action => 'count'},
:id => 'remoteLink', :class => 'hidden', :remote => true, 'data-type' => 'json' %>
And the controller action:
def count
render :json => get_counts_function_returning_a_hash
end
My hunch is that it's a race condition -- perhaps related to the use of setTimeout? -- but I haven't been able to verify that hunch. Can anyone tell me what might be going on here? I've seen this in the wild on other sites, too -- also intermittent, not generally occurring but annoying when it happens.
Do you have any authentication code that uses store_location or similar functionality to redirect the user to a specific page after they have logged in?
I once had a similar problem where the AJAX callback was causing store_location to store the JSON URL and the user was redirected to the JSON response rather than their dashboard. This was hard to track down because the user had to sit idle at the page for some time before the bug was manifest.
In any case, I suspect it is something like this rather than a problem with Rails.
I would suspect the Content-Type of the HTTP response and check any places you are setting it for possible bugs. Run your browser with an extension for tracing HTTP headers to capture what the values are when it does occur.