winton-rails_widget 1.0.2 → 1.1
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/README.markdown +20 -5
- data/lib/rails_widget.rb +99 -3
- data/lib/rails_widget/assets.rb +196 -86
- data/lib/rails_widget/widget.rb +148 -227
- data/lib/rails_widget/widgets.rb +188 -0
- metadata +6 -6
- data/tasks/rails_widget.rake +0 -31
data/README.markdown
CHANGED
@@ -1,8 +1,23 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
RailsWidget
|
2
|
+
===========
|
3
3
|
|
4
|
-
|
4
|
+
Allows you to group your client-side assets into distributable "widgets"
|
5
5
|
|
6
|
-
|
6
|
+
* Attach assets and render partials with a single <tt>widget</tt> call
|
7
|
+
* Configure widgets via an <tt>options.rb</tt> file
|
8
|
+
* Supported assets: flash, images, partials, javascripts, stylesheets, and textarea templates
|
7
9
|
|
8
|
-
|
10
|
+
|
11
|
+
Install
|
12
|
+
-------
|
13
|
+
|
14
|
+
script/plugin install git://github.com/winton/rails_widget.git
|
15
|
+
|
16
|
+
|
17
|
+
Documentation
|
18
|
+
--------------
|
19
|
+
|
20
|
+
[RDoc](http://wintoni.us/rails_widget) for more details.
|
21
|
+
|
22
|
+
|
23
|
+
##### Copyright © 2008 [Winton Welsh](mailto:mail@wintoni.us), released under the MIT license
|
data/lib/rails_widget.rb
CHANGED
@@ -1,7 +1,103 @@
|
|
1
|
+
# RailsWidget allows you to group your client-side assets into distributable "widgets"
|
2
|
+
#
|
3
|
+
# * Attach assets and render partials with a single <tt>widget</tt> call
|
4
|
+
# * Configure widgets via an <tt>options.rb</tt> file
|
5
|
+
# * Supports flash, images, javascripts, partials, stylesheets, and textarea templates
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
# === What is a widget?
|
9
|
+
# Each directory in <tt>app/widgets</tt> is considered a widget. Let's make a widget called <tt>alert</tt>.
|
10
|
+
#
|
11
|
+
# app/
|
12
|
+
# widgets/
|
13
|
+
# alert/
|
14
|
+
# options.rb
|
15
|
+
# flash/
|
16
|
+
# images/
|
17
|
+
# javascripts/
|
18
|
+
# alert.js
|
19
|
+
# init.js
|
20
|
+
# partials/
|
21
|
+
# _init.html.erb
|
22
|
+
# stylesheets/
|
23
|
+
# init.css
|
24
|
+
# style.css
|
25
|
+
# templates/
|
26
|
+
#
|
27
|
+
# <b>Init</b> files are rendered directly into the layout (inline, dynamically generated).
|
28
|
+
#
|
29
|
+
# ==== options.rb
|
30
|
+
# { :id => 'alert', :message => 'Hello world!', :color => 'red' }
|
31
|
+
#
|
32
|
+
# ==== javascripts/alert.js
|
33
|
+
# function alertWidget(options) {
|
34
|
+
# alert(options.message);
|
35
|
+
# }
|
36
|
+
#
|
37
|
+
# ==== javascripts/init.js
|
38
|
+
# alertWidget(<%= options.to_json %>);
|
39
|
+
#
|
40
|
+
#
|
41
|
+
# ==== partials/_init.html.erb
|
42
|
+
# <div id="<%= id %>" class="alert">
|
43
|
+
# You just got alerted.
|
44
|
+
# </div>
|
45
|
+
#
|
46
|
+
# ==== stylesheets/init.css
|
47
|
+
# #<%= id %> { color:<%= color %>; }
|
48
|
+
#
|
49
|
+
# ==== stylesheets/style.css
|
50
|
+
# .alert { font-size:18px; }
|
51
|
+
#
|
52
|
+
# === Layout view
|
53
|
+
# <html>
|
54
|
+
# <head>
|
55
|
+
# <%= javascripts %>
|
56
|
+
# <%= stylesheets %>
|
57
|
+
# </head>
|
58
|
+
# <body>
|
59
|
+
# <%= yield %>
|
60
|
+
# </body>
|
61
|
+
# </html>
|
62
|
+
#
|
63
|
+
# === Action view
|
64
|
+
# <%= widget :alert, :id => 'alert1', :color => 'blue' %>
|
65
|
+
# <%= widget :alert, :id => 'alert2' %>
|
66
|
+
#
|
67
|
+
# === Resulting HTML
|
68
|
+
# <html>
|
69
|
+
# <head>
|
70
|
+
# <script src="/javascripts/widgets/alert/alert.js?1220593492" type="text/javascript"></script>
|
71
|
+
# <script type='text/javascript'>
|
72
|
+
# alertWidget({ id: 'alert', message: 'Hello world!', color: 'red' });
|
73
|
+
# </script>
|
74
|
+
# <link href="/stylesheets/widgets/alert/style.css?1220593492" media="screen" rel="stylesheet" type="text/css" />
|
75
|
+
# <style type="text/css">
|
76
|
+
# #alert1 { color:blue; }
|
77
|
+
# #alert2 { color:red; }
|
78
|
+
# </style>
|
79
|
+
# </head>
|
80
|
+
# <body>
|
81
|
+
# <div id="alert1" class="alert">
|
82
|
+
# You just got alerted.
|
83
|
+
# </div>
|
84
|
+
# <div id="alert2" class="alert">
|
85
|
+
# You just got alerted.
|
86
|
+
# </div>
|
87
|
+
# </body>
|
88
|
+
# </html>
|
89
|
+
#
|
90
|
+
# == Inheritance
|
91
|
+
#
|
92
|
+
module RailsWidget
|
93
|
+
end
|
94
|
+
|
1
95
|
Dir[File.expand_path('*/*.rb', File.dirname(__FILE__))].each do |f|
|
2
96
|
require [ File.dirname(f), File.basename(f, '.rb') ].join('/')
|
3
97
|
end
|
4
98
|
|
5
|
-
ActionView::Base.send :include,
|
6
|
-
ActionController::Base.send :include,
|
7
|
-
ActionController::Base.view_paths += [ RAILS_ROOT + '/app/widgets' ]
|
99
|
+
ActionView::Base.send :include, RailsWidget
|
100
|
+
ActionController::Base.send :include, RailsWidget
|
101
|
+
ActionController::Base.view_paths += [ RAILS_ROOT + '/app/widgets' ]
|
102
|
+
|
103
|
+
# :main:RailsWidget
|
data/lib/rails_widget/assets.rb
CHANGED
@@ -1,114 +1,224 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
def default_javascript
|
4
|
-
"#{params[:controller]}/#{params[:action]}"
|
5
|
-
end
|
6
|
-
|
7
|
-
def default_stylesheet
|
8
|
-
"#{params[:controller]}/#{params[:action]}"
|
9
|
-
end
|
1
|
+
module RailsWidget
|
10
2
|
|
3
|
+
# Adds or renders javascript assets based on whether parameters are given or not.
|
4
|
+
#
|
5
|
+
# ==== Layout view
|
6
|
+
# <html>
|
7
|
+
# <head>
|
8
|
+
# <%= javascripts %>
|
9
|
+
# </head>
|
10
|
+
# <%= yield %>
|
11
|
+
# </html>
|
12
|
+
#
|
13
|
+
# ==== Action view
|
14
|
+
# <% javascripts 'script1', 'script2' do -%>
|
15
|
+
# alert('Hello world!');
|
16
|
+
# <% end -%>
|
17
|
+
# Content goes here.
|
18
|
+
#
|
19
|
+
# ==== Resulting HTML
|
20
|
+
# <html>
|
21
|
+
# <head>
|
22
|
+
# <script src="/javascripts/script1.js?1220593492" type="text/javascript"></script>
|
23
|
+
# <script src="/javascripts/script2.js?1220593492" type="text/javascript"></script>
|
24
|
+
# <script type='text/javascript'>
|
25
|
+
# alert('Hello world!');
|
26
|
+
# </script>
|
27
|
+
# </head>
|
28
|
+
# Content goes here.
|
29
|
+
# </html>
|
30
|
+
#
|
31
|
+
# Calling <tt>javascripts</tt> with path parameters or a script block will store the asset for later rendering.
|
32
|
+
#
|
33
|
+
# Calling <tt>javascripts</tt> without parameters renders the assets in the order they were added.
|
34
|
+
#
|
35
|
+
# The method accepts all options supported by <tt>javascript_include_tag</tt>.
|
36
|
+
#
|
11
37
|
def javascripts(*paths, &block)
|
12
|
-
|
38
|
+
@assets ||= Assets.new binding, controller, logger
|
39
|
+
@assets.javascripts *paths, &block
|
13
40
|
end
|
14
41
|
|
42
|
+
# Adds or renders stylesheet assets based on whether parameters are given or not.
|
43
|
+
#
|
44
|
+
# ==== Layout example
|
45
|
+
# <html>
|
46
|
+
# <head>
|
47
|
+
# <%= stylesheets %>
|
48
|
+
# </head>
|
49
|
+
# <%= yield %>
|
50
|
+
# </html>
|
51
|
+
#
|
52
|
+
# ==== Action example
|
53
|
+
# <% stylesheets 'style1', 'style2' -%>
|
54
|
+
# Content goes here.
|
55
|
+
#
|
56
|
+
# ==== Result
|
57
|
+
# <html>
|
58
|
+
# <head>
|
59
|
+
# <link href="/stylesheets/style1.css?1224923418" media="screen" rel="stylesheet" type="text/css" />
|
60
|
+
# <link href="/stylesheets/style2.css?1224923418" media="screen" rel="stylesheet" type="text/css" />
|
61
|
+
# </head>
|
62
|
+
# Content goes here.
|
63
|
+
# </html>
|
64
|
+
#
|
65
|
+
# Calling <tt>stylesheets</tt> with path parameters will store the asset for later rendering.
|
66
|
+
#
|
67
|
+
# Calling <tt>stylesheets</tt> without parameters renders the assets in the order they were added.
|
68
|
+
#
|
69
|
+
# The method accepts all options supported by <tt>stylesheet_link_tag</tt>.
|
70
|
+
#
|
15
71
|
def stylesheets(*paths, &block)
|
16
|
-
|
72
|
+
@assets ||= Assets.new binding, controller, logger
|
73
|
+
@assets.stylesheets *paths, &block
|
17
74
|
end
|
18
75
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
76
|
+
# Adds or renders textarea-based templates based on whether parameters are given or not.
|
77
|
+
#
|
78
|
+
# Use this with something like PURE <http://beebole.com/pure> or TrimPath's JST <http://trimpath.com>.
|
79
|
+
#
|
80
|
+
# ==== Layout example
|
81
|
+
# <html>
|
82
|
+
# <%= yield %>
|
83
|
+
# <%= templates %>
|
84
|
+
# </html>
|
85
|
+
#
|
86
|
+
# ==== Action example
|
87
|
+
# <% templates :id => 'myid', :partial => 'some_action/partial', :locals => { :x => 'Hello world' } -%>
|
88
|
+
# <% templates do -%>
|
89
|
+
# Template goes here.
|
90
|
+
# <% end -%>
|
91
|
+
# Content goes here.
|
92
|
+
#
|
93
|
+
# ==== Partial example (<tt>some_action/partial</tt>)
|
94
|
+
# <%= x %>!
|
95
|
+
#
|
96
|
+
# ==== Result
|
97
|
+
# <html>
|
98
|
+
# Content goes here.
|
99
|
+
# <textarea id='template_myid' style='display:none'>
|
100
|
+
# Hello world!
|
101
|
+
# </textarea>
|
102
|
+
# <textarea id='template' style='display:none'>
|
103
|
+
# Template goes here.
|
104
|
+
# </textarea>
|
105
|
+
# </html>
|
106
|
+
#
|
107
|
+
# Calling <tt>templates</tt> with path parameters or a block will store the asset for later rendering.
|
108
|
+
#
|
109
|
+
# Calling <tt>templates</tt> without parameters renders the assets in the order they were added.
|
110
|
+
#
|
111
|
+
def templates(*options, &block)
|
112
|
+
@assets ||= Assets.new binding, controller, logger
|
113
|
+
@assets.templates *options, &block
|
31
114
|
end
|
32
115
|
|
33
|
-
|
34
|
-
|
35
|
-
def add_assets(type, paths, &block)
|
36
|
-
options = paths.extract_options!
|
37
|
-
paths.flatten! unless type == :templates
|
116
|
+
class Assets
|
117
|
+
attr :assets, true
|
38
118
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
119
|
+
# Used for eval access
|
120
|
+
attr :block, true
|
121
|
+
attr :params, true
|
122
|
+
attr :options, true
|
43
123
|
|
44
|
-
|
124
|
+
def initialize(bind, controller, logger)
|
125
|
+
@assets = {}
|
126
|
+
@bind = bind
|
127
|
+
@controller = controller
|
128
|
+
@logger = logger
|
129
|
+
end
|
45
130
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
131
|
+
def javascripts(*params, &block)
|
132
|
+
add_assets :javascripts, params, &block
|
133
|
+
end
|
134
|
+
|
135
|
+
def stylesheets(*params, &block)
|
136
|
+
add_assets :stylesheets, params, &block
|
137
|
+
end
|
138
|
+
|
139
|
+
def templates(*params, &block)
|
140
|
+
add_assets :templates, params, &block
|
55
141
|
end
|
56
142
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
143
|
+
private
|
144
|
+
|
145
|
+
def add_assets(type, params, &block)
|
146
|
+
options = params.extract_options! unless type == :templates
|
147
|
+
capture = block_to_string &block
|
148
|
+
asset = delete_if_empty(:options => options, :params => params, :capture => capture)
|
149
|
+
if asset.empty?
|
150
|
+
remove_dups :javascripts, :params
|
151
|
+
remove_dups :stylesheets, :params, :capture
|
152
|
+
remove_dups :templates, :params, :capture
|
153
|
+
captures = []
|
154
|
+
tags = []
|
155
|
+
@assets[type].each do |item|
|
156
|
+
@capture = item[:capture]
|
157
|
+
@params = item[:params]
|
72
158
|
case type
|
73
159
|
when :javascripts
|
74
|
-
|
160
|
+
captures.push(@capture) if @capture
|
161
|
+
tags.push(eval("javascript_include_tag *[ @assets.params, @assets.options ].flatten.compact", @bind)) if @params
|
75
162
|
when :stylesheets
|
76
|
-
|
163
|
+
captures.push(@capture) if @capture
|
164
|
+
tags.push(eval("stylesheet_link_tag *[ @assets.params, @assets.options ].flatten.compact", @bind)) if @params
|
77
165
|
when :templates
|
78
|
-
textarea_template
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
when :javascripts
|
83
|
-
js.push(item) unless item.blank?
|
84
|
-
nil
|
85
|
-
else
|
86
|
-
item
|
166
|
+
captures.push(textarea_template(@params.pop.merge(:body => @capture))) if @capture
|
167
|
+
@params.each do |options|
|
168
|
+
captures << textarea_template(options)
|
169
|
+
end
|
87
170
|
end
|
88
171
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
172
|
+
case type
|
173
|
+
when :javascripts
|
174
|
+
tags.join("\n") + "\n<script type='text/javascript'>\n#{captures.join "\n"}\n</script>"
|
175
|
+
when :stylesheets
|
176
|
+
tags.join("\n") + "\n<style type='text/css'>\n#{captures.join "\n"}\n</style>"
|
177
|
+
when :templates
|
178
|
+
captures.uniq.join "\n"
|
179
|
+
end
|
92
180
|
else
|
93
|
-
assets
|
181
|
+
@assets[type] ||= []
|
182
|
+
@assets[type].push asset
|
94
183
|
end
|
95
184
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
185
|
+
|
186
|
+
def block_to_string(&block)
|
187
|
+
return nil unless block
|
188
|
+
@block = block
|
189
|
+
eval "capture(&@assets.block)", @bind
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_if_empty(hash)
|
193
|
+
list = []
|
194
|
+
hash.each { |key, value| list.push(key) if !value || value.empty? || value.blank? }
|
195
|
+
list.each { |key| hash.delete key }
|
196
|
+
hash
|
197
|
+
end
|
198
|
+
|
199
|
+
def remove_dups(asset_type, *types)
|
200
|
+
asset = @assets[asset_type]
|
201
|
+
types.each do |type|
|
202
|
+
list = []
|
203
|
+
asset.each do |a|
|
204
|
+
if list.include?(a[type])
|
205
|
+
a.delete type
|
206
|
+
else
|
207
|
+
list << a[type]
|
208
|
+
end
|
109
209
|
end
|
210
|
+
end if asset
|
211
|
+
end
|
212
|
+
|
213
|
+
def textarea_template(options)
|
214
|
+
id = 'template' + (options[:id] ? "_#{options[:id]}" : '')
|
215
|
+
if options[:body]
|
216
|
+
body = options[:body]
|
217
|
+
elsif options[:partial]
|
218
|
+
body = @controller.render_to_string :partial => options[:partial], :locals => options[:locals]
|
110
219
|
end
|
220
|
+
return nil unless body
|
221
|
+
"<textarea id='#{id}' style='display:none'>\n#{body}\n</textarea>"
|
111
222
|
end
|
112
223
|
end
|
113
|
-
|
114
224
|
end
|
data/lib/rails_widget/widget.rb
CHANGED
@@ -1,248 +1,169 @@
|
|
1
|
-
module
|
1
|
+
module RailsWidget
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
javascripts *(@layout_happened ? [ :layout => true ] : []) do
|
19
|
-
js
|
20
|
-
end
|
21
|
-
partial
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def require_widget(*path)
|
27
|
-
widgets, options = widget_instances path
|
28
|
-
widgets.each do |w|
|
29
|
-
w.copy_assets
|
30
|
-
js = w.helper_targets :javascripts
|
31
|
-
css = w.helper_targets :stylesheets
|
32
|
-
javascripts *(js + [ :cache => w.cache, :layout => @layout_happened ]) unless js.empty?
|
33
|
-
stylesheets *(css + [ :cache => w.cache, :layout => @layout_happened ]) unless css.empty?
|
34
|
-
templates *(w.assets[:templates].collect do |t|
|
35
|
-
[ File.basename(t), t, options.merge(:options => options) ]
|
36
|
-
end) unless w.assets[:templates].empty?
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def widget_flash_path(*path)
|
41
|
-
flash = path.pop
|
42
|
-
"/flash/widgets/#{path.join('/')}/#{flash}"
|
43
|
-
end
|
44
|
-
|
45
|
-
def widget_image(*path)
|
46
|
-
options = path.extract_options!
|
47
|
-
image = path.pop
|
48
|
-
image_tag "widgets/#{path.join('/')}/#{image}", options
|
49
|
-
end
|
50
|
-
|
51
|
-
def widget_image_path(*path)
|
52
|
-
image = path.pop
|
53
|
-
"/images/widgets/#{path.join('/')}/#{image}"
|
54
|
-
end
|
55
|
-
|
56
|
-
def widget_partial(*path)
|
57
|
-
options = path.extract_options!
|
58
|
-
partial = path.pop
|
59
|
-
path << options
|
60
|
-
widgets, options = widget_instances path
|
61
|
-
options = {
|
62
|
-
:locals => options.merge(:options => options),
|
63
|
-
:partial => "#{path.join('/')}/partials/#{partial}"
|
64
|
-
}
|
65
|
-
render options
|
66
|
-
end
|
67
|
-
|
68
|
-
def widget_instances(path)
|
69
|
-
@widgets ||= Widgets.new binding, controller, logger
|
70
|
-
options = path.extract_options!
|
71
|
-
@widgets.build path, options
|
72
|
-
end
|
73
|
-
|
74
|
-
class Widgets
|
75
|
-
attr :widgets, true
|
76
|
-
|
77
|
-
def initialize(bind, controller, logger)
|
78
|
-
@bind = bind
|
3
|
+
# Stores information about a widget and renders assets to <tt>public/</tt> when necessary.
|
4
|
+
#
|
5
|
+
class Widget
|
6
|
+
attr :assets, true # Paths for each ASSET_TYPE
|
7
|
+
attr :cache, true # Cache path for Rails asset helpers
|
8
|
+
attr :options, true # Options hash from options.rb
|
9
|
+
attr :path, true # Path to widget
|
10
|
+
|
11
|
+
ASSET_TYPES = [ :flash, :images, :javascripts, :stylesheets, :templates, :init_css, :init_js, :init_partials ]
|
12
|
+
|
13
|
+
# Calls <tt>update_options</tt> and <tt>update_asset</tt> for each <tt>ASSET_TYPE</tt>.
|
14
|
+
#
|
15
|
+
def initialize(path, bind, controller, logger)
|
16
|
+
@path = path
|
17
|
+
@bind = bind
|
79
18
|
@controller = controller
|
80
|
-
@logger
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@widgets[r]
|
19
|
+
@logger = logger
|
20
|
+
|
21
|
+
@assets = {}
|
22
|
+
@options = {}
|
23
|
+
@rendered = {}
|
24
|
+
@targeted = {}
|
25
|
+
|
26
|
+
update_options
|
27
|
+
ASSET_TYPES.each do |type|
|
28
|
+
update_asset type
|
91
29
|
end
|
92
|
-
[ widgets, opts.merge(options) ]
|
93
30
|
end
|
94
31
|
|
95
|
-
|
96
|
-
|
97
|
-
def
|
98
|
-
|
99
|
-
last = paths.length - 1
|
100
|
-
paths.each_index do |x|
|
101
|
-
if x != 0 && File.exists?("app/widgets/#{paths[x]}")
|
102
|
-
ordered << related_paths(paths[x..last])
|
103
|
-
end
|
104
|
-
path = paths[0..x].join '/'
|
105
|
-
if File.exists?("app/widgets/#{path}")
|
106
|
-
ordered << path
|
107
|
-
end
|
108
|
-
end
|
109
|
-
ordered.flatten
|
32
|
+
# Returns a cache path suitable for Rails asset helpers.
|
33
|
+
#
|
34
|
+
def cache
|
35
|
+
'cache/' + (@path.empty? ? 'base' : @path.gsub('/', '_'))
|
110
36
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
@assets.each do |key, value|
|
138
|
-
from, to = to_path key
|
139
|
-
value.each do |asset|
|
140
|
-
base = File.basename asset
|
141
|
-
f = [ from, base ].join '/'
|
142
|
-
t = [ to, base ].join '/'
|
143
|
-
t.gsub!('/stylesheets/', '/stylesheets/sass/') if t.include?('.sass')
|
144
|
-
next unless needs_update?(f, t)
|
145
|
-
case key
|
146
|
-
when :flash, :images
|
147
|
-
FileUtils.mkdir_p to
|
148
|
-
FileUtils.copy f, t
|
149
|
-
when :javascripts, :stylesheets
|
150
|
-
FileUtils.mkdir_p File.dirname(t)
|
151
|
-
File.open t, 'w' do |file|
|
152
|
-
file.write @controller.render_to_string(:file => f, :locals => @options.merge(:options => @options))
|
153
|
-
end
|
37
|
+
|
38
|
+
# Copies widget images to <tt>public/images/widgets</tt>.
|
39
|
+
#
|
40
|
+
# Copies widget flash files to <tt>public/flash/widgets</tt>.
|
41
|
+
#
|
42
|
+
# Renders javascripts to <tt>public/javascripts/widgets</tt>.
|
43
|
+
#
|
44
|
+
# Renders stylesheets to <tt>public/stylesheets/widgets</tt>.
|
45
|
+
#
|
46
|
+
def copy_assets
|
47
|
+
@assets.each do |key, value|
|
48
|
+
from, to = to_path key
|
49
|
+
value.each do |asset|
|
50
|
+
base = File.basename asset
|
51
|
+
f = [ from, base ].join '/'
|
52
|
+
t = [ to, base ].join '/'
|
53
|
+
t.gsub!('/stylesheets/', '/stylesheets/sass/') if t.include?('.sass')
|
54
|
+
next unless needs_update?(f, t)
|
55
|
+
case key
|
56
|
+
when :flash, :images
|
57
|
+
FileUtils.mkdir_p to
|
58
|
+
FileUtils.cp_r f, t
|
59
|
+
when :javascripts, :stylesheets
|
60
|
+
FileUtils.mkdir_p File.dirname(t)
|
61
|
+
File.open t, 'w' do |file|
|
62
|
+
file.write @controller.render_to_string(:file => f, :locals => @options.merge(:options => @options))
|
154
63
|
end
|
155
64
|
end
|
156
65
|
end
|
157
66
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
174
|
-
else @assets[type]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns asset paths to be included via the Assets helpers for a particular <tt>ASSET_TYPE</tt>.
|
70
|
+
#
|
71
|
+
# See <tt>add_static_assets (Widgets)</tt>.
|
72
|
+
#
|
73
|
+
def asset_paths(type)
|
74
|
+
return [] if @targeted[type]
|
75
|
+
@targeted[type] = true
|
76
|
+
|
77
|
+
from, to = to_path type
|
78
|
+
case type
|
79
|
+
when :javascripts
|
80
|
+
@assets[type].collect do |asset|
|
81
|
+
[ to.split('javascripts/')[1], File.basename(asset, '.js') ].join '/'
|
175
82
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
return nil if @rendered[type][options[:id]]
|
181
|
-
@rendered[type][options[:id]] = true
|
182
|
-
|
183
|
-
@assets["init_#{type}".intern].collect do |f|
|
184
|
-
@controller.render_to_string :file => f, :locals => options.merge(:options => options)
|
185
|
-
end.join("\n")
|
186
|
-
end
|
187
|
-
|
188
|
-
private
|
189
|
-
|
190
|
-
def cache_name
|
191
|
-
'cache/' + (@path.empty? ? 'base' : @path.gsub('/', '_'))
|
192
|
-
end
|
193
|
-
|
194
|
-
def filename_to_partial(file, remove=nil)
|
195
|
-
base = File.basename file
|
196
|
-
dir = File.dirname file
|
197
|
-
file = [ dir, (base[0..0] == '_' ? base[1..-1] : base ).split('.')[0..-2].join('.') ].join '/'
|
198
|
-
if remove
|
199
|
-
if remove.respond_to?(:pop)
|
200
|
-
remove.each { |r| file.gsub! r, '' }
|
201
|
-
else
|
202
|
-
file.gsub! remove, ''
|
203
|
-
end
|
83
|
+
when :stylesheets
|
84
|
+
@assets[type].collect do |asset|
|
85
|
+
sass = asset.include? '.sass'
|
86
|
+
[ to.split('stylesheets/')[1], File.basename(asset, sass ? '.sass' : '.css') ].join '/'
|
204
87
|
end
|
205
|
-
|
88
|
+
else @assets[type]
|
206
89
|
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Renders and returns the init file for a particular <tt>ASSET_TYPE</tt>.
|
93
|
+
#
|
94
|
+
# The render will not occur if it has already happened with the same <tt>:id</tt> option.
|
95
|
+
#
|
96
|
+
def render_init(type, options=@options)
|
97
|
+
@rendered[type] ||= {}
|
98
|
+
#return nil if @rendered[type][options[:id]]
|
99
|
+
@rendered[type][options[:id]] = true
|
207
100
|
|
208
|
-
|
209
|
-
|
210
|
-
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
101
|
+
@assets["init_#{type}".intern].collect do |f|
|
102
|
+
@controller.render_to_string :file => f, :locals => options.merge(:options => options)
|
103
|
+
end.join("\n")
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Converts a full file name to a path that can be used by <tt>render :partial</tt>.
|
109
|
+
#
|
110
|
+
def filename_to_partial(file, remove=nil) #:doc:
|
111
|
+
base = File.basename file
|
112
|
+
dir = File.dirname file
|
113
|
+
file = [ dir, (base[0..0] == '_' ? base[1..-1] : base ).split('.')[0..-2].join('.') ].join '/'
|
114
|
+
if remove
|
115
|
+
if remove.respond_to?(:pop)
|
116
|
+
remove.each { |r| file.gsub! r, '' }
|
117
|
+
else
|
118
|
+
file.gsub! remove, ''
|
225
119
|
end
|
226
120
|
end
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
121
|
+
file
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns true if <tt>from</tt> is newer than <tt>to</tt> or <tt>to</tt> does not exist.
|
125
|
+
#
|
126
|
+
def needs_update?(from, to) #:doc:
|
127
|
+
File.exists?(to) ? File.mtime(from) > File.mtime(to) : true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns a full path for the specified <tt>ASSET_TYPE</tt>.
|
131
|
+
#
|
132
|
+
def to_path(type, path=@path) #:doc:
|
133
|
+
slash = path.empty? ? '' : '/'
|
134
|
+
base = "app/widgets#{slash}#{path}"
|
135
|
+
case type
|
136
|
+
when :base: base
|
137
|
+
when :init_css: base + '/stylesheets/init'
|
138
|
+
when :init_js: base + '/javascripts/init'
|
139
|
+
when :init_partials: base + '/partials/_init'
|
140
|
+
when :options: base + '/options.rb'
|
141
|
+
when :templates: base + '/templates'
|
142
|
+
when :flash: [ base + '/flash', "public/flash/widgets" + slash + path ]
|
143
|
+
when :images: [ base + '/images', "public/images/widgets" + slash + path ]
|
144
|
+
when :javascripts: [ base + '/javascripts', "public/javascripts/widgets" + slash + path ]
|
145
|
+
when :stylesheets: [ base + '/stylesheets', "public/stylesheets/widgets" + slash + path ]
|
237
146
|
end
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
147
|
+
end
|
148
|
+
|
149
|
+
# Updates <tt>@assets[type]</tt> with an array of paths for the specified <tt>ASSET_TYPE</tt>.
|
150
|
+
#
|
151
|
+
def update_asset(type) #:doc:
|
152
|
+
@assets[type] ||= []
|
153
|
+
from = to_path type
|
154
|
+
from = from[0] if from.respond_to?(:pop)
|
155
|
+
from = File.directory?(from) ? "#{from}/*" : "#{from}.*"
|
156
|
+
Dir[from].sort.each do |f|
|
157
|
+
next if (type == :javascripts || type == :stylesheets) && File.basename(f)[0..3] == 'init'
|
158
|
+
@assets[type] << (type == :templates ? filename_to_partial(f, 'app/widgets/') : f)
|
245
159
|
end
|
246
160
|
end
|
161
|
+
|
162
|
+
# Assigns <tt>@options</tt> to the hash in <tt>options.rb</tt> (if it exists).
|
163
|
+
#
|
164
|
+
def update_options(path=@path, empty=false) #:doc:
|
165
|
+
path = to_path :options, path
|
166
|
+
@options = File.exists?(path) ? eval(File.read(path), @bind) : {}
|
167
|
+
end
|
247
168
|
end
|
248
169
|
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module RailsWidget
|
2
|
+
|
3
|
+
# See <tt>RailsWidget</tt>.
|
4
|
+
#
|
5
|
+
def widget(*path)
|
6
|
+
@assets ||= Assets.new binding, controller, logger
|
7
|
+
@widgets ||= Widgets.new @assets, binding, controller, logger
|
8
|
+
options = path.extract_options!
|
9
|
+
@widgets.build path, options
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns a path for a flash asset.
|
13
|
+
#
|
14
|
+
# ==== Example
|
15
|
+
# <%= flash_path :some, :widget, 'flash.swf' %>
|
16
|
+
# # => 'app/widgets/some/widget/flash/flash.swf'
|
17
|
+
#
|
18
|
+
def flash_path(*path)
|
19
|
+
flash = path.pop
|
20
|
+
"/flash/widgets/#{path.join('/')}/#{flash}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an image tag for a image asset.
|
24
|
+
#
|
25
|
+
# ==== Example
|
26
|
+
# <%= image :some, :widget, 'image.png', :border => 0 %>
|
27
|
+
# # => '<img src="app/widgets/some/widget/images/image.png" border=0 />'
|
28
|
+
#
|
29
|
+
def image(*path)
|
30
|
+
options = path.extract_options!
|
31
|
+
image = path.pop
|
32
|
+
image_tag "widgets/#{path.join('/')}/#{image}", options
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns an image path for a image asset.
|
36
|
+
#
|
37
|
+
# ==== Example
|
38
|
+
# <%= image_path :some, :widget, 'image.png' %>
|
39
|
+
# # => 'app/widgets/some/widget/images/image.png'
|
40
|
+
#
|
41
|
+
def image_path(*path)
|
42
|
+
image = path.pop
|
43
|
+
"/images/widgets/#{path.join('/')}/#{image}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Renders a partial asset.
|
47
|
+
#
|
48
|
+
# ==== Example
|
49
|
+
# <%= partial :some, :widget, 'partial', :locals => { :x => true } %>
|
50
|
+
# # => render :partial => 'app/widgets/some/widget/partials/partial', :locals => { :x => true }
|
51
|
+
#
|
52
|
+
def partial(*path)
|
53
|
+
options = path.extract_options!
|
54
|
+
partial = path.pop
|
55
|
+
path << options
|
56
|
+
widgets, options = widget_instances path
|
57
|
+
options = {
|
58
|
+
:locals => options.merge(:options => options),
|
59
|
+
:partial => "#{path.join('/')}/partials/#{partial}"
|
60
|
+
}
|
61
|
+
render options
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates and recycles instances of the Widget class.
|
65
|
+
#
|
66
|
+
class Widgets
|
67
|
+
attr :widgets, true
|
68
|
+
|
69
|
+
# Should be called from a helper. See <tt>widget (RailsWidget)</tt>.
|
70
|
+
#
|
71
|
+
# ==== Example
|
72
|
+
# w = Widgets.new Assets.new(binding, controller), binding, controller, logger
|
73
|
+
#
|
74
|
+
def initialize(assets, bind, controller, logger)
|
75
|
+
@assets = assets
|
76
|
+
@bind = bind
|
77
|
+
@controller = controller
|
78
|
+
@logger = logger
|
79
|
+
@widgets = {}
|
80
|
+
build
|
81
|
+
end
|
82
|
+
|
83
|
+
# See <tt>widget (RailsWidget)</tt>.
|
84
|
+
#
|
85
|
+
def build(path=[''], options={})
|
86
|
+
widgets, opts = instanciate path
|
87
|
+
options = opts.merge options # Merge the options parameter (highest precedence)
|
88
|
+
add_static_assets widgets, options
|
89
|
+
return_init_assets widgets, options # Returns the init partial to <tt>widget (RailsWidget)</tt>
|
90
|
+
end
|
91
|
+
|
92
|
+
# Creates Widget instances for the widget path and its <tt>related_paths</tt> if they do not already exist.
|
93
|
+
#
|
94
|
+
# Returns an array of widget instances and merged <tt>options.rb</tt> hashes.
|
95
|
+
#
|
96
|
+
# ==== Example
|
97
|
+
# w.build([ :some, :widget ], { :option1 => true })
|
98
|
+
# # => [ #<Widget>, #<Widget>, #<Widget> ], { :option1 => true, :option2 => true }
|
99
|
+
#
|
100
|
+
# (See the <tt>related_paths</tt> example for context.)
|
101
|
+
#
|
102
|
+
def instanciate(path)
|
103
|
+
opts = {}
|
104
|
+
widgets = related_paths(path).collect do |r|
|
105
|
+
@widgets[r] ||= Widget.new r, @bind, @controller, @logger
|
106
|
+
opts.merge! @widgets[r].options
|
107
|
+
@widgets[r]
|
108
|
+
end
|
109
|
+
[ widgets, opts ]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Calls <tt>copy_assets (Widget)</tt> for a number of <tt>Widget</tt> instances.
|
113
|
+
#
|
114
|
+
# Also adds static (non-init) assets to the layout via the Assets helpers.
|
115
|
+
#
|
116
|
+
def add_static_assets(widgets, options)
|
117
|
+
widgets.each do |w|
|
118
|
+
w.copy_assets
|
119
|
+
js = w.asset_paths :javascripts
|
120
|
+
css = w.asset_paths :stylesheets
|
121
|
+
@assets.javascripts *(js + [ :cache => w.cache ]) unless js.empty?
|
122
|
+
@assets.stylesheets *(css + [ :cache => w.cache ]) unless css.empty?
|
123
|
+
@assets.templates *(w.assets[:templates].collect do |t|
|
124
|
+
{ :id => File.basename(t), :partial => t, :locals => options.merge(:options => options) }
|
125
|
+
end) unless w.assets[:templates].empty?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Renders and returns the init partial (<tt>partials/_init.*</tt>) for a number of <tt>Widget</tt> instances.
|
130
|
+
#
|
131
|
+
# The <tt>:include_js => true</tt> option appends <tt>javascripts/init.js</tt> in <script> tags.
|
132
|
+
# Use this option when rendering a widget in an Ajax response.
|
133
|
+
#
|
134
|
+
def return_init_assets(widgets, options)
|
135
|
+
# Render partials/_init.* (options[:include_js] will render javascripts/init.js in <script> tags)
|
136
|
+
widgets.collect do |w|
|
137
|
+
# We want widgets rendered from the partial to include first
|
138
|
+
partial = w.render_init :partials, options
|
139
|
+
css = w.render_init :css, options
|
140
|
+
js = w.render_init :js, options
|
141
|
+
if options[:include_js] && js && !js.empty?
|
142
|
+
partial + "\n<script type='text/javascript'>\n#{js}\n</script>"
|
143
|
+
else
|
144
|
+
@assets.stylesheets do
|
145
|
+
css
|
146
|
+
end unless css.empty?
|
147
|
+
@assets.javascripts do
|
148
|
+
js
|
149
|
+
end unless js.empty?
|
150
|
+
partial
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# Returns an array of related paths based on a single widget path.
|
158
|
+
#
|
159
|
+
# ==== Example
|
160
|
+
# related_paths([ :some, :widget ])
|
161
|
+
# # => [ 'some', 'widget', 'some/widget' ]
|
162
|
+
#
|
163
|
+
# Options are merged based on the order of the array that <tt>related_paths</tt> returns:
|
164
|
+
# app/widgets/some/options.rb # { :option1 => true, :option2 => false }
|
165
|
+
# app/widgets/widget/options.rb # { :option1 => false }
|
166
|
+
# app/widgets/some/widget/options.rb # { :option2 => true }
|
167
|
+
#
|
168
|
+
# Sequentially merging the options in this example produces <tt>{ :option1 => false, :option2 => true }</tt>.
|
169
|
+
#
|
170
|
+
# Assets are also included and rendered (init files) in the order of the <tt>related_paths</tt> array.
|
171
|
+
#
|
172
|
+
def related_paths(paths)
|
173
|
+
ordered = []
|
174
|
+
last = paths.length - 1
|
175
|
+
paths.each_index do |x|
|
176
|
+
if x != 0 && File.exists?("app/widgets/#{paths[x]}")
|
177
|
+
ordered << related_paths(paths[x..last])
|
178
|
+
end
|
179
|
+
path = paths[0..x].join '/'
|
180
|
+
if File.exists?("app/widgets/#{path}")
|
181
|
+
ordered << path
|
182
|
+
end
|
183
|
+
end
|
184
|
+
ordered.flatten
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: winton-rails_widget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: "1.1"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Winton Welsh
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-08
|
12
|
+
date: 2008-11-08 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
16
|
+
description: Allows you to group your client-side assets into distributable widgets
|
17
17
|
email: mail@wintoni.us
|
18
18
|
executables: []
|
19
19
|
|
@@ -25,12 +25,12 @@ files:
|
|
25
25
|
- init.rb
|
26
26
|
- lib/rails_widget.rb
|
27
27
|
- lib/rails_widget
|
28
|
+
- lib/rails_widget/widgets.rb
|
28
29
|
- lib/rails_widget/assets.rb
|
29
30
|
- lib/rails_widget/widget.rb
|
30
31
|
- MIT-LICENSE
|
31
32
|
- README.markdown
|
32
|
-
|
33
|
-
has_rdoc: false
|
33
|
+
has_rdoc: true
|
34
34
|
homepage: http://github.com/winton/rails_widget
|
35
35
|
post_install_message:
|
36
36
|
rdoc_options: []
|
@@ -55,6 +55,6 @@ rubyforge_project:
|
|
55
55
|
rubygems_version: 1.2.0
|
56
56
|
signing_key:
|
57
57
|
specification_version: 2
|
58
|
-
summary:
|
58
|
+
summary: Allows you to group your client-side assets into distributable widgets
|
59
59
|
test_files: []
|
60
60
|
|
data/tasks/rails_widget.rake
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
desc 'Updates app/widgets assets'
|
2
|
-
task :widgets => [ 'widgets:javascripts', 'widgets:stylesheets' ]
|
3
|
-
|
4
|
-
namespace :widgets do
|
5
|
-
desc 'Updates app/widgets/javascripts'
|
6
|
-
task :javascripts do
|
7
|
-
rails_widget_resource 'widgets/javascripts', 'app/widgets/javascripts'
|
8
|
-
end
|
9
|
-
|
10
|
-
desc 'Updates app/widgets/stylesheets'
|
11
|
-
task :stylesheets do
|
12
|
-
rails_widget_resource 'widgets/stylesheets', 'app/widgets/stylesheets'
|
13
|
-
end
|
14
|
-
|
15
|
-
def rails_widget_resource(type, to, reverse=false)
|
16
|
-
from = "#{File.dirname(__FILE__)}/../resources/#{type}"
|
17
|
-
from, to = to, from if reverse
|
18
|
-
puts "=> Removing old #{type}..."
|
19
|
-
FileUtils.remove_dir to, true
|
20
|
-
FileUtils.mkdir_p to
|
21
|
-
puts "=> Copying #{type}..."
|
22
|
-
Dir["#{from}/*"].each do |f|
|
23
|
-
if File.directory? f
|
24
|
-
FileUtils.mkdir_p "#{to}/#{File.basename(f)}"
|
25
|
-
FileUtils.cp_r f, to
|
26
|
-
else
|
27
|
-
FileUtils.cp f, to
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|