= Apotomo
Web Components for Rails.
== Overview
Do you need an interactive user interface for your Rails application? A cool Rich Client Application with dashboards, portlets and AJAX, Drag&Drop and jQuery?
Is your controller gettin' fat? And your partial-helper-AJAX pile is getting out of control?
Do you want a framework to make the implementation easier? You want Apotomo.
== Apotomo
Apotomo is based on {Cells}[http://github.com/apotonick/cells], the popular View Components framework for Rails.
It gives you widgets and encapsulation, bubbling events, AJAX page updates, rock-solid testing and more. Check out http://apotomo.de for a bunch of tutorials and a nice web 2.0 logo.
== Installation
Easy as hell.
=== Rails 3
gem install apotomo
=== Rails 2.3
gem install apotomo -v 0.1.4
Don't forget to load the gem in your app, either in your +Gemfile+ or +environment.rb+.
== Example!
A _shitty_ example is worse than a _shitty_ framework, so let's choose wisely...
Say you had a blog application. The page showing the post should have a comments block, with a list of comments and a form to post a new comment. Submitting should validate and send back the updated comments list, via AJAX.
Let's wrap that comments block in a widget.
== Generate
Go and generate a widget stub.
$ rails generate apotomo:widget CommentsWidget display write -e haml
create app/cells/
create app/cells/comments_widget
create app/cells/comments_widget.rb
create app/cells/comments_widget/display.html.haml
create app/cells/comments_widget/write.html.haml
create test/widgets/comments_widget_test.rb
Nothing special.
== Plug it in
You now tell your controller about the new widget.
class PostsController < ApplicationController
include Apotomo::Rails::ControllerMethods
has_widgets do |root|
root << widget('comments_widget', 'post-comments', :post => @post)
end
The widget is named post-comments. We pass the current post into the widget - the block is executed in controller instance context, that's were @post comes from. Handy, isn't it?
== Render the widget
Rendering usually happens in your controller view, views/posts/show.html.haml, for instance.
%h1 @post.title
%p
@post.body
%p
= render_widget 'post-comments'
== Write the widget
A widget is like a cell which is like a mini-controller.
class CommentsWidget < Apotomo::Widget
responds_to_event :post
def display(args)
@comments = args[:post].comments # the parameter from outside.
render
end
Having +display+ as the default state when rendering, this method collects comments to show and renders its view.
And look at line 2 - if encountering a :post event we invoke +#post+, which is simply another state. How cool is that?
def post(evt)
@comment = Comment.new(:post_id => evt[:post_id])
@comment.update_attributes evt[:comment] # a bit like params[].
update :state => :display
end
end
The event is processed with three steps in our widget:
* create the new comment
* re-render the +display+ state
* update itself on the page
Apotomo helps you focusing on your app and takes away the pain of action dispatching and page updating.
== Triggering events
So how and where is the :post event triggered?
Take a look at the widget's view display.html.haml.
= widget_div do
%ul
- for c in @comments
%li c.text
- form_for :comment, @comment, :url => url_for_event(:post), :remote => true do |f|
= f.error_messages
= f.text_field :text
= submit_tag "Don't be shy, comment!"
That's a lot of familiar view code, almost looks like a _partial_.
As soon as the form is submitted, the form gets serialized and sent using the standard Rails mechanisms. The interesting part here is the endpoint URL returned by #url_for_event as it will trigger an Apotomo event.
== Event processing
Now what happens when the event request is sent? Apotomo - again - does three things for you, it
* accepts the request on a special event route it adds to your app
* triggers the event in your ruby widget tree, which will invoke the +#post+ state in our comment widget
* sends back the page updates your widgets rendered
== JavaScript Agnosticism
In this example, we use jQuery for triggering. We could also use Prototype, RightJS, YUI, or a self-baked framework, that's up to you.
Also, updating the page is in your hands. Where Apotomo provides handy helpers as +#replace+, you could also emit your own JavaScript.
Look, +replace+ basically generates
$("post-comments").replaceWith();
If that's not what you want, do
def post(evt)
if evt[:comment][:text].explicit?
render :text => 'alert("Hey, you wanted to submit a pervert comment!");'
end
end
Apotomo doesn't depend on _any_ JS framework - you choose!
== Testing
Apotomo comes with its own test case and assertions to build rock-solid web components.
class CommentsWidgetTest < Apotomo::TestCase
has_widgets do |root|
root << widget(:comments_widget, 'me', :post => @pervert_post)
end
def test_render
render_widget 'me'
assert_select "li#me"
trigger :post, :comment => {:text => "Sex on the beach"}
assert_response 'alert("Hey, you wanted to submit a pervert comment!");'
end
end
You can render your widgets, spec the markup, trigger events and assert the event responses, so far. If you need more, let us know!
== More features
There's even more, too much for a simple README.
[Statefulness] Deriving your widget from +StatefulWidget+ gives you free statefulness.
[Composability] Widgets can range from small standalone components to nested widget trees like complex dashboards.
[Bubbling events] Events bubble up from their triggering source to root and thus can be observed, providing a way to implement loosely coupled, distributable components.
[Team-friendly] Widgets encourage encapsulation and help having different developers working on different components without getting out of bounds.
Give it a try- you will love the power and simplicity of real web components!
== Bugs, Community
Please visit http://apotomo.de, the official project page with lots of examples.
If you have questions, visit us in the IRC channel #cells at irc.freenode.org.
If you wanna be cool, subscribe to our feed[http://feeds.feedburner.com/Apotomo]!
== License
Copyright (c) 2007-2011 Nick Sutterer under the MIT License