Today I’m waiting at Così Cafe for our train to Washington DC for Agile 2007, so it’s time to add the next part of the “preview” feature. I have an internet connection, so I can afford to be a little more aggressive with the story I tackle next, because Google knows all. I think I’ll try to build the real-time preview I imagined earlier this morning. Now I don’t think I can spec-drive the real-time aspect of the preview, but I can certainly spec-drive showing the preview in a nearby window while I’m typing in the posting content text area. To get a sense for how that would look, I do some quick UI design.
In order to make room, I shrink the “content” text area and put the live preview, as it’s called, below it. This looks reasonable to me. In the process, I make one design decision: the <div> that contains the live preview text has the ID content-live-preview, which I’ll need to spec-drive the RJS I’ll have to write.
Speaking of spec-driving RJS, I have no idea how to do that, so I need to read a little about that before I get started.
Unfortunately, after reading Rails Recipes, I’m convinced that I need to implement this first before I can learn how to spec-drive it for next time. I find this happens a lot with Rails: it’s almost too simple. I paste an implementation into my view and inspect it manually. When I do that, it becomes clear that I need an action to handle the live preview, so although I didn’t drive the controller behavior with an automated test or spec, I’m certain I need something. Now I think I can spec-drive the controller behavior. Since this is familiar territory, this should go smoothly. Let’s see.
According to Rails Recipes_, all I need to do in my action is render the default template without a layout, which means I will need to move the live preview markup to a new @livepreview.rhtml@ template. I’ll do that first, to make sure it will be wired together properly.
After a few false starts, I have these specs:
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
assigns[:posting] = Posting.new(:content => "Some @textile@ content.")
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
assigns[:posting] = Posting.new(:content => nil)
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
This is the live_preview.rhtml template:
<div id="content-live-preview" class="entry">
<%= textilize(@posting.content) if @posting.content %>
</div>
That decidedly doesn’t work. When I consult Rails Recipes, I realize I should display the request parameter posting[content] and not the Ruby object @posting.content. For this, I need to learn how to stub the request parameters in the view. Evidently this is as simple as assigning to a params Hash. I fix the specs to match this new information.
describe "Live Preview for weblog postings" do
it "should have a section to display the live preview" do
params[:posting] = { :content => "Some @textile@ content." }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
it "should be able to handle nil content" do
params[:posting] = { :content => nil }
render "/postings/live_preview"
response.should have_tag("div#content-live-preview")
end
end
I’m on a green bar, but the UI doesn’t work. Firebug tells me that my controller live_preview method works, but the corresponding <div> is not updating. I don’t know how to spec-drive this, so I need to hack for a while. It’s 14.44; let’s see how long I need to hack.
Fortunately, only about five minutes. I had one <div> too many.
After fixing that, and making a few small changes, it looks like I’m done. I would have preferred to spec-drive the observe_field code, but at least I have less untested code than I used to. Now I can remove the preview button and the code that went with it. It served its purpose; now it can go to its rest.