wycats-merb-core 0.9.8 → 0.9.9

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.
Files changed (58) hide show
  1. data/CHANGELOG +136 -2
  2. data/CONTRIBUTORS +6 -0
  3. data/PUBLIC_CHANGELOG +15 -0
  4. data/Rakefile +12 -14
  5. data/lib/merb-core.rb +82 -43
  6. data/lib/merb-core/bootloader.rb +268 -60
  7. data/lib/merb-core/config.rb +119 -34
  8. data/lib/merb-core/controller/abstract_controller.rb +58 -18
  9. data/lib/merb-core/controller/exceptions.rb +2 -15
  10. data/lib/merb-core/controller/merb_controller.rb +28 -1
  11. data/lib/merb-core/controller/mime.rb +4 -0
  12. data/lib/merb-core/controller/mixins/controller.rb +14 -17
  13. data/lib/merb-core/controller/mixins/render.rb +23 -28
  14. data/lib/merb-core/controller/mixins/responder.rb +0 -1
  15. data/lib/merb-core/controller/template.rb +44 -20
  16. data/lib/merb-core/core_ext/kernel.rb +8 -3
  17. data/lib/merb-core/dispatch/default_exception/default_exception.rb +1 -1
  18. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +3 -1
  19. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +71 -67
  20. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +6 -2
  21. data/lib/merb-core/dispatch/dispatcher.rb +5 -9
  22. data/lib/merb-core/dispatch/request.rb +46 -57
  23. data/lib/merb-core/dispatch/router.rb +83 -6
  24. data/lib/merb-core/dispatch/router/behavior.rb +87 -27
  25. data/lib/merb-core/dispatch/router/resources.rb +281 -167
  26. data/lib/merb-core/dispatch/router/route.rb +141 -27
  27. data/lib/merb-core/logger.rb +213 -202
  28. data/lib/merb-core/rack.rb +3 -1
  29. data/lib/merb-core/rack/adapter.rb +7 -4
  30. data/lib/merb-core/rack/adapter/ebb.rb +12 -13
  31. data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -15
  32. data/lib/merb-core/rack/adapter/irb.rb +3 -2
  33. data/lib/merb-core/rack/adapter/mongrel.rb +22 -15
  34. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +4 -16
  35. data/lib/merb-core/rack/adapter/thin.rb +21 -22
  36. data/lib/merb-core/rack/adapter/thin_turbo.rb +4 -11
  37. data/lib/merb-core/rack/adapter/webrick.rb +54 -18
  38. data/lib/merb-core/rack/handler/mongrel.rb +12 -13
  39. data/lib/merb-core/rack/middleware/csrf.rb +1 -1
  40. data/lib/merb-core/server.rb +135 -98
  41. data/lib/merb-core/tasks/gem_management.rb +50 -12
  42. data/lib/merb-core/tasks/merb.rb +1 -0
  43. data/lib/merb-core/tasks/merb_rake_helper.rb +9 -38
  44. data/lib/merb-core/tasks/stats.rake +2 -2
  45. data/lib/merb-core/test.rb +9 -3
  46. data/lib/merb-core/test/helpers.rb +1 -0
  47. data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -2
  48. data/lib/merb-core/test/helpers/request_helper.rb +40 -372
  49. data/lib/merb-core/test/helpers/route_helper.rb +15 -7
  50. data/lib/merb-core/test/matchers.rb +1 -0
  51. data/lib/merb-core/test/matchers/controller_matchers.rb +4 -247
  52. data/lib/merb-core/test/matchers/view_matchers.rb +22 -4
  53. data/lib/merb-core/test/run_specs.rb +117 -25
  54. data/lib/merb-core/version.rb +1 -1
  55. metadata +1 -1
  56. data/lib/merb-core/vendor/facets.rb +0 -2
  57. data/lib/merb-core/vendor/facets/dictionary.rb +0 -433
  58. data/lib/merb-core/vendor/facets/inflect.rb +0 -342
@@ -7,6 +7,10 @@ module Merb
7
7
  ResponderMixin::TYPES
8
8
  end
9
9
 
10
+ def available_accepts
11
+ ResponderMixin::MIMES
12
+ end
13
+
10
14
  # Any specific outgoing headers should be included here. These are not
11
15
  # the content-type header but anything in addition to it.
12
16
  # +transform_method+ should be set to a symbol of the method used to
@@ -62,6 +62,7 @@ module Merb
62
62
  # ==== Parameters
63
63
  # data<String>:: a chunk of data to return.
64
64
  def send_chunk(data)
65
+ only_runs_on_mongrel!
65
66
  @response.write('%x' % data.size + "\r\n")
66
67
  @response.write(data + "\r\n")
67
68
  end
@@ -76,12 +77,8 @@ module Merb
76
77
  # A block that Mongrel can call later, allowing Merb to release the
77
78
  # thread lock and render another request.
78
79
  def render_deferred(&blk)
79
- must_support_streaming!
80
80
  Proc.new {|response|
81
- result = blk.call
82
- response.send_status(result.length)
83
- response.send_header
84
- response.write(result)
81
+ response.write(blk.call)
85
82
  }
86
83
  end
87
84
 
@@ -96,10 +93,7 @@ module Merb
96
93
  # Proc::
97
94
  # A block that Mongrel can call after returning the string to the user.
98
95
  def render_then_call(str, &blk)
99
- must_support_streaming!
100
96
  Proc.new {|response|
101
- response.send_status(str.length)
102
- response.send_header
103
97
  response.write(str)
104
98
  blk.call
105
99
  }
@@ -166,7 +160,13 @@ module Merb
166
160
  'Content-Disposition' => disposition,
167
161
  'Content-Transfer-Encoding' => 'binary'
168
162
  )
169
- File.open(file, 'rb')
163
+ Proc.new {|response|
164
+ file = File.open(file, 'rb')
165
+ while chunk = file.read(16384)
166
+ response.write chunk
167
+ end
168
+ file.close
169
+ }
170
170
  end
171
171
 
172
172
  # Send binary data over HTTP to the user as a file download. May set content type,
@@ -217,7 +217,6 @@ module Merb
217
217
  # end
218
218
  # end
219
219
  def stream_file(opts={}, &stream)
220
- must_support_streaming!
221
220
  opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
222
221
  disposition = opts[:disposition].dup || 'attachment'
223
222
  disposition << %(; filename="#{opts[:filename]}")
@@ -229,8 +228,6 @@ module Merb
229
228
  'CONTENT-LENGTH' => opts[:content_length].to_s
230
229
  )
231
230
  Proc.new{|response|
232
- response.send_status(opts[:content_length])
233
- response.send_header
234
231
  stream.call(response)
235
232
  }
236
233
  end
@@ -306,13 +303,13 @@ module Merb
306
303
  alias escape_html escape_xml
307
304
 
308
305
  private
309
- # Checks whether streaming is supported by the current Rack adapter.
306
+ # Marks an output method that only runs on the mongrel webserver.
310
307
  #
311
308
  # ==== Raises
312
- # NotImplemented:: The Rack adapter doens't support streaming.
313
- def must_support_streaming!
314
- unless request.env['rack.streaming']
315
- raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter does not support streaming")
309
+ # NotImplemented:: The Rack adapter is not mongrel.
310
+ def only_runs_on_mongrel!
311
+ unless Merb::Config[:log_stream] == 'mongrel'
312
+ raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter is not mongrel. cannot support this feature")
316
313
  end
317
314
  end
318
315
  end
@@ -268,17 +268,6 @@ module Merb::RenderMixin
268
268
  #
269
269
  # In this case, "one" will be available in the partial through the local
270
270
  # variable named +number+.
271
- #
272
- # ==== Notes
273
- # It is important to note that the object being passed to the partial
274
- # as well as any extra local variables cannot use names of helper methods
275
- # since any helper method of the same name will take precedence over the
276
- # passed variable. Example:
277
- #
278
- # partial :bar, :with => "one", :as => :partial
279
- #
280
- # In this case, "one" will not be available in the partial because "partial"
281
- # is already a helper method.
282
271
  def partial(template, opts={})
283
272
 
284
273
  # partial :foo becomes "#{controller_name}/_foo"
@@ -290,32 +279,36 @@ module Merb::RenderMixin
290
279
  kontroller = (m = template.match(/.*(?=\/)/)) ? m[0] : controller_name
291
280
  template = "_#{File.basename(template)}"
292
281
  end
293
- template_method, template_location =
294
- _template_for(template, opts.delete(:format) || content_type, kontroller, template_path)
295
282
 
296
- (@_old_partial_locals ||= []).push @_merb_partial_locals
297
-
298
283
  # This handles no :with as well
299
284
  with = [opts.delete(:with)].flatten
300
- as = opts.delete(:as) || template_location.match(%r[.*/_([^\.]*)])[1]
301
-
302
- @_merb_partial_locals = opts.merge(:collection_index => -1, :collection_size => with.size)
285
+ as = (opts.delete(:as) || template.match(%r[(?:.*/)?_([^\./]*)])[1]).to_sym
286
+
287
+ # Ensure that as is in the locals hash even if it isn't passed in here
288
+ # so that it's included in the preamble.
289
+ locals = opts.merge(:collection_index => -1, :collection_size => with.size, as => opts[as])
290
+ template_method, template_location = _template_for(
291
+ template,
292
+ opts.delete(:format) || content_type,
293
+ kontroller,
294
+ template_path,
295
+ locals.keys)
303
296
 
304
297
  # this handles an edge-case where the name of the partial is _foo.* and your opts
305
298
  # have :foo as a key.
306
- named_local = @_merb_partial_locals.key?(as.to_sym)
299
+ named_local = opts.key?(as)
307
300
 
308
301
  sent_template = with.map do |temp|
309
- @_merb_partial_locals[as.to_sym] = temp unless named_local
302
+ locals[as] = temp unless named_local
303
+
310
304
  if template_method && self.respond_to?(template_method)
311
- @_merb_partial_locals[:collection_index] += 1
312
- send(template_method)
305
+ locals[:collection_index] += 1
306
+ send(template_method, locals)
313
307
  else
314
308
  raise TemplateNotFound, "Could not find template at #{template_location}.*"
315
309
  end
316
310
  end.join
317
311
 
318
- @_merb_partial_locals = @_old_partial_locals.pop
319
312
  sent_template
320
313
  end
321
314
 
@@ -382,6 +375,7 @@ module Merb::RenderMixin
382
375
  # context<Object>:: The controller action or template (basename or absolute path).
383
376
  # content_type<~to_s>:: The content type (like html or json).
384
377
  # controller<~to_s>:: The name of the controller. Defaults to nil.
378
+ # locals<Array[Symbol]>:: A list of locals to assign from the args passed into the compiled template.
385
379
  #
386
380
  # ==== Options (opts)
387
381
  # :template<String>::
@@ -391,13 +385,13 @@ module Merb::RenderMixin
391
385
  # ==== Returns
392
386
  # Array[Symbol, String]::
393
387
  # A pair consisting of the template method and location.
394
- def _template_for(context, content_type, controller=nil, template=nil)
388
+ def _template_for(context, content_type, controller=nil, template=nil, locals=[])
395
389
  template_method, template_location = nil, nil
396
390
 
397
391
  # absolute path to a template (:template => "/foo/bar")
398
392
  if template.is_a?(String) && template =~ %r{^/}
399
393
  template_location = self._absolute_template_location(template, content_type)
400
- return [_template_method_for(template_location), template_location]
394
+ return [_template_method_for(template_location, locals), template_location]
401
395
  end
402
396
 
403
397
  self.class._template_roots.reverse_each do |root, template_meth|
@@ -409,7 +403,7 @@ module Merb::RenderMixin
409
403
  template_location = root / self.send(template_meth, context, content_type, controller)
410
404
  end
411
405
 
412
- break if template_method = _template_method_for(template_location.to_s)
406
+ break if template_method = _template_method_for(template_location.to_s, locals)
413
407
  end
414
408
 
415
409
  # template_location is a Pathname
@@ -421,11 +415,12 @@ module Merb::RenderMixin
421
415
  #
422
416
  # ==== Parameters
423
417
  # template_location<String>:: The phyical path of the template
418
+ # locals<Array[Symbol]>:: A list of locals to assign from the args passed into the compiled template.
424
419
  #
425
420
  # ==== Returns
426
421
  # String:: The method, if it exists. Otherwise return nil.
427
- def _template_method_for(template_location)
428
- meth = Merb::Template.template_for(template_location)
422
+ def _template_method_for(template_location, locals)
423
+ meth = Merb::Template.template_for(template_location, [], locals)
429
424
  meth && self.respond_to?(meth) ? meth : nil
430
425
  end
431
426
 
@@ -1,6 +1,5 @@
1
1
  require 'enumerator'
2
2
  require 'merb-core/controller/mime'
3
- require "merb-core/vendor/facets/dictionary"
4
3
 
5
4
  module Merb
6
5
  # The ResponderMixin adds methods that help you manage what
@@ -3,9 +3,10 @@ end
3
3
 
4
4
  module Merb::Template
5
5
 
6
- EXTENSIONS = {} unless defined?(EXTENSIONS)
7
- METHOD_LIST = {} unless defined?(METHOD_LIST)
8
- MTIMES = {} unless defined?(MTIMES)
6
+ EXTENSIONS = {} unless defined?(EXTENSIONS)
7
+ METHOD_LIST = {} unless defined?(METHOD_LIST)
8
+ SUPPORTED_LOCALS_LIST = Hash.new([].freeze) unless defined?(SUPPORTED_LOCALS_LIST)
9
+ MTIMES = {} unless defined?(MTIMES)
9
10
 
10
11
  class << self
11
12
  # Get the template's method name from a full path. This replaces
@@ -55,26 +56,37 @@ module Merb::Template
55
56
  # ==== Parameters
56
57
  # path<String>:: A full path to find a template method for.
57
58
  # template_stack<Array>:: The template stack. Not used.
59
+ # locals<Array[Symbol]>:: The names of local variables
58
60
  #
59
61
  # ==== Returns
60
62
  # <String>:: name of the method that inlines the template.
61
63
  #---
62
64
  # @semipublic
63
- def template_for(path, template_stack = [])
65
+ def template_for(path, template_stack = [], locals=[])
64
66
  path = File.expand_path(path)
65
67
 
66
- ret =
67
- if Merb::Config[:reload_templates]
68
+ if needs_compilation?(path, locals)
68
69
  file = Dir["#{path}.{#{template_extensions.join(',')}}"].first
69
- METHOD_LIST[path] = file ? inline_template(load_template_io(file)) : nil
70
- else
71
- METHOD_LIST[path] ||= begin
72
- file = Dir["#{path}.{#{template_extensions.join(',')}}"].first
73
- file ? inline_template(load_template_io(file)) : nil
74
- end
70
+ METHOD_LIST[path] = file ? inline_template(load_template_io(file), locals) : nil
75
71
  end
76
72
 
77
- ret
73
+ METHOD_LIST[path]
74
+ end
75
+
76
+ # Decide if a template needs to be re/compiled.
77
+ #
78
+ # ==== Parameters
79
+ # path<String>:: The full path of the template to check support for.
80
+ # locals<Array[Symbol]>:: The list of locals that need to be supported
81
+ #
82
+ # ==== Returns
83
+ # Boolean:: Whether or not the template for the provided path needs to be recompiled
84
+ #---
85
+ def needs_compilation?(path, locals)
86
+ return true if Merb::Config[:reload_templates] || !METHOD_LIST[path]
87
+
88
+ current_locals = SUPPORTED_LOCALS_LIST[path]
89
+ locals.any?{|local| !current_locals.include?(local)}
78
90
  end
79
91
 
80
92
  # Get all known template extensions
@@ -93,6 +105,9 @@ module Merb::Template
93
105
  # ==== Parameters
94
106
  # io<#path>::
95
107
  # An IO that responds to #path (File or VirtualFile)
108
+ # locals<Array[Symbol]>::
109
+ # A list of local names that should be assigned in the template method
110
+ # from the arguments hash. Defaults to [].
96
111
  # mod<Module>::
97
112
  # The module to put the compiled method into. Defaults to
98
113
  # Merb::InlineTemplates
@@ -102,10 +117,14 @@ module Merb::Template
102
117
  # must be available to instances of AbstractController that will use it.
103
118
  #---
104
119
  # @public
105
- def inline_template(io, mod = Merb::InlineTemplates)
106
- path = File.expand_path(io.path)
107
- ret = METHOD_LIST[path.gsub(/\.[^\.]*$/, "")] =
108
- engine_for(path).compile_template(io, template_name(path), mod)
120
+ def inline_template(io, locals=[], mod = Merb::InlineTemplates)
121
+ full_file_path = File.expand_path(io.path)
122
+ engine_neutral_path = full_file_path.gsub(/\.[^\.]*$/, "")
123
+
124
+ SUPPORTED_LOCALS_LIST[engine_neutral_path] |= locals unless locals.empty?
125
+ ret = METHOD_LIST[engine_neutral_path] =
126
+ engine_for(full_file_path).compile_template(io, template_name(full_file_path), locals, mod)
127
+
109
128
  io.close
110
129
  ret
111
130
  end
@@ -156,12 +175,17 @@ module Merb::Template
156
175
  # ==== Parameters
157
176
  # io<#path>:: An IO containing the full path of the template.
158
177
  # name<String>:: The name of the method that will be created.
178
+ # locals<Array[Symbol]>:: A list of locals to assign from the args passed into the compiled template.
159
179
  # mod<Module>:: The module that the compiled method will be placed into.
160
- def self.compile_template(io, name, mod)
180
+ def self.compile_template(io, name, locals, mod)
161
181
  template = ::Erubis::BlockAwareEruby.new(io.read)
162
-
163
182
  _old_verbose, $VERBOSE = $VERBOSE, nil
164
- template.def_method(mod, name, File.expand_path(io.path))
183
+ assigns = locals.inject([]) do |assigns, local|
184
+ assigns << "#{local} = _locals[#{local.inspect}]"
185
+ end.join(";")
186
+
187
+ code = "def #{name}(_locals={}); #{assigns}; #{template.src}; end"
188
+ mod.module_eval code, File.expand_path(io.path)
165
189
  $VERBOSE = _old_verbose
166
190
 
167
191
  name
@@ -34,10 +34,13 @@ module Kernel
34
34
  # @param name<String> The name of the gem to load.
35
35
  # @param *ver<Gem::Requirement, Gem::Version, Array, #to_str>
36
36
  # Version requirements to be passed to Gem::Dependency.new.
37
+ # If the last argument is a Hash, extract the :immediate option,
38
+ # forcing a dependency to load immediately.
37
39
  #
38
40
  # @return <Gem::Dependency> The dependency information.
39
41
  def dependency(name, *ver)
40
- if Merb::BootLoader.finished?(Merb::BootLoader::Dependencies)
42
+ immediate = ver.last.is_a?(Hash) && ver.pop[:immediate]
43
+ if immediate || Merb::BootLoader.finished?(Merb::BootLoader::Dependencies)
41
44
  load_dependency(name, *ver)
42
45
  else
43
46
  track_dependency(name, *ver)
@@ -230,9 +233,11 @@ module Kernel
230
233
  else
231
234
  lines = File.read(file).split("\n")
232
235
  first_line = (f = line - size - 1) < 0 ? 0 : f
233
- lines = lines[first_line, size * 2 + 1]
234
236
 
235
- lines.each_with_index do |str, index|
237
+ old_lines = lines
238
+ lines = lines[first_line, size * 2 + 1]
239
+
240
+ lines && lines.each_with_index do |str, index|
236
241
  yield index + line - size, str.chomp
237
242
  end
238
243
  end
@@ -41,7 +41,7 @@ module Merb
41
41
  ret << " <tbody>"
42
42
  (arr || []).each_with_index do |(key, val), i|
43
43
  klass = i % 2 == 0 ? "even" : "odd"
44
- ret << " <tr class=#{klass}><td>#{key}</td><td>#{val.inspect}</td></tr>"
44
+ ret << " <tr class=\"#{klass}\"><td>#{key}</td><td>#{val.inspect}</td></tr>"
45
45
  end
46
46
  if arr.blank?
47
47
  ret << " <tr class='odd'><td colspan='2'>None</td></tr>"
@@ -1,3 +1,4 @@
1
+ <% if Merb.env != "test" %>
1
2
  <style type="text/css" media="screen">
2
3
  body {
3
4
  font-family:arial;
@@ -195,4 +196,5 @@
195
196
  color:#444;
196
197
  text-align:right;
197
198
  }
198
- </style>
199
+ </style>
200
+ <% end %>
@@ -1,73 +1,77 @@
1
- <script type="text/javascript" charset="utf-8">
2
- (function() {
3
- els = document.getElementsByTagName('td');
4
- var forEach = function(arr, fn) {
5
- for(var i=0; i<arr.length; i++) {
6
- var res = fn(arr[i]);
7
- if(res === false) break;
8
- }
9
- }
10
- var toggleClasses = function(node, first, second) {
11
- var classes = node.className.split(" ");
12
- var newClasses = [];
13
- forEach(classes, function(k) {
14
- if(k == first) newClasses.push(second);
15
- else if(k == second) newClasses.push(first);
16
- else newClasses.push(k);
17
- });
18
- node.className = newClasses.join(" ");
19
- }
20
- forEach(els, function(el) {
21
- // swap the open & closed classes
22
- if(hasClass(el, "expand") || hasClass(el, "collapse")) {
23
- el.onclick = function(e){
24
- tbody = this.parentNode.parentNode;
25
- toggleClasses(tbody, "open", "close");
26
- }
27
- }
28
- })
29
- forEach(document.getElementsByTagName("h3"), function(el) {
30
- el.onclick = function(e) {
31
- var tag = this.nextSibling;
32
- while(tag.nodeType != 1) tag = tag.nextSibling;
33
- tag.style.display = tag.style.display == "none" ? "" : "none";
34
- }
35
- })
36
- function hasClass(node, matchClass) {
37
- var classes = node.className.split(" ");
38
- for(var i=0,className;className=classes[i];i++)
39
- if(className == matchClass) return true;
40
- return false;
41
- }
42
- var els = document.getElementsByTagName("p");
43
- forEach(els, function(tag) {
44
- if(tag.className != "options") return true;
45
- var checkboxes = tag.getElementsByTagName("input");
46
- forEach(checkboxes, function(box) {
47
- if(window.navigator.userAgent.match(/Firefox/)) {
48
- box.style.top = "3px";
1
+ <% if Merb.env != "test" %>
2
+ <script type="text/javascript">
3
+ //<![CDATA[
4
+ (function() {
5
+ els = document.getElementsByTagName('td');
6
+ var forEach = function(arr, fn) {
7
+ for(var i=0; i<arr.length; i++) {
8
+ var res = fn(arr[i]);
9
+ if(res === false) break;
49
10
  }
50
- });
51
- tag.getElementsByTagName("input")[0].onclick = function(e) {
11
+ }
12
+ var toggleClasses = function(node, first, second) {
13
+ var classes = node.className.split(" ");
14
+ var newClasses = [];
15
+ forEach(classes, function(k) {
16
+ if(k == first) newClasses.push(second);
17
+ else if(k == second) newClasses.push(first);
18
+ else newClasses.push(k);
19
+ });
20
+ node.className = newClasses.join(" ");
21
+ }
22
+ forEach(els, function(el) {
23
+ // swap the open & closed classes
24
+ if(hasClass(el, "expand") || hasClass(el, "collapse")) {
25
+ el.onclick = function(e){
26
+ tbody = this.parentNode.parentNode;
27
+ toggleClasses(tbody, "open", "close");
28
+ }
29
+ }
30
+ })
31
+ forEach(document.getElementsByTagName("h3"), function(el) {
32
+ el.onclick = function(e) {
33
+ var tag = this.nextSibling;
34
+ while(tag.nodeType != 1) tag = tag.nextSibling;
35
+ tag.style.display = tag.style.display == "none" ? "" : "none";
36
+ }
37
+ })
38
+ function hasClass(node, matchClass) {
39
+ var classes = node.className.split(" ");
40
+ for(var i=0,className;className=classes[i];i++)
41
+ if(className == matchClass) return true;
42
+ return false;
43
+ }
44
+ var els = document.getElementsByTagName("p");
45
+ forEach(els, function(tag) {
46
+ if(tag.className != "options") return true;
47
+ var checkboxes = tag.getElementsByTagName("input");
52
48
  forEach(checkboxes, function(box) {
53
- if(box == e.target) return true;
54
- box.checked = e.target.checked;
55
- toggleTraces(box, box);
56
- })
57
- };
58
- var toggleTraces = function(box, target) {
59
- var tbodies = tag.parentNode.getElementsByTagName("tbody");
60
- forEach(tbodies, function(tbody) {
61
- if(hasClass(tbody, target.parentNode.className)) {
62
- if(target.checked) tbody.style.display = "";
63
- else tbody.style.display = "none";
49
+ if(window.navigator.userAgent.match(/Firefox/)) {
50
+ box.style.top = "3px";
64
51
  }
52
+ });
53
+ tag.getElementsByTagName("input")[0].onclick = function(e) {
54
+ forEach(checkboxes, function(box) {
55
+ if(box == e.target) return true;
56
+ box.checked = e.target.checked;
57
+ toggleTraces(box, box);
58
+ })
59
+ };
60
+ var toggleTraces = function(box, target) {
61
+ var tbodies = tag.parentNode.getElementsByTagName("tbody");
62
+ forEach(tbodies, function(tbody) {
63
+ if(hasClass(tbody, target.parentNode.className)) {
64
+ if(target.checked) tbody.style.display = "";
65
+ else tbody.style.display = "none";
66
+ }
67
+ })
68
+ }
69
+ forEach(checkboxes, function(box) {
70
+ if(box == checkboxes[0]) return true;
71
+ box.onchange = function(e) { toggleTraces(box, e.target) }
65
72
  })
66
- }
67
- forEach(checkboxes, function(box) {
68
- if(box == checkboxes[0]) return true;
69
- box.onchange = function(e) { toggleTraces(box, e.target) }
70
73
  })
71
- })
72
- })();
74
+ })();
75
+ //]]>
73
76
  </script>
77
+ <% end %>