= 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