apotomo 1.0.0.beta1 → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/Gemfile +1 -1
  2. data/README.rdoc +144 -90
  3. data/Rakefile +2 -1
  4. data/lib/apotomo.rb +1 -3
  5. data/lib/apotomo/rails/controller_methods.rb +16 -2
  6. data/lib/apotomo/request_processor.rb +1 -8
  7. data/lib/apotomo/version.rb +1 -1
  8. data/lib/apotomo/widget.rb +4 -2
  9. data/lib/apotomo/widget_shortcuts.rb +18 -5
  10. data/test/dummy/Rakefile +7 -0
  11. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  12. data/test/dummy/config.ru +4 -0
  13. data/test/dummy/config/database.yml +22 -0
  14. data/test/dummy/config/locales/en.yml +5 -0
  15. data/{rails/init.rb → test/dummy/db/test.sqlite3} +0 -0
  16. data/test/dummy/public/404.html +26 -0
  17. data/test/dummy/public/422.html +26 -0
  18. data/test/dummy/public/500.html +26 -0
  19. data/test/dummy/public/favicon.ico +0 -0
  20. data/test/dummy/public/javascripts/application.js +2 -0
  21. data/test/dummy/public/javascripts/controls.js +965 -0
  22. data/test/dummy/public/javascripts/dragdrop.js +974 -0
  23. data/test/dummy/public/javascripts/effects.js +1123 -0
  24. data/test/dummy/public/javascripts/prototype.js +6001 -0
  25. data/test/dummy/public/javascripts/rails.js +175 -0
  26. data/test/dummy/script/rails +6 -0
  27. data/test/fixtures/mouse/content.html.erb +1 -0
  28. data/test/fixtures/mouse/eating.html.erb +1 -0
  29. data/test/fixtures/mouse/educate.html.erb +1 -0
  30. data/test/fixtures/mouse/feed.html.erb +1 -0
  31. data/test/fixtures/mouse/make_me_squeak.html.erb +1 -0
  32. data/test/fixtures/mouse/posing.html.erb +1 -0
  33. data/test/fixtures/mouse/snuggle.html.erb +1 -0
  34. data/test/rails/controller_methods_test.rb +11 -4
  35. data/test/unit/request_processor_test.rb +1 -8
  36. data/test/unit/widget_shortcuts_test.rb +25 -1
  37. data/test/unit/widget_test.rb +10 -0
  38. metadata +51 -11
  39. data/Gemfile.lock +0 -98
  40. data/README +0 -141
  41. data/test/dummy/tmp/app/cells/mouse_widget.rb +0 -11
  42. data/test/dummy/tmp/test/widgets/mouse_widget_test.rb +0 -15
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "rails", "~> 3.0.0"
4
- gem "cells", :path => "/home/nick/projects/cells"#"3.4.1"
4
+ gem "cells", "~> 3.4"
5
5
  gem "onfire"
6
6
  gem "hooks", "~> 0.1.2"
7
7
 
@@ -1,141 +1,195 @@
1
- how does Apotomo handle Ajax and JavaScript?
2
- framework abstraction with JavascriptGenerator
1
+ = Apotomo
3
2
 
4
- no manual AJAX-routes for dozens of controllers, one generic route to rule them all
3
+ <em>Web Components for Rails.</em>
5
4
 
6
- = Apotomo
5
+ == Overview
7
6
 
8
- Apotomo is a stateful widget component framework for Rails.
7
+ Do you need an <b>interactive user interface</b> for your Rails application? A cool Rich Client Application with dashboards, portlets and AJAX, Drag&Drop and jQuery?
9
8
 
10
- It is good for you if you
11
- It is perfect for building AJAXed Rich Client Applications.
12
- It is suitable for iGoogle-like widgets, dashboards, portals, or even full-blown interactive web apps.
13
- * Development Teams, where separate teams can work on separate components
9
+ Is your controller gettin' fat? And your partial-helper-AJAX pile is getting out of control?
14
10
 
15
- == Widgets
11
+ Do you want a framework to make the implementation easier? <b>You want Apotomo.</b>
16
12
 
17
- === Widget Trees
13
+ == Apotomo
18
14
 
15
+ Apotomo is based on {Cells}[http://github.com/apotonick/cells], the popular View Components framework for Rails.
19
16
 
20
- == Events
17
+ 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.
21
18
 
22
- === Event Handlers
19
+ == Installation
23
20
 
24
- == AJAX
21
+ Easy as hell.
25
22
 
26
- == Statefulness vs. Stateless
23
+ === Rails 3
27
24
 
25
+ gem install apotomo
28
26
 
27
+ === Rails 2.3
29
28
 
30
- A stateful WHAT?
29
+ gem install apotomo -v 0.1.4
31
30
 
32
- Well, you know that. In Rails, people tend to have fat controllers. One controller action renders the complete page. While many programmers try to separate their code into helpers, controller actions, RJS logic and partials, it is still the controller that has to care about when to update what, and how!
31
+ Don't forget to load the gem in your app, either in your +Gemfile+ or +environment.rb+.
33
32
 
33
+ == Example!
34
34
 
35
- In Apotomo, it is the opposite. Widgets are small, autonomous components that look and feel like controllers. These tiny monsters listen to events and thus keep updating themselves on the page via AJAX. However, there's no JavaScript for you - they're pure Ruby.
35
+ A _shitty_ example is worse a _shitty_ framework, so let's choose wisely...
36
36
 
37
+ 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.
37
38
 
38
- == Man, gimme code!
39
+ Let's wrap that comments block in a widget.
39
40
 
40
- Let's use the famous and tiresome counter example.
41
+ == Generate
41
42
 
42
- class CounterCell < Apotomo::StatefulWidget
43
- transition :from => :display, :to => :increment
44
-
45
- def display
46
- respond_to_event :counterClick, :with => :increment
47
-
48
- @count = 0
49
- render # renders display.html.erb
50
- end
43
+ Go and generate a widget stub.
51
44
 
52
- def increment
53
- @count += 1 # @count is simply there - that's stateful.
54
- render :view => :display
55
- end
56
- end
57
-
58
- Since this widget calls <tt>render</tt> it surely needs a view.
45
+ $ rails generate apotomo:widget CommentsWidget display process --haml
46
+ create app/cells/
47
+ create app/cells/comments_widget
48
+ create app/cells/comments_widget.rb
49
+ create app/cells/comments_widget/display.html.haml
50
+ create app/cells/comments_widget/process.html.haml
51
+ create test/widgets/comments_widget_test.rb
52
+
53
+ Nothing special.
54
+
55
+ == Plug it in
56
+
57
+ You now tell your controller about the new widget.
58
+
59
+ class PostsController < ApplicationController
60
+ include Apotomo::Rails::ControllerMethods
61
+
62
+ has_widgets do |root|
63
+ root << widget('comments_widget', 'post-comments', :display, :post => post)
64
+ end
65
+
66
+ The widget is named <tt>post-comments</tt> and jumps to the <tt>:display</tt> state per default. Assuming your controller has a method +post+ we pass the current post into the widget.
67
+
68
+ == Render the widget
59
69
 
60
- <!-- I'm display.html.erb -->
70
+ Rendering usually happens in your controller view, <tt>views/posts/show.haml</tt>, for instance.
71
+
72
+ %h1 @post.title
73
+
74
+ %p
75
+ @post.body
61
76
 
62
- <h1><%= @count %></h1>
63
-
64
- <%= link_to_event "Increment me!", :counterClick %>
77
+ %p
78
+ = render_widget 'post-comments'
65
79
 
80
+ == Write the widget
66
81
 
67
- We now plug the widget in a page.
82
+ A widget is like a cell which is like a mini-controller.
68
83
 
69
- class ExistingController < ApplicationController
70
- include Apotomo::ControllerHelper
84
+ class CommentsWidget < Apotomo::Widget
85
+ responds_to_event :submit, :with => :process
86
+
87
+ def display
88
+ @comments = param(:post).comments # the parameter from outside.
89
+ render
90
+ end
71
91
 
72
- def some_action
73
- # do what you want...
74
-
75
- use_widgets do |root|
76
- root << cell(:counter, :display, 'my_first_counter')
77
- end
78
- end
79
- end
92
+ The +display+ state collects comments to show and renders its view.
80
93
 
81
- As soon as the widget is rendered it will jump to its <tt>:display</tt> state which initializes the counter and renders itself.
94
+ And look at line 2 - if encountering a <tt>:submit</tt> event we invoke +process+, which is simply another state. How cool is that?
82
95
 
83
- Speaking of rendering: how do we place the widget in our controller?
96
+ def process
97
+ @comment = Comment.new(:post => param(:post))
98
+ @comment.update_attributes param(:comment) # like params[].
99
+
100
+ update :state => :display
101
+ end
102
+ end
84
103
 
85
- <!-- I'm some_action.html.erb -->
86
- <p>
87
- <%= render_widget 'my_first_counter' %>
88
104
 
89
- Ok, so this renders the widget in our controller page.
105
+ The event is processes with three steps in our widget:
90
106
 
91
- When clicking the link it updates automatically on the screen showing the incremented value. Wow.
107
+ * create the new comment
108
+ * re-render the +display+ state
109
+ * update itself on the page.
92
110
 
111
+ Apotomo helps you focusing on your app and takes away the pain of <b>action dispatching</b> and <b>page updating</b>.
93
112
 
94
- == That's cool!
95
- Yes, it is.
113
+ == Triggering events
96
114
 
97
- == Is there more?
98
- Apotomo got a load of features.
115
+ So how and where is the <tt>:submit</tt> event triggered?
99
116
 
100
- [Composability] Widgets can range from small standalone components to nested widget trees like dashboards or forms. Remember that each widget can have any number of children.
117
+ Take a look at the widget's view <tt>display.html.haml</tt>.
118
+ = widget_div do
119
+ %ul
120
+ - for c in @comments
121
+ %li c.text
122
+
123
+ - form_for :comment, @comment, :html => {"data-event-url" => url_for_event(:submit)} do |f|
124
+ = f.error_messages
125
+ = f.text_field :text
101
126
 
102
- [Bubbling events] Widgets can trigger events and watch out for them. While events bubble up from their triggering source to root they can be observed, providing a way to implement loosely coupled, distributable components.
103
-
104
- [Deep Linking] Apotomo deals with deep links (or url fragments) out-of-the-box while using SWFAddress. Components that register for deep linking will update as soon as the deep link changes. That makes your application back-button-safe!
127
+ = submit_tag "Don't be shy, comment!"
128
+
129
+ That's a lot of familiar view code, almost looks like a _partial_.
130
+
131
+ The view also contains a bit of JavaScript.
132
+
133
+ :javascript
134
+ var form = $("##{widget_id} form");
105
135
 
106
- [Testing] Needless to say that it is simply easier to test small components instead of fat do-it-all controllers.
136
+ form.submit(function() {
137
+ $.ajax({url: form.attr("data-event-url"), data: form.serialize()})
138
+ return false;
139
+ });
107
140
 
141
+ Triggering happens by sending an AJAX request to an address that the Apotomo helper +url_for_event+ generated for us.
108
142
 
109
- Give it a try- you will love the power and simplicity of real stateful components!
143
+ == Event processing
110
144
 
145
+ Now what happens when the event request is sent? Apotomo - again - does three things for you, it
111
146
 
112
- == Bugs, Community
113
- Please visit http://apotomo.de, the official project page with <em>lots</em> of examples.
114
- Join the mailing list and visit us in the IRC channel. More information is
115
- here[http://apotomo.de/download].
147
+ * <b>accepts the request</b> on a special event route it adds to your app
148
+ * <b>triggers the event</b> in your ruby widget tree, which will invoke the +#process+ state in our comment widget
149
+ * <b>sends back</b> the page updates your widgets rendered
116
150
 
151
+ == JavaScript Agnosticism
152
+
153
+ 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.
154
+
155
+ Also, updating the page is in your hands. Where Apotomo provides handy helpers as +#replace+, you could also <b>emit your own JavaScript</b>.
156
+
157
+ Look, +replace+ basically generates
158
+
159
+ $("post-comments").replaceWith(<the rendered view>);
160
+
161
+ If that's not what you want, do
162
+
163
+ def process
164
+ if param(:comment)[:text].explicit?
165
+ render :text => 'alert("Hey, you wanted to submit a pervert comment!");'
166
+ end
167
+ end
168
+
169
+ Apotomo doesn't depend on _any_ JS framework - you choose!
117
170
 
118
- == License
119
- Copyright (c) 2007-2010 Nick Sutterer <apotonick@gmail.com>
120
171
 
121
- The MIT License
172
+ == More features
122
173
 
123
- Permission is hereby granted, free of charge, to any person obtaining a copy
124
- of this software and associated documentation files (the "Software"), to deal
125
- in the Software without restriction, including without limitation the rights
126
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
127
- copies of the Software, and to permit persons to whom the Software is
128
- furnished to do so, subject to the following conditions:
174
+ There's even more, too much for a simple README.
129
175
 
130
- The above copyright notice and this permission notice shall be included in
131
- all copies or substantial portions of the Software.
176
+ [Statefulness] Deriving your widget from +StatefulWidget+ gives you free statefulness.
177
+ [Composability] Widgets can range from small standalone components to nested widget trees like complex dashboards.
178
+ [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.
179
+ [Testing] Apotomo comes with testing assertions to build rock-solid web components.
180
+ [Team-friendly] Widgets encourage encapsulation and help having different developers working on different components without getting out of bounds.
132
181
 
133
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
134
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
135
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
136
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
137
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
138
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
139
- THE SOFTWARE.
140
182
 
183
+ Give it a try- you will love the power and simplicity of real web components!
141
184
 
185
+
186
+ == Bugs, Community
187
+ Please visit http://apotomo.de, the official project page with <em>lots</em> of examples.
188
+
189
+ If you have questions, visit us in the IRC channel #cells at irc.freenode.org.
190
+
191
+ If you wanna be cool, subscribe to our feed[http://feeds.feedburner.com/Apotomo]!
192
+
193
+
194
+ == License
195
+ Copyright (c) 2007-2010 Nick Sutterer <apotonick@gmail.com> under the MIT License
data/Rakefile CHANGED
@@ -30,7 +30,8 @@ Jeweler::Tasks.new do |spec|
30
30
  spec.authors = ["Nick Sutterer"]
31
31
  spec.email = "apotonick@gmail.com"
32
32
 
33
- spec.files = FileList["[A-Z]*", File.join(*%w[{generators,lib,rails,app,config} ** *]).to_s]
33
+ spec.files = FileList["[A-Z]*", "lib/**/*", "config/*"] - ["Gemfile.lock"]
34
+ spec.test_files = FileList["test/**/*"] - FileList["test/dummy/tmp", "test/dummy/tmp/**/*", "test/dummy/log/*"]
34
35
 
35
36
  spec.add_dependency 'cells', '~> 3.4.2'
36
37
  spec.add_dependency 'rails', '>= 3.0.0'
@@ -46,12 +46,10 @@ module Apotomo
46
46
  end
47
47
 
48
48
  require 'apotomo/javascript_generator'
49
- Apotomo.js_framework = :prototype ### DISCUSS: move to rails.rb
49
+ Apotomo.js_framework = :jquery ### DISCUSS: move to rails.rb
50
50
 
51
51
  require 'apotomo/widget'
52
52
  require 'apotomo/stateful_widget'
53
53
  require 'apotomo/container_widget'
54
54
  require 'apotomo/widget_shortcuts'
55
55
  require 'apotomo/rails/controller_methods'
56
-
57
- #require 'apotomo/engine' if defined?(Rails)
@@ -21,6 +21,15 @@ require 'apotomo/rails/view_methods'
21
21
  end
22
22
 
23
23
  module ClassMethods
24
+ # Yields the root widget to setup your widgets for a controller. The block is executed in
25
+ # controller _instance_ context, so you may use instance methods and variables of the
26
+ # controller.
27
+ #
28
+ # Example:
29
+ # class PostsController < ApplicationController
30
+ # has_widgets do |root|
31
+ # root << widget(:comments_widget, 'post-comments', :user => @current_user)
32
+ # end
24
33
  def has_widgets(&block)
25
34
  has_widgets_blocks << block
26
35
  end
@@ -63,7 +72,7 @@ require 'apotomo/rails/view_methods'
63
72
  # Example:
64
73
  # def login
65
74
  # use_widgets do |root|
66
- # root << cell(:login, :form, 'login_box')
75
+ # root << widget(:login_widget, 'login_box')
67
76
  # end
68
77
  #
69
78
  # @box = render_widget 'login_box'
@@ -111,6 +120,11 @@ require 'apotomo/rails/view_methods'
111
120
 
112
121
  protected
113
122
 
123
+ def parent_controller
124
+ self
125
+ end
126
+
127
+
114
128
 
115
129
  # Renders the page updates through an iframe. Copied from responds_to_parent,
116
130
  # see http://github.com/markcatley/responds_to_parent .
@@ -158,4 +172,4 @@ with(window.parent) { setTimeout(function() { window.eval('#{escaped_script}');
158
172
  end
159
173
  end
160
174
  end
161
- end
175
+ end
@@ -22,14 +22,7 @@ module Apotomo
22
22
  end
23
23
 
24
24
  def attach_stateless_blocks_for(blocks, root, controller)
25
- blocks.each do |blk|
26
- if blk.arity == 1
27
- #blk.call(root) and next # fixes misbehaviour in ruby 1.8.
28
- root.instance_exec(root, &blk) and next
29
- end
30
- ### FIXME: use Widget.has_widgets func.
31
- root.instance_exec(root, controller, &blk)
32
- end
25
+ blocks.each { |blk| controller.instance_exec(root, &blk) }
33
26
  end
34
27
 
35
28
  def flushed_root
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Apotomo
4
- VERSION = '1.0.0.beta1'
4
+ VERSION = '1.0.0.beta2'
5
5
  end
@@ -70,6 +70,8 @@ module Apotomo
70
70
 
71
71
  @cell = self ### DISCUSS: needed?
72
72
 
73
+ @params = parent_controller.params.dup.merge(opts)
74
+
73
75
  run_hook(:after_initialize, id, start_state, opts)
74
76
  end
75
77
 
@@ -88,7 +90,7 @@ module Apotomo
88
90
  end
89
91
 
90
92
  def unfreezable_ivars
91
- [:@childrenHash, :@children, :@parent, :@parent_controller, :@_request, :@_config, :@cell, :@invoke_block, :@rendered_children, :@page_updates, :@opts,
93
+ [:@childrenHash, :@children, :@parent, :@parent_controller, :@_request, :@_config, :@cell, :@invoke_block, :@rendered_children, :@page_updates, :@opts, :@params,
92
94
  :@suppress_javascript ### FIXME: implement with ActiveHelper and :locals.
93
95
 
94
96
  ]
@@ -250,7 +252,7 @@ module Apotomo
250
252
 
251
253
  ### DISCUSS: use #param only for accessing request data.
252
254
  def param(name)
253
- params[name]
255
+ @params[name]
254
256
  end
255
257
 
256
258
 
@@ -1,17 +1,30 @@
1
1
  module Apotomo
2
2
  # Shortcut methods for creating widget trees.
3
3
  module WidgetShortcuts
4
- # Creates an instance of <tt>class_name</tt> with the id <tt>id</tt> and start state <tt>state</tt>.
5
- # Default start state is <tt>:display</tt>.
6
- # Yields self if a block is passed.
4
+ # Shortcut for creating an instance of +class_name+ named +id+.
5
+ # If +start_state+ is omited, :display is default. Yields self.
6
+ #
7
7
  # Example:
8
- # widget(:form, 'uploads', :build_form) do |form|
9
- # form << widget(:upload_field)
10
8
  #
9
+ # widget(:comments_widget, 'post-comments')
10
+ # widget(:comments_widget, 'post-comments', :user => @current_user)
11
+ #
12
+ # Start state is <tt>:display</tt>, whereas the latter also populates @opts.
13
+ #
14
+ # widget(:comments_widget, 'post-comments', :reload)
15
+ # widget(:comments_widget, 'post-comments', :reload, :user => @current_user)
16
+ #
17
+ # Explicitely sets the start state.
18
+ #
11
19
  # You can also use namespaces.
12
20
  #
13
21
  # widget('jquery/tabs', 'panel')
14
22
  def widget(class_name, id, state=:display, *args)
23
+ if state.kind_of?(Hash)
24
+ args << state
25
+ state = :display
26
+ end
27
+
15
28
  object = constant_for(class_name).new(parent_controller, id, state, *args)
16
29
  yield object if block_given?
17
30
  object