Skip to content
Browse files

widget method takes instance or class; documentation and refactoring

  • Loading branch information...
1 parent 5526890 commit 0cacf53d2834d107980ef43aca9cf11950ecb606 @alexch alexch committed Apr 20, 2009
Showing with 148 additions and 92 deletions.
  1. +66 −0 api-announce.txt
  2. +0 −53 api-changes.txt
  3. +25 −11 lib/erector/widget.rb
  4. +57 −28 spec/erector/widget_spec.rb
View
66 api-announce.txt
@@ -0,0 +1,66 @@
+Inspired by GoGaRuCo, I just took a shot at rewriting the API. The basic concept is
+to clarify the lifecycle of a widget, so "new" accepts permanent state (like "assigns"
+variables) and "to_s" accepts temporary, rendering state (output stream, helpers,
+prettyprinting). This lets you do things like make collections of widgets in once place
+in your code and render them in another place, and basically establish a more coherent
+API than the 8 different ways I've seen people doing nested widgets (or, more likely,
+avoiding nesting them cause it's too confusing.)
+
+I also renamed "render" to "write", which removes confusion/ambiguity
+with Rails' "render" method and concept, and also allows "render :partial" to be made
+to work (though I haven't done that yet). Separating the two types of state also allowed
+me to unify a few concepts and remove some code, especially around prettyprinting.
+(I also fixed a couple of bugs along the way.)
+
+When we're outside a widget, the pattern is:
+
+ w = DateWidget.new(:when => Time.now, :title => "Nap Time") # assigns
+ puts w.to_s(:helpers => some_rails_view)
+
+When we're inside a widget we have a few more options. All of these ways inherit
+the rendering state from the parent widget if possible (like output, helpers, and
+indent level) and emit text onto the same stream for better performance.
+
+(1)
+ w = DateWidget.new(:when => Time.now, :title => "Nap Time")
+ w.write_via(self)
+
+(2)
+ widget DateWidget, :when => Time.now, :title => "Nap Time"
+
+(3)
+ w = DateWidget.new(:when => Time.now, :title => "Nap Time")
+ widget w
+
+(3b)
+ widget DateWidget.new(:when => Time.now, :title => "Nap Time")
+
+
+((3) and (3b) are the same but I thought I'd show how it looks when you inline it.)
+
+So what does everyone think? I think it might be too much to support both "widget"
+and "write_via", but since one actually calls the other under the hood, it's not
+too big a commitment to keep them both around in the public API. I'm not in love with
+the new names, either; other possibilites are "emit" instead of render and ""
+
+I've done these in a fork on GitHub (my first!). Please check it out and tell me what you think.
+
+Once we agree that it's a righteous change, I figure we will carefully pull it into
+the main repo and increment the minor version number and make a good HOWTO so people
+aren't taken off guard when their code breaks. I've put in what I hope are good
+deprecation warnings too (that actually give suggestions and say which class
+needs to be fixed).
+
+Brian, I got the Rails stuff working for 2.3.2 but I'll need your help getting it
+into the various Rails branches. And I don't really understand the plan for render_partial.
+Shouldn't calling render_partial on a widget just call render (now "write") on that widget?
+Why do we need/want a separate method called render_partial on the widget?
+
+---
+
+Widget#inspect just calls to_s. Is this useful? Do we want some other debugging info to appear instead?
+
+Widget#join -- is anyone using this? Also the rdoc says it works on "strings, which are quoted and output"
+-- but this is not really true, right? They're not quoted, just html-escaped like any text.
+
+Widget#after_initialize -- is anyone using this feature? It's not tested.
View
53 api-changes.txt
@@ -1,53 +0,0 @@
-API
-
-write (emit?)
- writes text
-
-write_via
- takes a widget as parent
-
-write_widget
- inherits parent
- inherits output, pretty, indent, helpers from parent
-
-inspect
- renders the to_s
-
-writing a tag
- fails if output is nil, informing the developer to call write instead of render now
-
-prepare
-
-ICKY
-
-action_view_template_handler.rb
- render_method = is_partial ? 'render_partial' : 'write'
-
-What's the deal with "def render_partial"?
-
-MISC
-
-indentation clarification (indent was a method and a variable)
- DONE
-
-rename/clarify enable_prettyprint to prettyprint
- DONE
-
-nested widgets
- DONE
-
-
-__element__
- complains if you pass both text and a block
-
-join
- "which are quoted and output" -- test this
-
-to_s
- # The @__to_s variable is used as a cache.
- # If it's useful we should add a test for it. -ac
-
- to_s(write_method_name) is not tested
-
-after_initialize
- is anyone using this?
View
36 lib/erector/widget.rb
@@ -193,23 +193,34 @@ def write
end
# To call one widget from another, inside the parent widget's write method, instantiate the child widget and call
- # its +write_via+ method, passing in +self+ (or self.output if you prefer). This assures that the same output string
+ # its +write_via+ method, passing in +self+. This assures that the same output string
# is used, which gives better performance than using +capture+ or +to_s+.
- def write_via(widget)
- @parent = widget
- prepare(widget.output, widget.prettyprint, widget.indentation, widget.helpers)
+ def write_via(parent)
+ @parent = parent
+ prepare(parent.output, parent.prettyprint, parent.indentation, parent.helpers)
write
end
- # TODO: deprecate?
- # Convenience method for on-the-fly widgets. This is a way of making
- # a sub-widget which still has access to the methods of the parent class.
+ # Emits a (nested) widget onto the current widget's output stream. Accepts either
+ # a class or an instance. If the first argument is a class, then the second argument
+ # is a hash used to populate its instance variables. If the first argument is an
+ # instance then the hash must be unspecified (or empty).
+ #
+ # The sub-widget will have access to the methods of the parent class, via some method_missing
+ # magic and a "parent" pointer.
+ #
# This is an experimental erector feature which may disappear in future
# versions of erector (see #widget in widget_spec in the Erector tests).
- def widget(widget_class, assigns={}, &block)
- child = widget_class.new(assigns, &block)
- child.prepare(output, @prettyprint, @indentation, helpers)
- child.write
+ def widget(target, assigns={}, &block)
+ child = if target.is_a? Class
+ target.new(assigns, &block)
+ else
+ unless assigns.empty?
+ raise "Unexpected second parameter. Did you mean to pass in variables when you instantiated the #{target.class.to_s}?"
+ end
+ target
+ end
+ child.write_via(self)
end
# (Should we make this hidden?)
@@ -467,6 +478,9 @@ def __element__(tag_name, *args, &block)
end
attributes ||= {}
open_tag tag_name, attributes
+ if block && value
+ raise ArgumentError, "You can't pass both a block and a value to #{tag_name} -- please choose one."
+ end
if block
instance_eval(&block)
else
View
85 spec/erector/widget_spec.rb
@@ -60,7 +60,64 @@ def widget.alternate_write
end
end
+ describe '#widget' do
+ context "basic nesting" do
+ before do
+ class Parent < Erector::Widget
+ def write
+ text 1
+ widget Child do
+ text 2
+ third
+ end
+ end
+
+ def third
+ text 3
+ end
+ end
+
+ class Child < Erector::Widget
+ def write
+ super
+ end
+ end
+ end
+
+ it "renders nested widgets in the correct order" do
+ Parent.new.to_s.should == '123'
+ end
+ end
+
+ end
+
describe "#widget" do
+ class Orphan < Erector::Widget
+ def write
+ p name
+ end
+ end
+
+ context "when passed a class" do
+ it "renders it" do
+ Erector::Widget.new do
+ div do
+ widget Orphan, :name => "Annie"
+ end
+ end.to_s.should == "<div><p>Annie</p></div>"
+ end
+ end
+
+ context "when passed an instance" do
+ it "renders it" do
+ Erector::Widget.new do
+ div do
+ widget Orphan.new(:name => "Oliver")
+ end
+ end.to_s.should == "<div><p>Oliver</p></div>"
+ end
+ end
+
context "when nested" do
it "renders the tag around the rest of the block" do
parent_widget = Class.new(Erector::Widget) do
@@ -584,34 +641,6 @@ def write
end
end
- describe '#widget' do
- before do
- class Parent < Erector::Widget
- def write
- text 1
- widget Child do
- text 2
- third
- end
- end
-
- def third
- text 3
- end
- end
-
- class Child < Erector::Widget
- def write
- super
- end
- end
- end
-
- it "renders nested widgets in the correct order" do
- Parent.new.to_s.should == '123'
- end
- end
-
describe '#write_via' do
class A < Erector::Widget
def write

0 comments on commit 0cacf53

Please sign in to comment.
Something went wrong with that request. Please try again.