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 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