I'm working on an application in which a certain model is updated from a number of different locations using remote forms. I'm looking for a pattern to dynamically call the right JS callback after updating this model.
With a normal form this can be solved by passing a redirect url in the form itself and then redirecting to this url in the controller. The remote form side is harder: Should I pass a file name? Should I pass an arbitrary string and then switch on that string in a single .js.erb file? Any other suggestions?
Is this just a sign that the application should be restructured to prevent updating the same model from more than one location?
No it's fine If you can call the same controller action from different locations.
Your options:
1) Preferably this controller action can give the same response and will work for the different locations, ex. it just updates a container with a id which is present in all those locations.
2) You noted that redirects made things easy in the past, consider adding the following to your application controller:
def js_redirect_to(path, flash_messages = {})
flash_messages.each { |type, message| flash[type] = message }
respond_to do |format|
format.js { render :js => "window.top.location='#{path}';" }
end
end
This is the same signature as the normal redirect_to, it just allows you to redirect from a js request. Note that if you use turbolinks the js should be 'Turbolinks.visit(url);'.
3) If you really can't handle it generically like the options above, you could pass your JS namespace of the location you are submitting from in the form, and the controller calls the same method for all locations, it's just a different namespace. Ex:
Let say one location is from Pet administration, then in assets pet.js:
var pet = {
load = function() {
your page load js...
},
... more functions...
post_callback = function(html_segment1, html_segment2) {
this is where you handle the form callback for pets...
$('some_element').html(html_segment1);
$('another_element').html(html_segment2);
}
}
Construct more like these for other locations of your app. Using JS namespaces like this is anyway a good idea. Then your form submits a parameter :location => :pet to the controller, which responds with:
... your JS code that all pages should execute...
html_segment1 = "<%= escape_javascript(render 'some_partial') %>";
html_segment2 = "<%= escape_javascript(render 'other_partial') %>";
<%= #location %>.post_callback(html_segment1, html_segment2);
4) Use a widget gem, most popular is apotomo or cells.
5) Just use a case in the controller to render different views.
Hope this helps, let me know if you need clarification.
Related
The classic way to work with Rails & Ajax is always something that looks like this:
// JS - let's assume this submits to dummies#create
$(form).submit()
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
respond_to do |format|
format.js
end
end
# /views/dummies/create.js.erb
$("page").append("<%= escape_javascript(render partial: 'dummy_view' ) %>");
# /views/dummies/_dummy_view.html
<h1><%= #dummy.name %></h1>
I've always been curious, because the above seems to create a random create.js.erb file with very little meat... is there a reason (e.g., it's terrible convention, or terribly insecure or whatever), why you should NOT instead just render the view directly back to ajax?
// JS - basically takes responsibilites of create.js and puts it into the always
$.ajax(...).always(function(xhr, status){
$("page").append($(xhr['responseText']))
// responseText contains the partial rendered by the controller action
})
# Dummies Controller
def create
#dummy = Dummy.new(dummy_params)
render partial: 'dummy_view'
end
# /views/dummies/_dummy_view.html
# unchanged
<h1><%= #dummy.name %></h1>
NOTE above is pseudo-code, apologies for minor errors. The conceptual idea & question remain unchanged, though.
The create.js.erb is not random, is the view for the action with the expected format.
Generally, you may not have a view so simple (You may have different selectors other than "page", you may have some extra js code to be executed after or before the append), a js view/script is the general solution for an ajax request to give the response full control over what to do.
You could have what you want, but it will just work for your particular case when the selector is always "page" and you only want to append html to that element. Nothing prevents you from doing that (though you might want to use a custom ajax request and not rails' one since it sets js format by default and executes the response's script).
The convention is that a rails' remote request renders a js script, you can move out of the convention if you want. You'll lose a lot of flexibility with your approach as is (like... what if the create action fails an you need to display errors?).
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
I'm working on a single-page site and have gotten the ajax loading of templates to insert into the content part of the site working. However, I'm having trouble doing this with multiple templates, using a parameter.
I have 5 templates, shared/blog, shared/projects, etc.
In my controller, I'm doing an AJAX call to 'replace'
pages = ['blog', 'projects', 'resume', 'gallery', 'contact']
def replace
#content = params[:content]
if not pages.include? content
content = 'blog'
end
respond_to do |format|
format.js
end
end
In replace.js.erb, I have this code:
$(".content_inner").html("<%= j render(:partial => 'shared/blog') %>");
I have kept it just saying 'shared/blog' because it works for loading the blog if I keep the embedded Ruby static like that. However, I can't figure out how to replace the 'blog' part of 'shared/blog' in here to whatever is in the #content variable. I've tried things like #{content}, but to no avail.
(It does receive the content variable correctly, the issue is just with using it)
Any help would be appreciated!
Thanks.
String interpolation requires double quotes. You're after:
$(".content_inner").html("<%= j render("shared/#{#content}") %>");
A few notes:
The :partial => hasn't been necessary for years in Rails. Just use render <partial_name>.
Rails already comes with a place to store your shared partials: app/views/application. You should move your shared partials there, and then you can render them simply by using render(#content). This is important to how Rails works, because it allows you to override the partial in controller-specific view paths. For example, calling render("blog") will render app/views/<controller_name>/blog.js.erb if it exists, and then fallback to app/views/application/blog.js.erb otherwise.
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
I'm not even sure how to ask this question in a way thats understandable.
Basically, I'd like to do some javascript using a js.erb after I save. I'm submitting the form using a regular javascript/coffee-script file (if all fields are filled in correctly, then the form is submitted, else the form does nothing & just displays errors).
Part of my coffee-script:
fieldCorrectResponse: (fields, response) ->
if fields == correct
$('#new_mail')[0].submit()
else
$('#mail_error').text('error while filling out form')
my mail controller:
def create
#mail = Mail.new(mail_params)
if #mail.save
#PERFORM SOME JS USING A JS.ERB
else
render :new
end
END
So I guess what I'm really is asking is, how would you call a js.erb in the controller?
Wrote the solution to my problem below..
You should be able to render js and use create.js.erb.
Please try:
# MailsController
def create
#mail = Mail.new(mail_params)
if #mail.save
respond_to do |format|
format.js
end
else
render :new
end
END
Then, implement your javascript in app/views/mails/create.js.erb.
"do some javascript" isn't terribly descriptive. are you wanting to return a JSON object from the create action, which can then be parsed by the success callback on your jquery? Or do you want to have a template that has javascript in it that gets called as a result of the save action?
vinod covered the second way. make sure you have your routes set up correctly.
if you want to return a parseable JSON object, then write
render json: { some: 'json objects', should: 'go here' }
Also, not knowing what "mails" are, if you're trying to send emails that should be done with action mailer, and probably done as a part of committing the main model to the database (if you're creating a user and also trying to send an email, have a method as part of user creation that sends out the email).