How do I render a partial asynchronously/using AJAX in Rails? - javascript

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.

Related

Load data from URL parameters faster

Assuming I am loading a web with domain.com/1/?nick=lili
Right now I am including in my html - multiple files, in one of them I do :
$(document).ready(function(){
let path = window.location.href ;
let nick = path.split('=')[1];
getData(nick,loadUI);
});
Using fast servers (Google's Firebase) I still get quite a delay, about 1-2 seconds while at this time the page is loaded with placeholders so no data, and the experience is not good.
I do not want to put a loader UI element, but to load faster.
Is there anywhere else in the code that I can put this to make things faster? (assuming I also need to load Google's server API on the body).
Removing all of this to the head should work better?
The window.location.href will be populated on pageload - there's no need to wait before checking it. Since getData does not need any data from the page in order to start the request, you can move it outside of the $(document).ready(, and even move it up to the top of the document, in the <head>, before any other scripts have run or elements have loaded. This ensures that the request gets sent out as fast as possible.
But
when someone open the page, all html placeholders (texts and photos) are loaded, then I need to fill them with data from DB
Because you need the page to be loaded when the request comes back, you can't just call getData alone, in the rare case that the page hasn't finished loading by then. So, if you change getData to return a Promise that resolves to the desired data, you could use Promise.all to wait for both the data and for the page to be loaded, after which you can populate the page elements:
Promise.all([
getData(nick,loadUI),
new Promise(res => window.addEventListener('DOMContentLoaded', res))
])
.then(([data]) => {
// Populate page with data from request
});

Ajax Partial Refresh by Polling in Rails 3, jQuery

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

Execute javascript inside the target of an Ajax Call Drag and Drop Shopping Cart without Server language

Well i wanna create an Ajax Drag and Drop Shopping cart using only javascript and ajax. Currently i'm using the example in this page as a stepping stone. Right now it's only with local jquery and it works fine but i want to make the cart work with ajax calls. Note that i do not want to use a server side language( like php, rubby, asp etc), only html and javascript.
My initial thought was that at the $(".basket").droppable i should add an ajax call to another html page containing the "server logic" in javascript, execute in that file all the necessary steps( like reading the get variables (product name, product id and quantity), set a cookie and then return an ok response back. When the server got the "ok" response it should "reload" the cart div with the updated info stored inside the cookie.
If this was with php i would know how to do it. The problem is that as far as i know, you can execute javascript once it reaches the DOM, but how can you execute that js from inside the page that isbeing called upon ? ( thanks to Amadan for the correction)
I've thought about loading the script using $.getScript( "ajax/test.js", function( data, textStatus, jqxhr ).. but the problem with that is that the url GET variables i want to pass to the "server script" do not exist in that page.
I havent implemented all the functionality yet as i am stuck in how to first achieve javascript execution inside an ajax target page.
Below is a very basic form of my logic so far
// read GET variables
var product = getQueryVariable("product");
var id = getQueryVariable("id");
var quantity= getQueryVariable("quantity");
//To DO
//--- here eill go all the logic regarding cookie handling
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
alert('Query Variable ' + variable + ' not found');
}
Any help regarding this matter will be appreciated.
Note: Logic in simple words:
1)have an html page with products+cart
2)Have an "addtocart.html" with the "Cart Server Logic"( being the target of the ajax call when an item is dropped into the product.)
If you have some other idea on this, please enlighten me :)
thanks in advance
Foot Note-1:
if i try loading the scipt using
$("#response").load("ajax/addtocart.html?"+ $.param({
product: product,
id: id,
quantity:quantity
})
);
i get the alert about not being able to find the url parameters( something that i thing is normal as because the content is being loaded into the initial page, from which the request is started, there are no get parameters in the url in the first place)
The problem is that as far as i know, you cannot execute javascript contained in the target of an ajax call, as that page never reaches the browser interpreter.
This is either incorrect or misleading. The browser will execute any JavaScript that enters DOM. Thus, you can use $.load to load content and execute code at the same time. Alternately, you can use hacked JSONP to both execute code and also provide content as a JSON document.
EDIT: Yes, you can't get to the AJAX parameters from JavaScript. Why do you want to? Do you have a good reason for it, or is it an XY problem?
The way I'd do it is this:
$('#response').load(url, data, function() {
onAddedToCart(product, id, quantity);
});
and wrap your JS code in your HTML into the onAddedToCart function.
Depending on what exactly you're doing, it could be simplified even further, but this should be enough to cover your use case.

Basic questions about javascript

Please take a peek at the following code, which is in _form.html.erb:
<script>
function typeCatch(){
$.post("<%= update_readers_link_essay_path(#essay) %>");
$(this).off("keypress", typeCatch);//remove handler
}
$(function(){
("form#new_revision").on("keypress", typeCatch);
});
</script>
When the user starts typing in a form, the ajax request should be fired and update the readers list. However, the post request is not fired when I start typing in the form and I am trying to debug this problem.
Since I am not that familiar with javacsript yet, I would appreciate if you helped me clarify a few things.
a. For the second part, can I just do
$("form#new_revision").on("keypress", typeCatch);
without wrapping it with $(function() {} ?
b. Is there anything that I'm doing wrong? Since ajax call isn't fired, I must have made a mistake in the second part?
Additional Question
my_personal_chat.js (in app/assets/javascripts pipeline)
$(function() {
var pusher = new Pusher('app_key');
var channel = pusher.subscribe('presence-my-chat');
channel.bind('pusher:subscription_succeeded', function(members) {
$('#online_users').empty();
members.each(function(member){
addMember(member);
});
... other functions ...
});
This is how I implemented my chat feature, using Pusher. Since the channel is private, everytime I call var channel, an ajax call to POST /pusher/auth is invoked.
I found that every time I navigate to a different page, even when it's not where the chat feature is, POST /pusher/auth is called. Basically, every time my_personal_chat.js is loaded, the ajax call will be unnecessarily invoked.
Question: How do I prevent this from happening? my_personal_chat.js should only be loaded when I go to myapp.com/chat. Should I just pull out everything from the javascript file and put it inside chat.html.erb? Is that the conventional way of doing it?
to answer my own question: I moved the code from my_personal_chat.js to chat.js.coffee and deleted my_personal_chat.js. Now the javascript only gets loaded when users go to the chat page.
a. There are alternatives, but wrapping the code in $(function() {}) is one of the best ways to ensure that the code isn't executed until all the elements are loaded into the DOM. It's a jQuery feature.
b. ("form#new_revision").on("keypress", typeCatch); should be $("form#new_revision").on("keypress", typeCatch);. You're missing the $ at the beginning.

Grails - rendering div with a javascript call within a remoteSubmit

I have a situation where I want to hit a button in the GSP (actionSubmit) and update a div when I finish the call (which includes a call to a javascript function). I want to ultimate end up in the controller rendering the searchResults parameter and the div with the results (which is currently working).
Problem is, I need to (presumably) wrap my actionSubmit in a remoteForm. But how do I:
1) Run the javascript method already existent in the onClick
2) Render the page in the controller.
If I try both wrapped in a controller, I finish the remoteForm action and the javascript action "hangs" and never finishes.
Any ideas?
List.gsp
<g:actionSubmit type="button" value="Ping All" onclick="getIds('contactList');"/>
function getIds(checkList)
{
var idList = new Array();
jQuery("input[name=" + checkList + "]:checked").each
(
function() {
idList.push(jQuery(this).val());
}
);
$.ajax({
url: "pingAll",
type:"GET",
data:{ids:JSON.stringify(idList)}
});
}
controller:
def pingAll() {
String ids = params.ids
if(ids == "[]") {
render(template:'searchResults', model:[searchResults:""])
return
}
def idArray = contactService.formatIDString(ids)
idArray.each {
def contactInstance = Contact.get(Integer.parseInt(it))
emailPingService.ping(contactInstance)
}
/**
* Added this on 3/13. Commented out line was initial code.
*/
def searchResults = contactSearchService.refactorSearchResults(contactSearchService.searchResults)
render(template:'searchResults', model:[searchResults:searchResults, total:searchResults.size()])
}
You have a couple options:
1) You can avoid using the Grails remote tags (formRemote, remoteField, etc.), and I really encourage you to explore and understand how they work. The Grails remote tags are generally not very flexible. The best way to learn how they work is to just write some sample tags using the examples from the Grails online docs and then look at the rendered page in a web browser. All the tags do generally speaking are output basic html with the attributes you define in your Grails tags. Open up your favorite HTML source view (i.e. Firebug) and see what Grails outputs for the rendered HTML.
The reason I say this is because, the code you've written so far somewhat accomplishes what I've stated above, without using any GSP tags.
g:actionSubmit submits the form you are working in using the controller action you define (which you haven't here, so it runs the action named in your value attribute). However, you also have an onClick on your actionSubmit that is running an AJAX call that also submits data to your pingAll action. Without seeing the rest of your code and what else is involved in your form, you are submitting your form twice!
You can simply just not write actionSubmit, and simply do an input of type button (not submit) with an onClick. Then in your javascript function that runs, define a jQuery success option for your AJAX call
$.ajax({
url: "pingAll",
type:"GET",
data:{ids:JSON.stringify(idList)},
success:function(data) {
$('#your-updatedDiv-id-here').html(data);
}
});
2) If you want to use the GSP tags, I think you are using the wrong one. Without knowing the full extent of your usage and form data involved, it looks like g:formRemote, g:submitToRemote, and g:remoteFunction could serve your purposes. All have attributes you can define to call javascript before the remote call, as well as defining a div to update and various event handlers.

Categories

Resources