strapyourself.in and flouri.sh
Writing view helpers with 'yield'
Ever wanted to pass in blocks of view code to a helper? Think of the power! Say we want to make a helper method which surrounds whatever block you pass in with a firefox html designer iframe, here's how I'd imagine a user would want to use it:
<% html_editor(:width => 600, :height => 400) do %> <p><b>This html should be editable by the user!</b></p> <% end %>
Here's how you could write such a helper function:
class ApplicationHelper def html_editor(options = {}, &block) concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID=myEditor>") yield concat("</IFRAME>") concat("<script>frames.myEditor.document.designMode = 'On'</script>") end end
Notice that I pass the block in as the last parameter "&block", but I never use it in the function! Ruby doesn't require that you pass the name of the block in when you yield, because it only supports the passing in of 0 or 1 blocks to a function (hence it always knows which one you're talking about). I like to pass it in as a named method parameter because it makes the method signature more clear.
Now let's improve on our initial version, by passing an object back to the view. This is exactly the way form_for passes back a |form| object to the view. Our updated use case is as follows:
<% html_editor(:width => 600, :height => 400) do |editor| %> <p><b>This html should be editable by the user!</b></p> <% editor.command_button("Bold", "bold") %> <% end %>
The "command_button" method above should insert a standard html button with the text "Bold" that executes the "bold" command on the selected text range inside the html editor. Let's see how this can be implemented:
class JsHtmlEditor attr_reader :output def command_button(name, command) @output ||= "" @output << "<button onclick='javascript: frames.myEditor.document.selection.createRange().execCommand(\"#{command}\")'> #{name}</button>\n" end end module ApplicationHelper def html_editor(options = {}, &block) editor = JsHtmlEditor.new("myEditor") concat("<IFRAME WIDTH=#{options[:width] || 400} HEIGHT=#{options[:height || 200]} ID='myEditor'>") yield editor concat("</IFRAME>") concat("<script>frames.myEditor.document.designMode = 'On'</script>") concat(editor.output) unless editor.output.blank? # stick the user buttons at the bottom end end
Pretty cool huh? When we create a new editor and yield it, we make its methods accessible to the view block. In this example, the user can execute editor.command_button whereever they want inside the block, but we collect the output of those statements and force them all to the bottom of the iframe (where they probably belong).
Another common use of blocks inside view helpers is simply to control whether or not they get yielded at all. If you don't call yield, the block never executes and never gets rendered. Often times we have complex conditions determining when a certain block of code should be rendered, and this approach can clean that up immensely.
Sorry, comments are closed for this article.