In this episode we're going to talk about creating forms that have multiple submit buttons, so in this case a good example that I came up with, was creating blog posts where you can publish them immediately or you can save them as a draft.
Hopping into our text editor, you can see that we already have the publish button in there, and all we have to do is copy this line and paste it in, and change
<%= f.submit "Publish", class: "btn btn-primary" %>
<%= f.submit "Save as draft", class: "btn btn-default" %>
Now we have these two submit buttons, and we can test the way that the form works by looking in our params inside our Rails logs. Let's create a test post, some text, and we'll click the publish button here to create it, and then we can look in our terminal and see what happened. If you look at the last POST request, you can see that the commit message inside the parameters hash was "Publish", so that means that when you submit these, the button that is submitted is set up so that it gets submitted based upon the text on the button you clicked. So that's really really neat, you can have this next post, click on "Save as draft", and if we look at our terminal and scroll down to that post, that post request now has a commit message of "Save as draft", and it's as simple as that, so inside of your controller, you can look for the params commit and determine if you've saved as draft or published the post.
Inside our posts_controller.rb we can createa couple methods here that are helpers that allow us to add the logic into our create and update actions. I'm going to go down here at the bottom and create a published? method, and this is going to look at the
params[:commit] and see if it equals "Publish". Also, I'm not going to use it, but you can add a method
save_as_draft for the other one and have that as well, in case you want to do separate logic here in these controllers. There's a handful of ways that you can update these actions to support this. One would be
def create @post = Post.new(post_params) @post.published_at = Time.zone.now if publising?
Choosing the names for the methods is important, published and publishing can both be candidates, but remember the importance of readability for yourself and other developers. You can name those accordingly however you want
Another approach might be updating the post once it's saved.
respond_to do |format| if @post.save @post.update(published_at: Time.zone.now) if publishing? #rest of the code
I often prefer going something like this, and the reason for that is because if the post gets saved and any of the publish logic fails, the post will automatically be saved as a draft and you will have it around in case the publish action fails. So maybe you have something like when you publish the post, rather then just simply setting the published_at: date, maybe you have a publish method on your post and inside of there it saves it, sets the published_at time, but it also goes and tells MailChamp to send out an email to everybody saying that we just published this new post. You know, you don't want that to fail here, so could do something like this where if it failed, it would be ok because you wouldn’t lose your progress. So I'm just going to go back to setting the published_at attribute here right after we create a new one in memory, and in our update we can also add that same thing down here, because when you're editing a post, you still want to be able to publish it if you saved it as a draft. That is how you can separate your logic and have two different paths of handling your actions based upon which button was clicked in the form.
Let's take a look at this in our browser and see if it's working in the way we want it to. If we clicked "Edit", now we have a functional "Publish" and "Save as Draft" buttons, and if we save as draft, it should still stay a draft, and if we publish this, the time stamp for the "published at" time is set and it marks the post as published. Now when we edit again, it's a little odd though, because now we have a publish, and it's already published, and we have a "Save as draft", but if you save as draft it still publishes and it didn't reset the post to a draft, so we definitely need to update the formm and if our post is published, let's add a couple other actions here, otherwise, we will have the regular publish and save as draft
<div class="form-group"> <% if @post.published? %> <%= f.submit "Update", class: "btn btn-primary" %> <%= f.submit "Unpublish", class: "btn btn-default" %> <% else %> <%= f.submit "Publish", class: "btn btn-primary" %> <%= f.submit "Save as Draft", class: "btn btn-default" %> </div>
If we change that, we need to go into our controller and do some modifications as well, to give meaning to the "Unpublish" text
def update @post.published_at = Time.zone.now if publishing? @post.published_at = nil if unpublishing? #Rest of code end #Some more code def unpublishing? params[:commit] == "Unpublish" end
And this is only going to affect the update action, obviously you can refactor this however you like and reorganize it, whatever makes more sense for you, but in this example, we now have the update and unpublish, successfully resetting that time stamp there, that is something that you'll probably need to do most of the time, each time the buttons change, and that's actually a really really good thing for usability, for you to update these buttons accordingly.
Hopping back to the form before we wrap up, I've talked about in a previous episode about using the button tag here instead of submit, to create HTML inside of those buttons, so you could have loading animations magically with that data attribute that we talked about in a previous episode, which I definitely recommend checking out if you haven't seen it before.
Now the problem with this is when you do a submit tag,
<input type="submit" name="commit" value="Unpublish"> <%= f.submit "Unpublish", class: "btn btn-primary" %>
That wraps up how to create forms with multiple buttons, and of course, as anything goes, you start simply and think you just need two buttons, but then pretty quickly you need four and so on. I hope you found this interesting, it certainly makes life a lot better for your users because it's a really fantastic way to add a little bit extra usability into your applications
Transcript written by Miguel
Though will this work with translatable strings too? It doesn't seem like a really solid solution to this problem.
If you use the localize method in both places, it will work just fine for translatable strings. You just want to make sure you always keep them the same.
Is this a good method? - What if you are using i18n so the commit message will show different languages?
@excid3:disqus published_at is a column in your db. Is "published?" a rails method that looks at published_at as a boolean and returns true or false depending on if it is nil or not?
Question, is it best practice to be putting the published? and save_as_draft? methods in your post controller? Or are you doing that for the sake of explaining it?