adamh-html_namespacing 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest CHANGED
@@ -8,7 +8,9 @@ ext/html_namespacing/html_namespacing.c
8
8
  ext/html_namespacing/html_namespacing_ext.c
9
9
  lib/html_namespacing.rb
10
10
  lib/html_namespacing/plugin.rb
11
+ lib/html_namespacing/plugin/dom_scan_jquery.compressed.js
11
12
  lib/html_namespacing/plugin/rails.rb
13
+ lib/html_namespacing/plugin/dom_scan_jquery.js
12
14
  lib/html_namespacing/plugin/sass.rb
13
15
  Manifest
14
16
  html_namespacing.gemspec
data/README.rdoc CHANGED
@@ -150,11 +150,11 @@ which is standard Sass and will equate to ".NAMESPACE" in this context.)
150
150
 
151
151
  Available options to <tt>HtmlNamespacing::Plugin::Rails.install</tt> are:
152
152
 
153
- <tt>:template_to_namespace_callback</tt>: Ruby lambda function which accepts an
154
- <tt>ActionView::Template</tt> and returns an HTML namespacing string. The
155
- default is:
153
+ <tt>:path_to_namespace_callback</tt>: Ruby lambda function which accepts a
154
+ relative path (e.g., "foo/bar") and returns an HTML namespacing string
155
+ (e.g., "foo-bar"). The default is:
156
156
 
157
- lambda { |template| template.path =~ /^([^\.]+)/ && $1.gsub('/', '-') || nil }
157
+ lambda { |path| path.gsub('/', '-') }
158
158
 
159
159
  If the callback returns <tt>nil</tt>, HTML namespacing will not be applied.
160
160
 
@@ -167,6 +167,23 @@ an <tt>Exception</tt>, an <tt>ActionView::Template</tt>, and an
167
167
  If your <tt>:handle_exception_callback</tt> does not raise an exception,
168
168
  the template will be rendered as if HtmlNamespacing were not installed.
169
169
 
170
+ <tt>:javascript</tt>: If set, enable
171
+ <tt>html_namespacing_javascript_tag()</tt> (see below). You will need to
172
+ <tt>include HtmlNamespacing::Plugin::Rails::Helpers</tt> in your helpers to
173
+ gain access to this method.
174
+
175
+ <tt>:javascript_root</tt>: Root of namespaced JavaScript files, if you are
176
+ using <tt>html_namespacing_javascript_tag()</tt> (see below).
177
+
178
+ <tt>:javascript_optional_suffix</tt>: optional suffix for your JavaScript
179
+ files, if you are using <tt>html_namespacing_javascript_tag()</tt>. For
180
+ instance, if <tt>:javascript_optional_suffix</tt> is <tt>'compressed'</tt> and
181
+ you rendered the partial <tt>app/views/foos/_foo.html.haml</tt>, then the
182
+ (presumably minified) file
183
+ <tt>app/javascripts/views/foos/_foo.compressed.js</tt> will be included if it
184
+ exists (otherwise the standard <tt>app/javascripts/views/foos/_foo.js</tt>,
185
+ otherwise nothing).
186
+
170
187
  Available options to <tt>HtmlNamespacing::Plugin::Sass.install</tt> are:
171
188
 
172
189
  <tt>:prefix</tt> (default <tt>views</tt>): subdirectory of Sass directory for
@@ -229,66 +246,80 @@ reader.
229
246
 
230
247
  === jQuery Namespacing
231
248
 
232
- Insert the following before all other JavaScript:
249
+ Use the following Rails helper, possibly in your default layout:
250
+
251
+ <tt>html_namespacing_javascript_tag(:jquery)</tt>
252
+
253
+ Afterwards, similar to our namespaced CSS framework above, you can easily
254
+ namespace JavaScript behavior:
255
+
256
+ <tt>app/javascripts/views/foos/_description.js</tt>:
257
+
258
+ $NS().find('p').click(function() { alert("Yup, you clicked!"); });
233
259
 
234
- $(document).ready(function() {
235
- $.NS = {};
260
+ You just write code specific to one Rails partial without actually writing the
261
+ partial's name anywhere, and you will not need to rewrite any selectors if you
262
+ rename or move the partial (as long as you rename or move the corresponding
263
+ JavaScript file).
264
+
265
+ (All namespaced JavaScript partials will be included inline, all in separate
266
+ <tt>jQuery(document).ready()</tt> blocks. Two local variables will be
267
+ available: <tt>NS</tt>, the namespace string, and <tt>$NS()</tt>, a function
268
+ returning a jQuery object containing all elements at the root of the
269
+ namespace.)
270
+
271
+ === Caching and JavaScript Namespacing
272
+
273
+ Caching throws a wrench into operations.
274
+ <tt>html_namespacing_javascript_tag()</tt> works by watching which templates get
275
+ rendered; when reading from the cache, not all templates will have
276
+ <tt>render()</tt> called on them.
277
+
278
+ The general solution is to cache the list of rendered templates along with the
279
+ data being cached. A new view method, <tt>html_rendering_rendered_paths</tt>,
280
+ returns an Array to be used for this purpose. As a practical example, here is a
281
+ plugin which makes JavaScript namespacing work with
282
+ {cache_advance}[http://github.com/aub/cache_advance]:
283
+
284
+ class HtmlNamespacingCachePlugin
285
+ def self.key_suffix
286
+ 'HTML_NAMESPACING_DATA'
287
+ end
236
288
 
237
- var SPLIT_REGEX = /\s+/;
289
+ def cache_it_options(view)
290
+ { :view => view }
291
+ end
238
292
 
239
- function populate_NS_from_node(node) {
240
- var node_stack = [];
241
- node_stack.push(node);
293
+ def before_render(cache_name, cache_key, options)
294
+ options[:html_namespacing_rendered_paths_length] = paths(options).length
295
+ end
242
296
 
243
- while (node_stack.length) {
244
- var cur_node = node_stack.pop();
297
+ def after_render(cache_name, cache_key, data, options)
298
+ old_length = options[:html_namespacing_rendered_paths_length]
299
+ new_length = paths(options).length
245
300
 
246
- // Populate entry in $.NS
247
- var class_name_list = node.className;
248
- var class_names = class_name_list.split(SPLIT_REGEX);
249
- for (var i = 0; i < class_names.length; i++) {
250
- var class_name = class_names[i];
251
- if (!$.NS[class_name]) {
252
- $.NS[class_name] = [cur_node];
253
- } else {
254
- $.NS[class_name].push(cur_node);
255
- }
256
- }
301
+ delta = paths(options)[old_length..new_length]
302
+ unless delta.empty?
303
+ key = cache_key + self.class.key_suffix
304
+ Rails.cache.write(key, delta)
305
+ end
306
+ end
257
307
 
258
- // "recurse" to the children, so they are handled in document order
259
- var children = cur_node.childNodes;
260
- for (var j = children.length - 1; j >= 0; j--) {
261
- var subnode = children[j];
262
- node_stack.push(subnode);
263
- }
264
- }
265
- }
308
+ def after_read(cache_name, cache_key, data, options)
309
+ key = cache_key + self.class.key_suffix
310
+ if delta = Rails.cache.read(key)
311
+ paths(options).concat(delta)
312
+ end
313
+ end
266
314
 
267
- populate_NS_from_node(document.getElementsByTagName('body')[0]);
268
- });
269
-
270
- (The CPU cost of this code is small but nonzero, as it precomputes many
271
- selectors. On a 75kb page with a 2.5Ghz computer, it might take 75ms with
272
- Firebug profiling turned on. This may or may not be faster than writing each
273
- selector by hand.)
274
-
275
- Afterwards, if you set up a JavaScript framework similar to the CSS framework
276
- above, you can easily namespace JavaScript behavior:
277
-
278
- <tt>javascripts/onload/foos/_description.js</tt>:
279
-
280
- $(document).ready(function() {
281
- var $NS = $.NS['foos-_description']; // auto-generated line at top of file
282
- $NS.find('p').click(function() { alert("Yup, you clicked!"); });
283
- });
315
+ protected
316
+
317
+ def paths(options)
318
+ options[:view].html_namespacing_rendered_paths
319
+ end
320
+ end
284
321
 
285
- In the above file, if every line but the third is automatically generated based
286
- on the file's location (an exercise, for now, left to the reader), the third
287
- line can be written in full confidence that only paragraphs within the
288
- <tt>_description</tt> partial will have this jQuery code bound. The end result?
289
- You can write code specific to one Rails partial without actually writing the
290
- partial's name anywhere, and you will not need to rewrite any selectors if you
291
- rename or move the partial.
322
+ Other plugins are welcome; I will consider integrating them into this project.
292
323
 
293
324
  == Tips and Tricks
294
325
 
@@ -312,10 +343,10 @@ Or this Sass:
312
343
 
313
344
  Or the following JavaScript (following the <tt>$NS</tt> example above):
314
345
 
315
- var $div = $NS.find('div');
346
+ var $div = $NS().find('div');
316
347
 
317
- Nothing will be selected, because the <tt>div</tt> element is not a child. You
318
- would need the following CSS:
348
+ Nothing will be selected, because the <tt>div</tt> element is not a child.
349
+ Instead, you need the following CSS:
319
350
 
320
351
  div.foos-show { font-weight: bold; }
321
352
 
@@ -326,7 +357,7 @@ Or this sass:
326
357
 
327
358
  Or the following JavaScript:
328
359
 
329
- var $div = $NS.filter('div');
360
+ var $div = $NS().filter('div');
330
361
 
331
362
  Also, watch out for nesting: selectors with the <tt>foos-show</tt> namespace
332
363
  will match anything inside any partials rendered by
@@ -338,9 +369,12 @@ HTML namespacing produces plenty of tiny CSS and/or JavaScript files. Best
338
369
  practice is to bundle all namespaced files together: by virtue of being
339
370
  namespaced, it is safe to concatenate them. (Advanced CSS users should
340
371
  be thoughtful about
341
- "cascading order and specificity":http://www.w3.org/TR/CSS2/cascade.html#cascading-order;
372
+ {cascading order and specificity}[http://www.w3.org/TR/CSS2/cascade.html#cascading-order]
342
373
  and anybody bundling JavaScript files together should wrap each with
343
- <tt>(function() { ... })();</tt> to prevent variable leakage.)
374
+ <tt>(function() { ... })();</tt> to prevent variable leakage.) CSS files can be
375
+ concatenated using a tool such as
376
+ {asset_library}[http://github.com/adamh/asset_library]; JavaScript files are
377
+ concatenated automatically in <tt>html_namespacing_javascript_tag()</tt>.
344
378
 
345
379
  These and similar strategies should be considered when building an HTML
346
380
  namespacing framework.
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('html_namespacing', '0.0.5') do |p|
5
+ Echoe.new('html_namespacing', '0.1.0') do |p|
6
6
  p.author = 'Adam Hooper'
7
7
  p.email = 'adam@adamhooper.com'
8
8
  p.summary = 'Automatic HTML namespacing'
@@ -61,10 +61,10 @@ html_namespacing_add_namespace_to_html(
61
61
 
62
62
  Check_Type(ns, T_STRING);
63
63
 
64
- html_ptr = RSTRING(html)->ptr;
65
- html_len = RSTRING(html)->len;
64
+ html_ptr = RSTRING_PTR(html);
65
+ html_len = RSTRING_LEN(html);
66
66
 
67
- ns_ptr = RSTRING(ns)->ptr;
67
+ ns_ptr = RSTRING_PTR(ns);
68
68
 
69
69
  rv = add_namespace_to_html_with_length_and_allocation_strategy(
70
70
  html_ptr,
@@ -2,16 +2,16 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{html_namespacing}
5
- s.version = "0.0.5"
5
+ s.version = "0.1.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Adam Hooper"]
9
- s.date = %q{2009-07-10}
9
+ s.date = %q{2009-07-28}
10
10
  s.description = %q{Inserts "class=" attributes within snippets of HTML so CSS and JavaScript can use automatic scopes}
11
11
  s.email = %q{adam@adamhooper.com}
12
12
  s.extensions = ["ext/html_namespacing/extconf.rb"]
13
13
  s.extra_rdoc_files = ["README.rdoc"]
14
- s.files = ["README.rdoc", "Rakefile", "test/c_extension_test.rb", "test/test_helper.rb", "ext/html_namespacing/extconf.rb", "ext/html_namespacing/html_namespacing.h", "ext/html_namespacing/html_namespacing.c", "ext/html_namespacing/html_namespacing_ext.c", "lib/html_namespacing.rb", "lib/html_namespacing/plugin.rb", "lib/html_namespacing/plugin/rails.rb", "lib/html_namespacing/plugin/sass.rb", "Manifest", "html_namespacing.gemspec"]
14
+ s.files = ["README.rdoc", "Rakefile", "test/c_extension_test.rb", "test/test_helper.rb", "ext/html_namespacing/extconf.rb", "ext/html_namespacing/html_namespacing.h", "ext/html_namespacing/html_namespacing.c", "ext/html_namespacing/html_namespacing_ext.c", "lib/html_namespacing.rb", "lib/html_namespacing/plugin.rb", "lib/html_namespacing/plugin/dom_scan_jquery.compressed.js", "lib/html_namespacing/plugin/rails.rb", "lib/html_namespacing/plugin/dom_scan_jquery.js", "lib/html_namespacing/plugin/sass.rb", "Manifest", "html_namespacing.gemspec"]
15
15
  s.has_rdoc = true
16
16
  s.homepage = %q{http://github.com/adamh/html_namespacing}
17
17
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Html_namespacing", "--main", "README.rdoc"]
@@ -0,0 +1 @@
1
+ jQuery(function(b){function a(f){var c=/[ \t\n]+/,m={},o=[f],n,l,p,h,k,g,e,d;while(o.length){n=o.pop();l=n.className;if(l){p=l.split(c);for(k=0;k<p.length;k++){h=p[k];if(!m[h]){m[h]=[n]}else{m[h].push(n)}}}e=n.childNodes;for(var g=e.length-1;g>=0;g--){d=e[g];o.push(d)}}return m}b.NS=a(document.getElementsByTagName("body")[0])});
@@ -0,0 +1,44 @@
1
+ jQuery(function($) {
2
+ function NS_from_node(root_node) {
3
+ var SPLIT_REGEX = /[ \t\n]+/,
4
+ ret = {},
5
+ node_stack = [root_node],
6
+ cur_node,
7
+ class_name_list,
8
+ class_names,
9
+ class_name,
10
+ i,
11
+ j,
12
+ children,
13
+ subnode;
14
+
15
+ while (node_stack.length) {
16
+ cur_node = node_stack.pop();
17
+
18
+ // Populate entry in $.NS
19
+ class_name_list = cur_node.className;
20
+ if (class_name_list) {
21
+ class_names = class_name_list.split(SPLIT_REGEX);
22
+ for (i = 0; i < class_names.length; i++) {
23
+ class_name = class_names[i];
24
+ if (!ret[class_name]) {
25
+ ret[class_name] = [cur_node];
26
+ } else {
27
+ ret[class_name].push(cur_node);
28
+ }
29
+ }
30
+ }
31
+
32
+ // "recurse" to the children, so they are handled in document order
33
+ children = cur_node.childNodes;
34
+ for (var j = children.length - 1; j >= 0; j--) {
35
+ subnode = children[j];
36
+ node_stack.push(subnode);
37
+ }
38
+ }
39
+
40
+ return ret;
41
+ }
42
+
43
+ $.NS = NS_from_node(document.getElementsByTagName('body')[0]);
44
+ });
@@ -3,15 +3,15 @@ module HtmlNamespacing
3
3
  module Rails
4
4
  def self.install(options = {})
5
5
  @options = options
6
- install_rails_2_3
6
+ install_rails_2_3(options)
7
7
  end
8
8
 
9
9
  # Called by ActionView
10
- def self.path_to_namespace(template)
11
- if func = @options[:template_to_namespace_callback]
12
- func.call(template)
10
+ def self.path_to_namespace(relative_path)
11
+ if func = @options[:path_to_namespace_callback]
12
+ func.call(relative_path)
13
13
  else
14
- HtmlNamespacing::Plugin.default_relative_path_to_namespace(template.path)
14
+ HtmlNamespacing::Plugin.default_relative_path_to_namespace(relative_path)
15
15
  end
16
16
  end
17
17
 
@@ -23,31 +23,111 @@ module HtmlNamespacing
23
23
  end
24
24
  end
25
25
 
26
+ def self.javascript_root
27
+ @options[:javascript_root] || RAILS_ROOT + '/app/javascripts/views'
28
+ end
29
+
30
+ def self.javascript_optional_suffix
31
+ @options[:javascript_optional_suffix]
32
+ end
33
+
34
+ module Helpers
35
+ def html_namespacing_javascript_tag(framework)
36
+ files = html_namespacing_files
37
+ unless files.empty?
38
+ r = "<script type=\"text/javascript\"><!--//--><![CDATA[//><!--\n"
39
+ r << HtmlNamespaceJs[framework][:top]
40
+ r << "\n"
41
+ files.collect do |relative_path, absolute_path|
42
+ r << html_namespacing_inline_js(framework, relative_path, absolute_path)
43
+ r << "\n"
44
+ end
45
+ r << "//--><!]]></script>"
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ HtmlNamespaceJs = {
52
+ :jquery => {
53
+ :top => File.open(File.join(File.dirname(__FILE__), 'dom_scan_jquery.compressed.js')) { |f| f.read },
54
+ :each => 'jQuery(function($){var NS=%s,$NS=function(){return $($.NS[NS])};%s});'
55
+ }
56
+ }
57
+
58
+ def html_namespacing_files
59
+ suffix = ::HtmlNamespacing::Plugin::Rails.javascript_optional_suffix
60
+ root = ::HtmlNamespacing::Plugin::Rails.javascript_root
61
+
62
+ r = html_namespacing_rendered_paths
63
+
64
+ r.uniq!
65
+
66
+ r.collect! do |relative_path|
67
+ prefix = File.join(root, relative_path)
68
+ absolute_path = "#{prefix}.js"
69
+ absolute_path_with_suffix = "#{prefix}.#{suffix}.js" if suffix
70
+
71
+ if suffix && File.exist?(absolute_path_with_suffix)
72
+ [relative_path, absolute_path_with_suffix]
73
+ elsif File.exist?(absolute_path)
74
+ [relative_path, absolute_path]
75
+ else
76
+ nil
77
+ end
78
+ end
79
+
80
+ r.compact!
81
+
82
+ r
83
+ end
84
+
85
+ def html_namespacing_inline_js(framework, relative_path, absolute_path)
86
+ namespace = ::HtmlNamespacing::Plugin::Rails.path_to_namespace(relative_path)
87
+ content = File.open(absolute_path) { |f| f.read }
88
+
89
+ HtmlNamespaceJs[framework][:each] % [namespace.to_json, content]
90
+ end
91
+ end
92
+
26
93
  private
27
94
 
28
95
  def self.install_rails_2_3(options = {})
96
+ if options[:javascript]
97
+ ::ActionView::Base.class_eval do
98
+ attr_writer(:html_namespacing_rendered_paths)
99
+ def html_namespacing_rendered_paths
100
+ @html_namespacing_rendered_paths ||= []
101
+ end
102
+ end
103
+ end
104
+
29
105
  ::ActionView::Template.class_eval do
30
- def render_template_with_html_namespacing(*args)
31
- s = render_template_without_html_namespacing(*args)
106
+ def render_with_html_namespacing(view, local_assigns = {})
107
+ html = render_without_html_namespacing(view, local_assigns)
108
+
109
+ view.html_namespacing_rendered_paths << path_without_format_and_extension if view.respond_to?(:html_namespacing_rendered_paths)
32
110
 
33
111
  if format == 'html'
34
- begin
35
- HtmlNamespacing::add_namespace_to_html(s, html_namespace)
36
- rescue ArgumentError => e
37
- view = args.first
38
- HtmlNamespacing::Plugin::Rails.handle_exception(e, self, view)
39
- s # unless handle_exception() raised something
40
- end
112
+ add_namespace_to_html(html, view)
41
113
  else
42
- s
114
+ html
43
115
  end
44
116
  end
45
- alias_method_chain :render_template, :html_namespacing
117
+ alias_method_chain :render, :html_namespacing
118
+
119
+ private
120
+
121
+ def add_namespace_to_html(html, view)
122
+ HtmlNamespacing::add_namespace_to_html(html, html_namespace)
123
+ rescue ArgumentError => e
124
+ HtmlNamespacing::Plugin::Rails.handle_exception(e, self, view)
125
+ html # unless handle_exception() raised something
126
+ end
46
127
 
47
128
  def html_namespace
48
- HtmlNamespacing::Plugin::Rails.path_to_namespace(self)
129
+ HtmlNamespacing::Plugin::Rails.path_to_namespace(path_without_format_and_extension)
49
130
  end
50
- memoize :html_namespace
51
131
  end
52
132
  end
53
133
  end
@@ -4,9 +4,7 @@ module HtmlNamespacing
4
4
  autoload(:Sass, File.dirname(__FILE__) + '/plugin/sass')
5
5
 
6
6
  def self.default_relative_path_to_namespace(path)
7
- if path =~ /^([^\.]+)/
8
- $1.gsub(/\//, '-')
9
- end
7
+ path.gsub(/\//, '-')
10
8
  end
11
9
  end
12
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adamh-html_namespacing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Hooper
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-10 00:00:00 -07:00
12
+ date: 2009-07-28 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -32,12 +32,15 @@ files:
32
32
  - ext/html_namespacing/html_namespacing_ext.c
33
33
  - lib/html_namespacing.rb
34
34
  - lib/html_namespacing/plugin.rb
35
+ - lib/html_namespacing/plugin/dom_scan_jquery.compressed.js
35
36
  - lib/html_namespacing/plugin/rails.rb
37
+ - lib/html_namespacing/plugin/dom_scan_jquery.js
36
38
  - lib/html_namespacing/plugin/sass.rb
37
39
  - Manifest
38
40
  - html_namespacing.gemspec
39
41
  has_rdoc: true
40
42
  homepage: http://github.com/adamh/html_namespacing
43
+ licenses:
41
44
  post_install_message:
42
45
  rdoc_options:
43
46
  - --line-numbers
@@ -64,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
67
  requirements: []
65
68
 
66
69
  rubyforge_project: html_namespacing
67
- rubygems_version: 1.2.0
70
+ rubygems_version: 1.3.5
68
71
  signing_key:
69
72
  specification_version: 2
70
73
  summary: Automatic HTML namespacing