How do I link an action to a button without creati

2019-07-31 17:24发布

问题:

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?

回答1:

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!' }


回答2:

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"