I am having a problem getting the page to update. I am completely lost and don't know where to start. My form for does have a :remote=>true The page initialy loads with a partial wrapped in a div tag called comments. I don't know what to place in the controller or what file to create, or even how to create it, to update the partial when the user clicks submit on the form. Thanks for any help.
Travis,
I'm not sure which action you're trying to perform here exactly, so let's assume you want to create a new resource.
Let's assume your resource is called Post
If you used rails to generate your PostController, you'll have a method create in there.
It may look something like this:
def create
#posts= Post.new(params[:post])
respond_to do |format|
if #post.save
format.html { redirect_to(#post, :notice => 'Post was successfully created.') }
format.xml { render :xml => #post, :status => :created, :location => #post}
else
format.html { render :action => "new" }
format.xml { render :xml => #post.errors, :status => :unprocessable_entity }
end
end
end
The key part here is that you'll need a format.js in the respond_to block.
When you put a :remote => true flag on your forms or links, you're telling rails that you want to make the request via Ajax. This results in a data-remote attribute being placed on your form or a element and that is in turn used to tell the unobtrusive Javascript to use Ajax to submit your request.
What you need to make sure is present on your end are the following:
In your controller, make sure there is a format.js response format listed in the respond_to block (see example above for html and xml.
Second, we're going to create a create.js.erb file under your app/views/posts folder. By default, rails will look for a action.format.erb file that corresponds to your action and format.
In the app/views/posts/create.js.erb file, you can now place your response javascript that will update your HTML document accordingly. If for example, you had a list of posts and you wanted to add a newly created one to the end of it, you may have something like this:
app/views/something/show.html.erb
<h1>Posts</h1>
<div id="posts">
<%= render #something.posts %>
</div>
<!-- here we will include the :remote => true option, which will add a data-remote attribute to our form -->
<%= form_for Post.new, :remote => true do |f| %>
<%= f.text_area :text %>
<%= f.submit %>
<% end %>
app/views/posts/create.js.erb
// here we're taking our newly created post and appending it to the list shown above
$('#posts').append("<%= escape_javascript(render #post) %>");
Finally, let's assume our post partial is something like this
app/views/posts/_post.html.erb
<p class="post-text"><%= #post.text %></p>
I'm not sure what javascript framework you're using, but here I'm using jQuery. I believe if you're using rails 3.1, jQuery is the default framework used, otherwise you'd have to look at jquery_ujs.
Hope this helps.
Related
I have a time logger controller method that calls from my view, called _embed_menu.html.erb
<%= link_to l(:start_time_logger) + ' #' + #issue.id.to_s + ' ',
{:controller => '/time_loggers', :action => 'start', :issue_id => #issue.id},
:class => 'icon icon-start',
:"data-replace" => '#time-logger-menu',
:remote => true
%>
The part of 'start' method, where I shown an error:
respond_to do |format|
format.js{ flash[:error] = l(:start_time_expired_error)}
end
Now to the part of the rendering flash messages. application_helper.rb
Have render_flash_messages function.
def render_flash_messages
s = ''
flash.each do |k,v|
s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
end
s.html_safe
end
and that function called on base.html.erb template.
<%= render_flash_messages %>
So the result - I launch a start method and flash shown only after I reload the page. I also tried to redirect with the hope that after this redirect the error will show.
redirect_to controller: 'issues', format: 'js'
but no result.
Maybe I can trigger re-rendering of <%= render_flash_messages %>?
What happens when you click a remote: true link is that Rails creates a request for text/javascript and then runs the resulting javascript in that page.
Usually a js.erb template contains some javascript that modifies the document:
$("<%= escape_javascript(render #user) %>").appendTo("#users");
So if you want to alter the flash messages on the page you need to target the wrapping element and append it:
$("<%= escape_javascript(render_flash_messages) %>").appendTo("#flashes");
This assumes you have a wrapping element around the flash messages. Otherwise just add one to the layout.
<div id="flashes">
<%= render_flash_messages %>
</div>
Use flash.now[:error] if you need to use the flash message on the same request.
https://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html#method-i-now
https://guides.rubyonrails.org/action_controller_overview.html#flash-now
replace flash to flash.now.
for example,
respond_to do |format|
format.js{ flash.now[:error] = l(:start_time_expired_error)}
end
and in js.erb file re-render the partial where you want to update.
I just manually redirect to the page by window.location solution - here I just call issues index controller, but here can any link.
flash[:error] = l(:start_time_expired_error)
respond_to do |format|
format.js{ render js:"window.location='/issues/"+#issue.id.to_s+"'"}
end
I have a vote button in my Rails app and to allow a user to vote I've created a route that connects the button and the respective action.
Route:
/posts/1/vote_up
But on other ROR websites I've been analyzing they can accomplish the same thing without creating a route (or at least, without showing it to the public user). An example would be Producthunt, there's a vote button, but when you hover over it, there is no route or URL mapped to it.
How does one do that? Can I link an action to a button without creating a route to it?
Without knowing much about your controller or models this is how you would do it. This is a silent Ajax call.
<%= form_tag(post, id: "post" + String(post.id),method: :patch, remote: true, authenticity_token: true) do %>
<%= hidden_field_tag "post[voter]", current_user.id %>
<%= hidden_field_tag "post[vote_up]", true %>
<%= button_tag "Up Vote", onclick: "$('#post#{String(post.id)}').trigger('submit.rails');" %>
<% end %>
The method: :patch will call your update method from your controller. So now you don't need to define a route.
If you're doing an increment in your DB you'll need to add the logic in your update method of your controller to catch the param, delete it, and then increase your count. You may also want to implement a way that the current user can only vote once per post.
In your controller you could add a JavaScript response of nothing if you'd like format.js {render nothing: true}
def update
respond_to do |format|
if #post.update(post_params)
format.js {render nothing: true}
format.html { redirect_to #post, notice: 'Post was successfully updated.' }
format.json { render :show, status: :ok, location: #post }
else
format.js {render nothing: true}
format.html { render :edit }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
In response to:
If I just need a 'link_to' to call a function in the controller with silent AJAX (instead of a form_tag doing that, like in your example), how would I achieve it?
A link by itself will not send any data... which is why you had to define a route. Unless you give the link params.
<%= link_to "Up Vote", post_path(#post, post: {up_vote: true}) %>
You can also provide a method for this. So if you put method: :patch it should call your update method.
<%= link_to "Up Vote", post_path(#post, post: {up_vote: true}), method: :patch %>
These examples will add a bunch of text in the url in the form of params:
# posts/1?post[up_vote]=true
So this works without configuring your routes.rb file. But you may need to add more params and modify your controller to handle these params.
My answer:
What I've written here will not appear as a form, but look just like a button. You can substitute button_tag with anything else that supports onclick to submit the data you want to the server.
Instead of a hyperlink reference you use hidden fields of a form to pass the data you want to use. You can't actually use link_to without giving it a hpyerlink reference. So a substitute for that is content_tag 'a'. Just replace the previous form button_tag line to produce this.
<%= form_tag(post, id: "post" + String(post.id),method: :patch, remote: true, authenticity_token: true) do %>
<%= hidden_field_tag "post[voter]", current_user.id %>
<%= hidden_field_tag "post[vote_up]", true %>
<%= content_tag 'a', "Up Vote", onclick: "$('#post#{String(post.id)}').trigger('submit.rails');" , style: 'cursor: pointer' %>
<% end %>
It now appears you have a regular link, and the mouse will change when you click on it. Now the issue is it will look like nothing happens when people click on the "Up Vote" link. You will need to add a CSS and/or JS action to change the user's view once they click on the link. So as well as the onlcick performing a submit you need to call a Javascript method to modify your content from "Up Vote" to "Thanks for Voting!". Other then that this works, it doesn't "appear" to be a form, and you have your link.
If you for some reason do want the page to reload after the link has been clicked you can change the JS response.
format.js { redirect_to #post, notice: 'Thanks for voting!' }
Try this. This is what we used to use before named routes:
link_to "Link to listing new", controller: :listings, action: :new
#or
link_to "string method", "/listings/new"
I'm new to Rails, and I'm having an issue where I can't render a .js.erb file. I think the root of the issue is that Rails' internal routing mechanism expects me to name and configure my files just so, but I'm missing one or two pieces, and I'm not sure how to look for what needs to be fixed.
I have an HTML view with a link to a controller action:
<%# snip %>
<div id="holding_issues_list">
<%= link_to "Show issues on hold", {
:action => "show_user_issues",
:controller => "support",
:issue_type => "holding",
:user_id => #user.id },
:remote => true %>
</div>
<%# snip %>
I think (but I'm not sure) that :remote => true causes the link to make an AJAX call.
This is the corresponding controller action in the controller app/controllers/support_controller.rb:
def show_user_issues
#target_div = params[:target_div] || "holding_issues_list"
user = User.find(params[:user_id])
issue_type = params[:issue_type]
#snip - set the value of #issues
end
I want this file, named show_user_issues.js.erb and placed in app/views/support, to be rendered when the controller exits:
$("#<%= #target_div %>").show();
alert('test');
$("#<%= #target_div %>").html(
"<%= escape_javascript render :partial => '_show_user_issues', :locals => {:target_div => #target_div, :issues => #issues} %>");
This is app/views/support/_show_user_issues.html.erb, the partial I want show_user_issues.js.erb to render:
<% for issue in #active_issues %>
<div id="issue_<%= issue.id %>_display">
<%= render :partial => 'show_issue_mini', :locals => {:issue => issue} %>
</div>
<% end %>
When I try clicking the link in my original HTML view, nothing happens. When I open it up in a new tab, I get this error message:
Template is missing
Missing template support/show_user_issues,
application/show_user_issues with {:locale=>[:en],
:handlers=>[:builder, :erb], :formats=>[:html]}. Searched in: *
"/home/<>/app/views" *
"/home/<>/gems/kaminari-0.14.1/app/views"
The alert('test') that I put into show_user_issues.js.erb doesn't show up, so I think that Rails is getting hung up on rendering that file - that is, the routing mechanism can't find it. How can I correct this issue?
P.S. I double-checked that I put in all the file names exactly as they are in the code base.
Change your controller action to handle the type of request.
def show_user_issues
#target_div = params[:target_div] || "holding_issues_list"
user = User.find(params[:user_id])
issue_type = params[:issue_type]
#snip - set the value of #issues
respond_to do |format|
format.js
end
end
This will check the format of the request which is .js in case of :remote => true. So it will handle it by rendering the show_user_issues.js.erb file.
A couple other problems that I ran into after applying Manoj Monga's answer that I suspect other new Rails devs might run into:
In show_user_issues.js.erb, I had
[...].html("<%= escape_javascript render :partial => '_show_user_issues',[...]
The underscore before '_show_user_issues' caused the ERB builder to fail. It should have just been 'show_user_issues'.
In _show_user_issues.html.erb, I had
<% for issue in #active_issues %>
If you look closely at show_user_issues.js.erb, though, I named the variable #issues, not #active_issues:
[...]:locals => {:target_div => #target_div, :issues => #issues}[...]
So I changed the line in the HTML partial to
<% for issue in #issues %>
After these last couple changes, the new functionality I was adding worked as expected.
I have a view with an ajax form:
<%= form_tag( {:action => 'some_action'}, {:remote => true}) do %>
...
<%= submit_tag "Submit" %>
<% end %>
Now, in the target action, I want to display a partial
def some_action
# some logic
render :partial => "some_partial"
end
The partial is just html. _some_partial.html.erb could be
<br>Hi, this is the partial</br>
When I submit the form, I see the html response packet is received in the browser (with firebug's net logs), but the html doesn't show up anywhere. Where should the html be? How to render a partial html view from an action?
you are rendering a partial but not specifying where to render it.
in rails 2 you could do it with:
def some_action
# some logic
render :update do |page|
page.replace_html 'some_div_id', :partial => 'some_partial'
end
end
but in rails 3, above code is no longer valid.
see Unobtrusive Javascript
create a file named some_action.js.erb and write the code in it:
// update div with id some_div_id
$("#some_div_id").html("<%= escape_javascript(render :partial => 'some_partial') %>");
in controller
def some_action
# some logic
respond_to do |format|
format.js
end
end
I figured out how to send and display html. From theReq, just add :remote => true, "data-type" => "html" to the form and then in the javascript of the page, capture the returned html on ajaxSuccess to display it:
$('form#whatever').on('ajaxSuccess', function (event, data, status, xhr) {
$('div#target').html(data);
}
)
I am trying to get some simple toggles to render small partials instead of changing the whole view - I'm not clear whether this is considered AJAX or not... anyway.
Here we have the user answering a question. While doing this, the question itself can be flagged as inappropriate.
View: answers#new.html.erb
Partial in view: _flag.html.erb
Controller for toggle: opinions/flag, which is supposed to update the opinion and render the flag partial inside the current answers#new.
# flag.html.erb
<% if #opinion.try(:flag) == true %>
<b>Flagged.</b>
<%= link_to "undo", opinions_flag_url(:id => #question.id, :flag => false), :remote => true %>
<% else %>
<%= link_to "flag this", opinions_flag_url(:id => #question.id, :flag => true), :remote => true %>
<% end %>
# last part of opinions controller / flag
if #opinion.save
format.html { render :partial => "answers/flag" }
format.xml { head :ok }
else
format.html { render :action => "new", :id=>params[:id] }
format.xml { render :xml => #opinion.errors, :status => :unprocessable_entity }
end
The partial is rendered, but not inside the current view (it's displayed on a blank page). In general, how do I render bits and pieces to the current view without Rails assuming it needs to show me the partial by itself?
Corollary: If I want to render text to a view, how do I specify where that new text will go?
You should use rjs. If you aren't sure if you are using it, you probably aren't :)
In short, you should use form_remote_for instead of form_for and you should use this in your controllers.
respond_to do |format|
...
format.js { Do stuff here }
end
Here's a screencast: http://railscasts.com/episodes/43-ajax-with-rjs
The specific command to put some html in a specific place would be:
page.replace_html "SOME_HTML_ID", "<p>SOME_HTML</p>"
But once you've seen the screencast, that should be obvious :)