apotomo 1.0.0.beta1 → 1.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Gemfile +1 -1
- data/README.rdoc +144 -90
- data/Rakefile +2 -1
- data/lib/apotomo.rb +1 -3
- data/lib/apotomo/rails/controller_methods.rb +16 -2
- data/lib/apotomo/request_processor.rb +1 -8
- data/lib/apotomo/version.rb +1 -1
- data/lib/apotomo/widget.rb +4 -2
- data/lib/apotomo/widget_shortcuts.rb +18 -5
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/{rails/init.rb → test/dummy/db/test.sqlite3} +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/prototype.js +6001 -0
- data/test/dummy/public/javascripts/rails.js +175 -0
- data/test/dummy/script/rails +6 -0
- data/test/fixtures/mouse/content.html.erb +1 -0
- data/test/fixtures/mouse/eating.html.erb +1 -0
- data/test/fixtures/mouse/educate.html.erb +1 -0
- data/test/fixtures/mouse/feed.html.erb +1 -0
- data/test/fixtures/mouse/make_me_squeak.html.erb +1 -0
- data/test/fixtures/mouse/posing.html.erb +1 -0
- data/test/fixtures/mouse/snuggle.html.erb +1 -0
- data/test/rails/controller_methods_test.rb +11 -4
- data/test/unit/request_processor_test.rb +1 -8
- data/test/unit/widget_shortcuts_test.rb +25 -1
- data/test/unit/widget_test.rb +10 -0
- metadata +51 -11
- data/Gemfile.lock +0 -98
- data/README +0 -141
- data/test/dummy/tmp/app/cells/mouse_widget.rb +0 -11
- data/test/dummy/tmp/test/widgets/mouse_widget_test.rb +0 -15
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -1,141 +1,195 @@
|
|
1
|
-
|
2
|
-
framework abstraction with JavascriptGenerator
|
1
|
+
= Apotomo
|
3
2
|
|
4
|
-
|
3
|
+
<em>Web Components for Rails.</em>
|
5
4
|
|
6
|
-
|
5
|
+
== Overview
|
7
6
|
|
8
|
-
|
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
|
-
|
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
|
-
|
11
|
+
Do you want a framework to make the implementation easier? <b>You want Apotomo.</b>
|
16
12
|
|
17
|
-
|
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
|
-
|
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
|
-
|
19
|
+
== Installation
|
23
20
|
|
24
|
-
|
21
|
+
Easy as hell.
|
25
22
|
|
26
|
-
|
23
|
+
=== Rails 3
|
27
24
|
|
25
|
+
gem install apotomo
|
28
26
|
|
27
|
+
=== Rails 2.3
|
29
28
|
|
30
|
-
|
29
|
+
gem install apotomo -v 0.1.4
|
31
30
|
|
32
|
-
|
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
|
-
|
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
|
-
|
39
|
+
Let's wrap that comments block in a widget.
|
39
40
|
|
40
|
-
|
41
|
+
== Generate
|
41
42
|
|
42
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
<%= link_to_event "Increment me!", :counterClick %>
|
77
|
+
%p
|
78
|
+
= render_widget 'post-comments'
|
65
79
|
|
80
|
+
== Write the widget
|
66
81
|
|
67
|
-
|
82
|
+
A widget is like a cell which is like a mini-controller.
|
68
83
|
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
105
|
+
The event is processes with three steps in our widget:
|
90
106
|
|
91
|
-
|
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
|
-
==
|
95
|
-
Yes, it is.
|
113
|
+
== Triggering events
|
96
114
|
|
97
|
-
|
98
|
-
Apotomo got a load of features.
|
115
|
+
So how and where is the <tt>:submit</tt> event triggered?
|
99
116
|
|
100
|
-
|
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
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
172
|
+
== More features
|
122
173
|
|
123
|
-
|
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
|
-
|
131
|
-
|
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
|
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'
|
data/lib/apotomo.rb
CHANGED
@@ -46,12 +46,10 @@ module Apotomo
|
|
46
46
|
end
|
47
47
|
|
48
48
|
require 'apotomo/javascript_generator'
|
49
|
-
Apotomo.js_framework = :
|
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 <<
|
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
|
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
|
data/lib/apotomo/version.rb
CHANGED
data/lib/apotomo/widget.rb
CHANGED
@@ -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
|
-
#
|
5
|
-
#
|
6
|
-
#
|
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
|