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