README.md in action_widget-0.5.1 vs README.md in action_widget-0.6.0.pre
- old
+ new
@@ -1,8 +1,25 @@
# ActionWidget
-TODO: Write a gem description
+`ActionWidget` is a light-weight widget system for [Ruby on
+Rails](http://rubyonrails.com) and [Middleman](http://middlemanapp.com). It is
+essentially a minimal tool set for building desktop-like UI components. The
+main idea behind `ActionWidget` is the separation of the concept of an UI
+component and its representation. While the representation of component might
+easily change over time, the concept of a component is unlikely to change
+significantly. Think of a button for instance: Most developers will agree that
+a button is conceptually something that has a caption and when clicked triggers
+an action. When we think about the visual representation of a button, however,
+things get more complicated. While most people will agree to a certain degree
+on what can visually be considered a button and what is something else, people
+tend to have different ideas about a button's exact representation. There are
+buttons with icons, without icons, round ones, rectangular ones, and so on.
+Despite their different appearances, the functionality and in this sense the
+component's concept stays the same: when clicked an action is triggered.
+ActionWidget provides developers with a tool set that helps them to strictly
+decouple a component's concept from its representation to support future
+change.
## Installation
Add this line to your application's Gemfile:
@@ -16,10 +33,277 @@
$ gem install action_widget
## Usage
-TODO: Write usage instructions here
+`ActionWidget` can be used to build arbitrarily complex view components. To
+illustrate the basic usage of `ActionWidget`, however, we start with a simple
+example, a widget for representing a button. We then continue with widgets that
+except blocks. We will use a widget representing panels as an example. Finally,
+we see discuss how to build widgets that utilize widgets themselves for
+constructing navigation components.
+
+### Simple Widgets
+
+The goal of this section is to build a widget that represents a button. The
+button we are designing must have a `caption` and a `type`. The type can either
+be `regular`, `accept`, or `cancel`. The button further must have a specified
+`size`, which can be `small`, `medium`, or `large`. Finally, the button
+requires a `target` that defines the resource it links to. `ActionWidget`
+compentens utilize [SmartProperties](http://github.com/t6d/smart_properties) to
+define attributes that can be configured to automatically enforce these
+constraints and provide sensible defaults.
+
+In the example below, we simple use an `<a>` tag to represent a button. The
+attributes `size` and `type` are simply translated into CSS classes. The
+`caption` will be used as the text encolsed by the `<a>` tag and the `target`
+will be used as the value the the `<a>` tag's `href` attribute.
+
+```ruby
+class ButtonWidget < ActionWidget::Base
+ property :caption,
+ converts: :to_s,
+ required: true
+
+ property :target,
+ converts: :to_s,
+ accepts: lambda { |uri| URI.parse(uri) rescue false },
+ required: true
+
+ property :type,
+ converts: :to_sym,
+ accepts: [:regular, :accept, :cancel],
+ default: :regular
+
+ property :size,
+ converts: :to_sym,
+ accepts: [:small, :medium, :large],
+ default: :medium
+
+ def render
+ content_tag(:a, caption, href: target, class: css_classes)
+ end
+
+protected
+
+ def css_classes
+ css_classes = ['btn']
+ css_classes << "btn-#{size}" unless size == :regular
+ css_classes << "btn-#{type}" unless type == :medium
+ css_classes
+ end
+end
+```
+
+By convention, a widget's class name should end in "Widget". This way,
+`ActionWidget` automatically generates `ActionView` helper methods for more
+convenient instantiation and rendering of a widget.
+
+In our example, the widget can be instantiated by simply calling the helper
+method `button_widget` and providing it with all necessary attributes:
+
+```erb
+<%= button_widget caption: 'Go to Admin Area', size: :small, target: '/admin' %>
+```
+
+Instead of using the provided helper method, a widget can always be instantiated
+manually:
+
+```erb
+<%= ButtonWidget.new(self, caption: 'Go to Admin Area', size: :small, target: '/admin').render %>
+```
+
+In both cases, the resulting HTML looks as follows:
+
+```html
+<a class="btn btn-small" href="/admin">Go to Admin Area</a>
+```
+
+### Widgets that Accept Blocks
+
+The panel widget we are building requires a `title` and a block that defines the
+widgets content.
+
+```ruby
+require 'action_widget'
+
+class PanelWidget < ActionWidget::Base
+ property :title, required: true, converts: :to_s
+
+ def render(&block)
+ content_tag(:div, class: 'panel') do
+ content_tag(:h2, title, class: 'title') +
+ content_tag(:div, class: 'content', &block)
+ end
+ end
+end
+```
+
+Again, the automatically generated helper method, `#panel_widget` in this case,
+can be used to instantiate and render the widget:
+
+```erb
+<%= panel_widget title: "Important Notice" do %>
+ The system will be down for maintanence today.
+<% end %>
+```
+
+Executing the code above results in the follwing HTML:
+
+```html
+<div class="panel">
+ <h2 class="title">Important Notice</h2>
+ <div class="content">
+ The system will be down for maintanence today.
+ </div>
+</div>
+```
+
+Since widgets are simple Ruby classes, they naturally support inheritance.
+Let's assume we require a special panel widget for sidebars that renders a
+different header. There are two options:
+
+1. we can provide the tag that is chosen for the header as a property, or
+2. we restructure the `PanelWidget` class and then subclass it.
+
+Let's take the second approach and extract the header rendering and the content
+rendering into their own methods so we can overwrite either one of them in a
+potential subclass.
+
+```ruby
+class PanelWidget < ActionWidget::Base
+ property :title, required: true, converts: :to_s
+
+ def render(&block)
+ header + content(&block)
+ end
+
+ protected
+
+ def header
+ content_tag(:h2, title)
+ end
+
+ def content(&block)
+ content_tag(:div, &block)
+ end
+end
+```
+
+After this refactoring, we are able to subclass `PanelWidget` and customize the
+`header` method:
+
+```ruby
+class SidebarPanelWidget < PanelWidget
+ protected
+
+ def header
+ content_tag(:h3, title)
+ end
+end
+```
+
+### Nested Widgets
+
+Let's assume we want to implement a widget that simplifies the rendering of
+navigational menus. The widget only exposes one property, `orientation`, which
+can either be `horizontal` or `vertical`.
+
+```ruby
+class MenuWidget < ActionWidget::Base
+ property :orientation,
+ accepts: [:horizontal, :vertical],
+ converts: :to_sym,
+ default: :horizontal,
+ required: true
+
+ def render(&block)
+ content_tag(:nav, class: orientation) do
+ content_tag(:ul) do
+ capture(self, &block)
+ end
+ end
+ end
+
+ def item(caption, target)
+ content_tag(:li) do
+ content_tag(:a, caption, href: target)
+ end
+ end
+
+ def submenu(caption, &block)
+ content_tag(:li) do
+ content_tag(:span, caption) +
+ self.class.new(view, orientation: orientation).render(&block)
+ end
+ end
+end
+```
+
+The following example demonstrates how to use this widget:
+
+```erb
+<%= menu_widget do |m| %>
+ <%= m.item "Dashboard", "/" %>
+ <%= m.submenu "Admin" do |m| %>
+ <%= m.item "Manage Users", "/admin/users" %>
+ <%= m.item "Manage Groups", "/admin/groups" %>
+ <% end %>
+<% end %>
+```
+
+Executing the code above, will result in the following HTML being generated:
+
+```html
+<nav class="horizontal">
+ <ul>
+ <li> <a href="/">Dashboard</a> </li>
+ <li>
+ <span>Admin</span>
+ <nav class="horizontal">
+ <ul>
+ <li><a href="/admin/users">Manage Users</a></li>
+ <li><a href="/admin/groups">Manage Groups</a></li>
+ </ul>
+ </nav>
+ </li>
+ </ul>
+</nav>
+```
+
+### Option Capturing and Positional Argument Forwarding
+
+`ActionWidget` instances capture all initializer keyword
+arguments that do not correspond to a property in a general `options` hash.
+All positional arguments supplied to an autogenerated helper are
+forwarded to the `render` method.
+
+```ruby
+class ParagraphWidget < ActionWidget::Base
+ def render(content, &block)
+ content = capture(&block) if block
+ content_tag(:p, content, class: options[:class])
+ end
+end
+```
+
+This widget can be used in the following two ways:
+
+```erb
+<%= paragraph_widget("Some content", class: 'important') %>
+
+<%= paragraph_widget(class: 'important') do %>
+ Some content
+<% end %>
+```
+
+In both cases, the resulting HTML reads as follows:
+
+```html
+<p class="important">
+ Some content
+</p>
+```
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)