Preview a post in a new window without saving it

Hi,

Ive got a form for a blog post and there are 3 buttons - Publish, Preview and Save as Draft.

Clicking on Preview should open a new window with the preview of the post without saving it while the current window stays on the "New Post" form. I dont want the post to be saved because, once the user views the preview and closes it, when he clicks on, say, Save as Draft on the form, a second post with the same details gets created. How can I achieve this functionality?

On a side note, I also want to perform model validations only if the post is Published. Thought it might be relevant.

Im quite stumped so any and all ideas are welcome :slight_smile:

Thanks.

Use a state machine. posts are initially in 'draft' state, and thus can be previewed, but are not published. When publish is pressed, change the state to 'published'. You probably don't even need to manage state transition formally. Just make sure when you display posts to get only published ones (use named_scope to make this more automatic).

Hi ross,

Thats what im doing right now. Im saving it as a Draft and rendering the show action for the instance @post as a preview. But that breaks 2 requirements, namely

1. The preview does not open in a new window.. its the same window. 2. There are no call to action buttons on the Preview page (there should not be any) and so when the user hits the back button, he's taken back to the new post form where hitting Save as Draft saves a whole new post with the same data.

Even if I manage to show the preview in a new window, i will still have the duplicating posts problem if i save it before previewing it.

A simple solution for that, open the preview in new window

<%= link_to "Preview", "url_path", :popup => true %>

For avoiding duplication, check in DB: if any post was saved with the same attributes within a time range, if yes, most likely its a duplicate (you can see more in rails space book's RESTful blog chapter)

Thanks, Abhinav

Hi Abhinav,

I was looking at this angle in the beginning but what would the "url_path" be? I basically need the resource's show method but that is a member method and the path is incomplete without a model id in the parameters. However, the post is not saved and does not have an id yet. Is there a way to create a member method/route for a resource that does not require a model id?

Checking in the DB to avid duplication isnt really elegant. I'd like to implement that as a last resort solution. It throws up other workflow problems too (if the user closes preview window and changes something in the form, how would u update the relevant model when the form is a POST form?).

As I see it, there are two ways of doing it:

1. As previously suggested, save it , but let the state be "preview", after a user saves it state can change to "draft". Essentially means as soon as user clicks on preview, you create in db and then you have an id

2. Othe way (I am not very sure about this): pass the content in params and in your preview action read from params and display. Not a very RESTful design I believe.

Thanks, Abhinav

Hi ross,

Thats what im doing right now. Im saving it as a Draft and rendering the show action for the instance @post as a preview. But that breaks 2 requirements, namely

1. The preview does not open in a new window.. its the same window. 2. There are no call to action buttons on the Preview page (there should not be any) and so when the user hits the back button, he's taken back to the new post form where hitting Save as Draft saves a whole new post with the same data.

Even if I manage to show the preview in a new window, i will still have the duplicating posts problem if i save it before previewing it.

It seems like you are trying to make your show action work too hard.
If you are dead set against saving the data, you'll probably have to
come up with a Javascript preview. On the other hand, creating a one- off action like 'preview' makes a ton of sense here because its job
would be simply to preview some disposable data.

Again, I don't know your requirements, but my philosophy is to write
really stupid controllers. If something like the show action tries to
get too clever, it's a code smell to me.

Ok I solved this by using a combination of suggestions above and to cover my specific requirements.

Save as Draft and Publish are input buttons. Preview is a link_to_function with the following javascript function.

function preview_post(element){   element.up('#post_form').writeAttribute('target', '_blank');   element.up('#post_form').down('#preview_value').value += 1;   element.up('#post_form').submit();   element.up('#post_form').writeAttribute('target', false); }

The above function is called only if Preview is clicked and adds a target = "_blank" attribute to the form in the page. This ensures that the form submit opens a new window with the response(which is in this case a preview). I then set the same attribute to 'false' so that subsequent form submissions dont open in a new window except if its preview again.

During the course of solving this, I decided i HAD to save before previewing for requirement reasons. Im updating a hidden 'preview' attribute (with a default value of 0) in the form to 1 so that back in the controller, i check for this value and if its 1, I save and render 'show' (which opens in a new window). Controller is quite complicated so im just gonna paste the code below and let you understand for yourself.

def create     @post = current_account.posts.build(params[:post])     if ((params[:preview].to_i == 1) && !((params[:save] == "Save") || params["save.x"]) && !((params[:publish] == "Publish") || params ["publish.x"]))       @post.status = "Draft"       @post.save(false)       render :action => 'show', :layout => 'application'     elsif ((params[:preview].to_i > 1) && !((params[:save] == "Save")

params["save.x"]) && !((params[:publish] == "Publish") || params

["publish.x"]))       render :action => 'show', :layout => 'application'     elsif ((params[:save] == "Save") || params["save.x"])       unless (params[:preview].to_i > 0)         if @post.save(false)           @post.update_attribute(:status, "Draft")           redirect_to posts_path         else           flash[:error] = "There was a problem creating your blog post. Please try again later."           render :action => 'new'         end       else         flash[:success] = "Post successfully created"         redirect_to posts_path       end     elsif ((params[:publish] == "Publish") || params["publish.x"])       unless (params[:preview].to_i > 0)         @post.status = "Active"         @post.published = 1         if @post.save           flash[:success] = "Post successfully published"           redirect_to post_path(@post)         else           render :action => 'new'         end       else         if @post.valid?           flash[:success] = "Post successfully published"           redirect_to post_path(@post)         else           render :action => 'new'         end       end     end   end

Thats quite a lot and quite ugly. Am refactoring it. But this is perfectly functional. Hope it helps someone.

Thank you all for your suggestions.

You should separate out publish & save to 2 different actions, also using state machine will help.

Thanks, Abhinav