winton-rails_widget 1.0.2 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,8 +1,23 @@
1
- Rails Widget
2
- ============
1
+ RailsWidget
2
+ ===========
3
3
 
4
- A mini-framework for your client side Rails assets.
4
+ Allows you to group your client-side assets into distributable "widgets"
5
5
 
6
- **This plugin is very beta. More details coming soon.**
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
- ##### Copyright (c) 2008 [Winton Welsh](mailto:mail@wintoni.us), released under the MIT license
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 &copy; 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, WidgetHelpers
6
- ActionController::Base.send :include, WidgetHelpers
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
@@ -1,114 +1,224 @@
1
- module WidgetHelpers
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
- add_assets :javascripts, paths, &block
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
- add_assets :stylesheets, paths, &block
72
+ @assets ||= Assets.new binding, controller, logger
73
+ @assets.stylesheets *paths, &block
17
74
  end
18
75
 
19
- def templates(*paths, &block)
20
- paths.each do |path|
21
- add_assets :templates, path, &block
22
- end
23
- add_assets(:templates, paths, &block) if paths.empty?
24
- end
25
-
26
- def textarea_template(id, path=nil, locals={})
27
- controller.render_to_string(:partial => 'app_helpers/template/textarea', :locals => {
28
- :id => id,
29
- :body => controller.render_to_string(:partial => path, :locals => locals)
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
- private
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
- @assets ||= {}
40
- @assets[type] ||= []
41
- @layout_assets ||= {}
42
- @layout_assets[type] ||= []
119
+ # Used for eval access
120
+ attr :block, true
121
+ attr :params, true
122
+ attr :options, true
43
123
 
44
- paths = nil if paths.empty?
124
+ def initialize(bind, controller, logger)
125
+ @assets = {}
126
+ @bind = bind
127
+ @controller = controller
128
+ @logger = logger
129
+ end
45
130
 
46
- if options[:layout]
47
- options.delete :layout
48
- paths.push(options) if paths
49
- @layout_assets[type].push(paths ) if paths
50
- @layout_assets[type].push(capture(&block)) if block
51
- else
52
- paths.push(options) if paths
53
- @assets[type].push(paths ) if paths
54
- @assets[type].push(capture(&block)) if block
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
- if !paths && !block
58
- #logger.info type.inspect
59
- #logger.info 'LAYOUT ' + @layout_assets[type].inspect
60
- #logger.info @assets[type].inspect
61
-
62
- @assets[type] = @layout_assets[type] + @assets[type]
63
-
64
- @assets[type].uniq!
65
- remove_dups @assets[type]
66
- @assets[type].collect! { |a| a[0].respond_to?(:keys) ? nil : a }
67
- @assets[type].compact!
68
-
69
- js = []
70
- assets = @assets[type].collect do |item|
71
- if item.respond_to?(:pop)
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
- javascript_include_tag *item
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
- stylesheet_link_tag *item
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 item[0], item[1], item[2]
79
- end + "\n"
80
- else
81
- case type
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
- end.compact
90
- if type == :javascripts
91
- assets.join + "<script type='text/javascript'>\n#{js.join "\n"}\n</script>"
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.join
181
+ @assets[type] ||= []
182
+ @assets[type].push asset
94
183
  end
95
184
  end
96
- end
97
-
98
- def remove_dups(arr, list=[])
99
- arr.dup.each do |a|
100
- if a.respond_to?(:keys)
101
- next
102
- elsif a.respond_to?(:pop)
103
- remove_dups a, list
104
- else
105
- if list.include?(a) || a.blank?
106
- arr.delete_at arr.rindex(a)
107
- else
108
- list << a
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
@@ -1,248 +1,169 @@
1
- module WidgetHelpers
1
+ module RailsWidget
2
2
 
3
- def widget_base
4
- @layout_happened = true
5
- require_widget ''
6
- render_widget ''
7
- end
8
-
9
- def render_widget(*path)
10
- widgets, options = widget_instances path
11
- widgets.collect do |w|
12
- # we want widgets rendered from the partial to include first
13
- partial = w.render_init :partials, options
14
- js = w.render_init :js, options
15
- if options[:include_js] && js && !js.empty?
16
- partial + "\n<script type='text/javascript'>\n#{js}\n</script>"
17
- else
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 = logger
81
- @widgets = {}
82
- end
83
-
84
- def build(path, options)
85
- opts = {}
86
- #@logger.info 'RELATED_PATHS ' + related_paths(path).inspect
87
- widgets = related_paths(path).collect do |r|
88
- @widgets[r] ||= Assets.new r, @bind, @controller, @logger
89
- opts.merge! @widgets[r].options
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
- private
96
-
97
- def related_paths(paths)
98
- ordered = []
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
- class Assets
113
- attr :assets, true
114
- attr :cache, true
115
- attr :options, true
116
- attr :path, true
117
-
118
- ASSET_TYPES = [ :flash, :images, :javascripts, :stylesheets, :templates, :init_js, :init_partials ]
119
-
120
- def initialize(path, bind, controller, logger)
121
- @bind = bind
122
- @controller = controller
123
- @logger = logger
124
- @assets = {}
125
- @options = {}
126
- @rendered = {}
127
- @targeted = {}
128
- @path = path
129
- @cache = cache_name
130
- update_options
131
- ASSET_TYPES.each do |type|
132
- update_asset type
133
- end
134
- end
135
-
136
- def copy_assets
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
- def helper_targets(type)
160
- return [] if @targeted[type]
161
- @targeted[type] = true
162
-
163
- from, to = to_path type
164
- case type
165
- when :javascripts
166
- @assets[type].collect do |asset|
167
- [ to.split('javascripts/')[1], File.basename(asset, '.js') ].join '/'
168
- end
169
- when :stylesheets
170
- @assets[type].collect do |asset|
171
- sass = asset.include? '.sass'
172
- [ to.split('stylesheets/')[1], File.basename(asset, sass ? '.sass' : '.css') ].join '/'
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
- end
177
-
178
- def render_init(type, options=@options)
179
- @rendered[type] ||= {}
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
- file
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
- def needs_update?(from, to)
209
- File.exists?(to) ? File.mtime(from) > File.mtime(to) : true
210
- end
211
-
212
- def to_path(type, path=@path)
213
- slash = path.empty? ? '' : '/'
214
- base = "app/widgets#{slash}#{path}"
215
- case type
216
- when :base: base
217
- when :init_js: base + '/javascripts/init'
218
- when :init_partials: base + '/partials/_init'
219
- when :options: base + '/options.rb'
220
- when :templates: base + '/templates'
221
- when :flash: [ base + '/flash', "public/flash/widgets" + slash + path ]
222
- when :images: [ base + '/images', "public/images/widgets" + slash + path ]
223
- when :javascripts: [ base + '/javascripts', "public/javascripts/widgets" + slash + path ]
224
- when :stylesheets: [ base + '/stylesheets', "public/stylesheets/widgets" + slash + path ]
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
- def update_asset(type)
229
- @assets[type] ||= []
230
- from = to_path type
231
- from = from[0] if from.respond_to?(:pop)
232
- from = File.directory?(from) ? "#{from}/*" : "#{from}.*"
233
- Dir[from].sort.each do |f|
234
- next if type == :javascripts && File.basename(f) == 'init.js'
235
- @assets[type] << (type == :templates ? filename_to_partial(f, 'app/widgets/') : f)
236
- end
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
- def update_options(path=@path, empty=false)
240
- options = to_path :options, path
241
- @options = (File.exists?(options) ? eval(File.read(options), @bind) : {}).merge(@options)
242
- path = path.split('/')[0..-2]
243
- # empty allows us to retrieve base directory's options
244
- update_options(path.join('/'), path.empty?) unless empty
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.0.2
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-16 00:00:00 -07:00
12
+ date: 2008-11-08 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A mini-framework for your client side Rails assets
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
- - tasks/rails_widget.rake
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: A mini-framework for your client side Rails assets
58
+ summary: Allows you to group your client-side assets into distributable widgets
59
59
  test_files: []
60
60
 
@@ -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