= 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 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 --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.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 :submit, :with => :write
def display
@comments = param(: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 :submit event we invoke +write+, which is simply another state. How cool is that?
def write
@comment = Comment.new(:post => param(:post))
@comment.update_attributes param(:comment) # like params[].
update :state => :display
end
end
The event is processes 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 :submit 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, :html => {"data-event-url" => url_for_event(:submit)} 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_.
The view also contains a bit of JavaScript.
:javascript
var form = $("##{widget_id} form");
form.submit(function() {
$.ajax({url: form.attr("data-event-url"), data: form.serialize()})
return false;
});
Triggering happens by sending an AJAX request to an address that the Apotomo helper +url_for_event+ generated for us.
== 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 +#process+ 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 write
if param(: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 :submit, :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-2010 Nick Sutterer under the MIT License