rear 0.2.0

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 (121) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +7 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +20 -0
  5. data/LICENSE +19 -0
  6. data/README.md +101 -0
  7. data/Rakefile +79 -0
  8. data/assets/api.js +307 -0
  9. data/assets/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css +8 -0
  10. data/assets/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js +26 -0
  11. data/assets/bootstrap/css/bootstrap-responsive.min.css +9 -0
  12. data/assets/bootstrap/css/bootstrap.min.css +9 -0
  13. data/assets/bootstrap/img/glyphicons-halflings-white.png +0 -0
  14. data/assets/bootstrap/img/glyphicons-halflings.png +0 -0
  15. data/assets/bootstrap/js/bootstrap.min.js +6 -0
  16. data/assets/jquery.js +5 -0
  17. data/assets/noty/jquery.noty.js +520 -0
  18. data/assets/noty/layouts/top.js +34 -0
  19. data/assets/noty/layouts/topRight.js +43 -0
  20. data/assets/noty/promise.js +432 -0
  21. data/assets/noty/themes/default.js +156 -0
  22. data/assets/select2-bootstrap.css +86 -0
  23. data/assets/select2/select2-spinner.gif +0 -0
  24. data/assets/select2/select2.css +652 -0
  25. data/assets/select2/select2.min.js +22 -0
  26. data/assets/select2/select2.png +0 -0
  27. data/assets/select2/select2x2.png +0 -0
  28. data/assets/ui.css +75 -0
  29. data/assets/xhr.js +4 -0
  30. data/bin/rear +65 -0
  31. data/docs/Assocs.md +100 -0
  32. data/docs/Columns.md +404 -0
  33. data/docs/Deploy.md +62 -0
  34. data/docs/FileManager.md +75 -0
  35. data/docs/Filters.md +341 -0
  36. data/docs/Setup.md +201 -0
  37. data/lib/rear.rb +13 -0
  38. data/lib/rear/actions.rb +98 -0
  39. data/lib/rear/constants.rb +61 -0
  40. data/lib/rear/controller_setup.rb +249 -0
  41. data/lib/rear/helpers.rb +17 -0
  42. data/lib/rear/helpers/class.rb +46 -0
  43. data/lib/rear/helpers/columns.rb +68 -0
  44. data/lib/rear/helpers/filters.rb +147 -0
  45. data/lib/rear/helpers/generic.rb +73 -0
  46. data/lib/rear/helpers/order.rb +47 -0
  47. data/lib/rear/helpers/pager.rb +35 -0
  48. data/lib/rear/helpers/render.rb +37 -0
  49. data/lib/rear/home_controller.rb +10 -0
  50. data/lib/rear/input.rb +341 -0
  51. data/lib/rear/orm.rb +73 -0
  52. data/lib/rear/rear.rb +74 -0
  53. data/lib/rear/setup.rb +9 -0
  54. data/lib/rear/setup/associations.rb +33 -0
  55. data/lib/rear/setup/columns.rb +109 -0
  56. data/lib/rear/setup/filters.rb +314 -0
  57. data/lib/rear/setup/generic.rb +59 -0
  58. data/lib/rear/setup/menu.rb +39 -0
  59. data/lib/rear/templates/editor/ace.slim +7 -0
  60. data/lib/rear/templates/editor/assocs.slim +10 -0
  61. data/lib/rear/templates/editor/boolean.slim +5 -0
  62. data/lib/rear/templates/editor/bulk_edit.slim +75 -0
  63. data/lib/rear/templates/editor/checkbox.slim +5 -0
  64. data/lib/rear/templates/editor/ckeditor.slim +7 -0
  65. data/lib/rear/templates/editor/date.slim +9 -0
  66. data/lib/rear/templates/editor/datetime.slim +9 -0
  67. data/lib/rear/templates/editor/layout.slim +103 -0
  68. data/lib/rear/templates/editor/password.slim +1 -0
  69. data/lib/rear/templates/editor/radio.slim +5 -0
  70. data/lib/rear/templates/editor/select.slim +3 -0
  71. data/lib/rear/templates/editor/string.slim +1 -0
  72. data/lib/rear/templates/editor/text.slim +1 -0
  73. data/lib/rear/templates/editor/time.slim +9 -0
  74. data/lib/rear/templates/error.slim +36 -0
  75. data/lib/rear/templates/filters/boolean.slim +10 -0
  76. data/lib/rear/templates/filters/checkbox.slim +15 -0
  77. data/lib/rear/templates/filters/date.slim +10 -0
  78. data/lib/rear/templates/filters/datetime.slim +10 -0
  79. data/lib/rear/templates/filters/layout.slim +26 -0
  80. data/lib/rear/templates/filters/radio.slim +14 -0
  81. data/lib/rear/templates/filters/select.slim +9 -0
  82. data/lib/rear/templates/filters/string.slim +3 -0
  83. data/lib/rear/templates/filters/text.slim +3 -0
  84. data/lib/rear/templates/filters/time.slim +10 -0
  85. data/lib/rear/templates/home.slim +0 -0
  86. data/lib/rear/templates/layout.slim +78 -0
  87. data/lib/rear/templates/pager.slim +22 -0
  88. data/lib/rear/templates/pane/ace.slim +2 -0
  89. data/lib/rear/templates/pane/assocs.slim +62 -0
  90. data/lib/rear/templates/pane/boolean.slim +2 -0
  91. data/lib/rear/templates/pane/checkbox.slim +5 -0
  92. data/lib/rear/templates/pane/ckeditor.slim +2 -0
  93. data/lib/rear/templates/pane/date.slim +2 -0
  94. data/lib/rear/templates/pane/datetime.slim +2 -0
  95. data/lib/rear/templates/pane/layout.slim +111 -0
  96. data/lib/rear/templates/pane/password.slim +2 -0
  97. data/lib/rear/templates/pane/quickview.slim +21 -0
  98. data/lib/rear/templates/pane/radio.slim +5 -0
  99. data/lib/rear/templates/pane/select.slim +5 -0
  100. data/lib/rear/templates/pane/string.slim +2 -0
  101. data/lib/rear/templates/pane/text.slim +2 -0
  102. data/lib/rear/templates/pane/time.slim +2 -0
  103. data/lib/rear/utils.rb +288 -0
  104. data/rear.gemspec +27 -0
  105. data/test/helpers.rb +33 -0
  106. data/test/models/ar.rb +52 -0
  107. data/test/models/dm.rb +53 -0
  108. data/test/models/sq.rb +58 -0
  109. data/test/setup.rb +4 -0
  110. data/test/templates/adhoc/book/editor/name.slim +1 -0
  111. data/test/templates/adhoc/book/editor/string.slim +1 -0
  112. data/test/templates/adhoc/book/pane/name.slim +1 -0
  113. data/test/templates/adhoc/book/pane/string.slim +1 -0
  114. data/test/templates/shared/shared-templates/editor/string.slim +1 -0
  115. data/test/templates/shared/shared-templates/pane/string.slim +1 -0
  116. data/test/test__assocs.rb +249 -0
  117. data/test/test__columns.rb +269 -0
  118. data/test/test__crud.rb +81 -0
  119. data/test/test__custom_templates.rb +147 -0
  120. data/test/test__filters.rb +228 -0
  121. metadata +220 -0
@@ -0,0 +1,59 @@
1
+ module RearSetup
2
+
3
+ # tell controller to create a CRUD interface for given model
4
+ # opts and proc will be passed to Espresso's `crudify` helper.
5
+ #
6
+ # @param [Class] model
7
+ # @param [Hash] opts to be passed to `crudify` method
8
+ # @param [Proc] proc to be passed to `crudify` method
9
+ #
10
+ def model model = nil, opts = {}, &proc
11
+ return @__rear__model if @__rear__model || model.nil?
12
+ model = RearUtils.extract_constant(model)
13
+ RearUtils.is_orm?(model) ||
14
+ raise(ArgumentError, '"%s" is not a ActiveRecord/DataMapper/Sequel model' % model.inspect)
15
+ @__rear__model = model
16
+ @__rear__default_label = model.name.gsub(/\W/, '_').freeze
17
+ RearControllerSetup.crudify self, model, opts, &proc
18
+ end
19
+
20
+ def pkey key = nil
21
+ return unless model
22
+ @__rear__pkey = key if key
23
+ @__rear__pkey ||
24
+ raise(ArgumentError, "Was unable to automatically detect primary key for %s model.
25
+ Please set it manually via `pkey key_name`" % model)
26
+ end
27
+
28
+ def order_by *columns
29
+ @__rear__order = columns if columns.any?
30
+ @__rear__order
31
+ end
32
+
33
+ def items_per_page n = nil
34
+ @__rear__ipp = n.to_i if n
35
+ @__rear__ipp || 10
36
+ end
37
+ alias ipp items_per_page
38
+
39
+ # executed when new item created and when existing item updated
40
+ def on_save &proc
41
+ # const_get(:RearController).
42
+ before :save, &proc
43
+ end
44
+
45
+ # executed when existing item updated
46
+ def on_update &proc
47
+ before :update, &proc
48
+ end
49
+
50
+ def on_delete &proc
51
+ before :destroy, &proc
52
+ end
53
+ alias on_destroy on_delete
54
+
55
+ def readonly!
56
+ @__rear__readonly = true
57
+ end
58
+
59
+ end
@@ -0,0 +1,39 @@
1
+ module RearSetup
2
+
3
+ # by default all controllers are shown in main menu
4
+ # using the demodulized controller name.
5
+ #
6
+ # to use a custom label, set it via `menu_label` or its alias - `label`
7
+ # to hide a controller from menu set label to false.
8
+ def menu_label label = nil
9
+ @__rear__menu_label = label.freeze if label || label == false
10
+ @__rear__menu_label.nil? ? default_label : @__rear__menu_label
11
+ end
12
+ alias label menu_label
13
+
14
+ # by default controllers will be shown in the menu in the order they was defined.
15
+ # to have a controller shown before other ones set its menu_position to a higher number.
16
+ def menu_position position = nil
17
+ @__rear__menu_position = position.to_i if position
18
+ @__rear__menu_position || 0
19
+ end
20
+ alias position menu_position
21
+
22
+ # put current controller under some group.
23
+ #
24
+ # @example put Articles and Pages under Cms dropdown
25
+ # class Articles < E
26
+ # include Rear
27
+ # under :Cms
28
+ # end
29
+ # class Pages < E
30
+ # include Rear
31
+ # under :Cms
32
+ # end
33
+ #
34
+ def menu_group group = nil
35
+ @__rear__menu_group = group.to_s if group
36
+ @__rear__menu_group
37
+ end
38
+ alias under menu_group
39
+ end
@@ -0,0 +1,7 @@
1
+ - editor_id = "EditorFor" + column.string_name.capitalize
2
+ - a = {id: editor_id, name: column.name?}
3
+ textarea.input-block-level.text_editor *a *attrs(column, :editor) = value
4
+
5
+ - if defined?(EL::Ace)
6
+ - o = {readonly: column.readonly? || readonly?, snippets: column.snippets}
7
+ == ace editor_id, o
@@ -0,0 +1,10 @@
1
+ - assocs(assoc_type).each_pair do |assoc_name, assoc|
2
+ - remote_ctrl = associated_model_controller(assoc[:remote_model])
3
+ - remote_url = remote_ctrl.route(:reverse_assoc, self.class, assoc_type, assoc_name, item_id)
4
+ .tab-pane id="editor-tabs-#{assoc_name}"
5
+ javascript:
6
+ $(function(){
7
+ new Rear.Assoc('#{remote_url}', '##{assoc[:dom_id]}').load();
8
+ });
9
+ div id=(assoc[:dom_id])
10
+ div id=(assoc[:dom_id] + '_detached')
@@ -0,0 +1,5 @@
1
+ - COLUMNS__BOOLEAN_MAP.each_pair do |k,v|
2
+ - a = value == k ? {checked: true} : {}
3
+ label.radio.inline
4
+ input name=column.name? type="radio" value=k.inspect *a *attrs(column, :editor) = v
5
+ | &nbsp;
@@ -0,0 +1,75 @@
1
+
2
+ - crudifier_toggler = lambda do |column, dom_id, opts={}|
3
+ label.checkbox.inline class=('pull-right' unless opts[:left])
4
+ - name = 'rear-bulk_editor-crudifier_toggler[]'
5
+ input name=name type="checkbox" id=(dom_id + '-update_me') value=column
6
+ = 'Update %s' % (opts[:label] || column)
7
+
8
+ form.form-horizontal#bulk_editor-main_form
9
+ input.hidden type="hidden" name="rear-bulk_editor-items" value=@items
10
+ ul.nav.nav-tabs
11
+ li.active
12
+ a href="#editor-tabs-generic" data-toggle="tab"
13
+ i.icon-edit
14
+ | &nbsp;
15
+ = __rear__.label || __rear__.default_label
16
+
17
+ - assocs(:belongs_to).each_key do |a|
18
+ li
19
+ a href="#editor-tabs-#{a}" data-toggle="tab"
20
+ i.icon-tags
21
+ | &nbsp;
22
+ = a
23
+
24
+ .tab-content
25
+ .tab-pane.active#editor-tabs-generic
26
+
27
+ - rowed_columns = {}
28
+ - editor_columns.each do |column|
29
+ javascript:
30
+ $('.#{column.css_class}').change(function(){
31
+ $('##{column.dom_id}-update_me').prop('checked', true);
32
+ });
33
+ - next if rowed_columns[column.__id__]
34
+ - if column.row?
35
+ - if column.row.is_a?(String)
36
+ .row-fluid
37
+ .span style="text-align: center;"
38
+ h4.muted
39
+ = column.row
40
+
41
+ .row-fluid
42
+ - row_columns = editor_columns.select {|c| c.row == column.row}
43
+ - row_columns.each do |rc|
44
+ - self.column = rc
45
+ - rowed_columns[rc.__id__] = true
46
+ div class=('span%s' % (12 / row_columns.size).ceil)
47
+ .editor-column_container title=rc.label
48
+ .editor-column_value
49
+ == render_editor_column rc
50
+ - crudifier_toggler.call rc.name, rc.dom_id
51
+
52
+ - else
53
+ - self.column = column
54
+ .editor-column_container title=column.label
55
+ .editor-column_value
56
+ == render_editor_column column
57
+ - crudifier_toggler.call column.name, column.dom_id
58
+
59
+ - assocs(:belongs_to).each_pair do |assoc_name, assoc|
60
+ - remote_ctrl = associated_model_controller(assoc[:remote_model])
61
+ - remote_url = remote_ctrl.route(:reverse_assoc, self.class, :belongs_to, assoc_name, item_id)
62
+ .tab-pane id="editor-tabs-#{assoc_name}"
63
+ javascript:
64
+ $(function(){
65
+ new Rear.Assoc('#{remote_url}', '##{assoc[:dom_id]}').load_detached(function() {
66
+ $('.#{assoc[:dom_id]}_detached-assoc_toggler').change(function() {
67
+ $('##{assoc[:dom_id]}-update_me').prop('checked', true);
68
+ });
69
+ });
70
+ });
71
+
72
+ - column = assoc[:belongs_to_keys][:source]
73
+ - crudifier_toggler.call column, assoc[:dom_id], left: true, label: assoc[:name]
74
+
75
+ div id=(assoc[:dom_id] + '_detached')
@@ -0,0 +1,5 @@
1
+ - options.each_pair do |k,v|
2
+ - a = active_options.include?(k) ? {checked: true} : {}
3
+ label.checkbox.inline.editor-checkbox_container
4
+ input name=column.name? type="checkbox" value=k *a *attrs(column, :editor) = v
5
+ | &nbsp;
@@ -0,0 +1,7 @@
1
+ - editor_id = "EditorFor" + column.string_name.capitalize
2
+ - a = {id: editor_id, name: column.name?}
3
+ textarea.input-block-level.text_editor *a *attrs(column, :editor) = value
4
+
5
+ - if defined?(EL::CKE)
6
+ - o = {readonly: column.readonly? || readonly?, snippets: column.snippets}
7
+ == ckeditor editor_id, o.merge(column.ckeditor_opts)
@@ -0,0 +1,9 @@
1
+ - value = value.strftime('%Y-%m-%d') if value.kind_of?(Date)
2
+ - a = {name: column.name?, type: "text", value: value, "data-format" => "yyyy-MM-dd"}
3
+ .input-append.date id=column.name?
4
+ input.input-small *a *attrs(column, :editor)
5
+ span.add-on
6
+ i data-time-icon="icon-time" data-date-icon="icon-calendar" class=column.css_class
7
+ - if column.name?
8
+ javascript:
9
+ $(function() {$('##{column.name}').datetimepicker({pickTime: false})});
@@ -0,0 +1,9 @@
1
+ - value = value.strftime('%Y-%m-%d %H:%M:%S') if value.kind_of?(DateTime)
2
+ - a = {name: column.name?, type: "text", value: value, "data-format" => "yyyy-MM-dd hh:mm:ss"}
3
+ .input-append.date id=column.name?
4
+ input.input-medium *a *attrs(column, :editor)
5
+ span.add-on
6
+ i data-time-icon="icon-time" data-date-icon="icon-calendar" class=column.css_class
7
+ - if column.name?
8
+ javascript:
9
+ $(function() {$('##{column.name}').datetimepicker()});
@@ -0,0 +1,103 @@
1
+ javascript:
2
+ var crudifier = new Rear.CRUD(#{item_id}, '#{self[:crud]}', '#{self[:edit]}', #{readonly? ? true : false});
3
+
4
+ - if readonly?
5
+ javascript:
6
+ $(function(){
7
+ Rear.sticky_warn('ReadOnly Mode! Any updates will be discarded!')
8
+ });
9
+
10
+ .row-fluid
11
+ .span8
12
+
13
+ form.form-horizontal#editor-main_form
14
+ ul.nav.nav-tabs
15
+ li.active
16
+ a href="#editor-tabs-generic" data-toggle="tab"
17
+ i.icon-edit
18
+ | &nbsp;
19
+ = __rear__.label || __rear__.default_label
20
+
21
+ - assocs(:belongs_to).each_key do |a|
22
+ li
23
+ a href="#editor-tabs-#{a}" data-toggle="tab"
24
+ i.icon-tags
25
+ | &nbsp;
26
+ = a
27
+
28
+ - if item_id > 0
29
+ - assocs(:has_one, :has_many).each_key do |a|
30
+ li
31
+ a href="#editor-tabs-#{a}" data-toggle="tab"
32
+ i.icon-tags
33
+ | &nbsp;
34
+ = a
35
+ li
36
+ a href=route(:edit, 0)
37
+ span.badge.badge-warning
38
+ i.icon-plus
39
+ | &nbsp;New
40
+
41
+ - if item_id > 0
42
+ li
43
+ a onclick="if(confirm('This action can not be undone! Continue?')) { crudifier.delete('#{{route(pager_params)}}') } else { return false }" href="#"
44
+ span.badge.badge-important
45
+ i.icon-remove
46
+ | &nbsp;Delete
47
+
48
+ li
49
+ a.saveButton onclick="crudifier.save();" href="javascript:void(null);"
50
+ span.badge.badge-success#editor-save_badge
51
+ i.icon-check
52
+ | &nbsp;Save
53
+
54
+ .tab-content style="min-height: 50em;"
55
+ .tab-pane.active#editor-tabs-generic
56
+
57
+ - rowed_columns = []
58
+ - editor_columns.each do |column|
59
+ - next if rowed_columns.include?(column.__id__)
60
+ - if column.row?
61
+ - if column.row.is_a?(String)
62
+ .row-fluid
63
+ .span style="text-align: center;"
64
+ h4.muted
65
+ = column.row
66
+
67
+ .row-fluid
68
+ - columns = editor_columns.select {|c| c.row == column.row}
69
+ - columns.each do |rc|
70
+ - self.column = rc
71
+ - rowed_columns << rc.__id__
72
+ div class=('span%s' % (12 / columns.size).ceil)
73
+ .editor-column_container title=rc.label
74
+ .editor-column_value
75
+ == render_editor_column rc
76
+
77
+ - else
78
+ - self.column = column
79
+ .editor-column_container title=column.label
80
+ .editor-column_value
81
+ == render_editor_column column
82
+
83
+ == reander_p 'editor/assocs', assoc_type: :belongs_to
84
+ - if item_id > 0
85
+ - [:has_one, :has_many].each do |type|
86
+ == reander_p 'editor/assocs', assoc_type: type
87
+
88
+ hr
89
+ .row-fluid
90
+ .pull-right
91
+ a.btn.saveButton onclick="crudifier.save();" style="width: 10em;"
92
+ i.icon-check
93
+ | &nbsp;Save
94
+
95
+ .span4
96
+ #editor-navigation.hide_under_940px
97
+ javascript:
98
+ $(function(){
99
+ $.ajax({
100
+ type: 'GET',
101
+ url: '#{{route :quickview, pager_params.merge(selected: item_id.to_s)}}'
102
+ }).done(function(response){ $('#editor-navigation').html(response) });
103
+ });
@@ -0,0 +1 @@
1
+ input.input-block-level type="password" name=column.name? value=value *attrs(column, :editor)
@@ -0,0 +1,5 @@
1
+ - options.each_pair do |k,v|
2
+ - a = active_options.include?(k) ? {checked: true} : {}
3
+ label.radio.inline.editor-radio_container
4
+ input name=column.name? type="radio" value=k *a *attrs(column, :editor) = v
5
+ | &nbsp;
@@ -0,0 +1,3 @@
1
+ select.selectable name=column.name? *attrs(column, :editor)
2
+ - options.each_pair do |k,v|
3
+ option value=k selected=active_options.include?(k) = v
@@ -0,0 +1 @@
1
+ input.input-block-level type="text" name=column.name? value=value *attrs(column, :editor)
@@ -0,0 +1 @@
1
+ textarea.text_editor.input-block-level name=column.name? *attrs(column, :editor) = value
@@ -0,0 +1,9 @@
1
+ - value = value.strftime('%H:%M:%S') if value.kind_of?(Time)
2
+ - a = {name: column.name?, type: "text", value: value, "data-format" => "hh:mm:ss"}
3
+ .input-append.date id=column.name?
4
+ input.input-small *a *attrs(column, :editor)
5
+ span.add-on
6
+ i data-time-icon="icon-time" data-date-icon="icon-calendar" class=column.css_class
7
+ - if column.name?
8
+ javascript:
9
+ $(function() {$('##{column.name}').datetimepicker({pickDate: false})});
@@ -0,0 +1,36 @@
1
+ .alert.alert-error
2
+ h4 Something went wrong...
3
+ .text-info
4
+ | Please consider to report this at &nbsp;
5
+ a href="https://github.com/espresso/rear/issues" github.com/espresso/rear
6
+ h5 = error.first
7
+
8
+ h4.text-info Backtrace:
9
+ - error.each do |l|
10
+ div = l
11
+
12
+ h4.text-info Path:
13
+ = rq.path
14
+
15
+ h4.text-info Request Method:
16
+ = rq.request_method
17
+
18
+ h4.text-info Params:
19
+ table
20
+ - params.each_pair do |key,val|
21
+ tr
22
+ td
23
+ .pull-right.text-info
24
+ = '%s: ' % key
25
+ td
26
+ = val.is_a?(String) ? val : val.inspect
27
+
28
+ h4.text-info Env:
29
+ table
30
+ - env.each_pair do |key,val|
31
+ tr
32
+ td
33
+ .pull-right.text-info
34
+ = '%s: ' % key
35
+ td
36
+ = val.is_a?(String) ? val : val.inspect
@@ -0,0 +1,10 @@
1
+ .input-prepend.input-append
2
+ span.add-on
3
+ = setup[:label]
4
+
5
+ span.add-on
6
+ - COLUMNS__BOOLEAN_MAP.each_pair do |k,v|
7
+ - attrs = value == k.inspect ? {checked: true} : {}
8
+ label.radio
9
+ input name=name type="radio" value=k.inspect *attrs = v
10
+ | &nbsp;
@@ -0,0 +1,15 @@
1
+ - values = value.is_a?(Array) ? value : []
2
+ - if setup[:decorative?]
3
+ - attrs[:onclick] = "new Rear.Filters('%s', '%s').update();" % \
4
+ [dom_id, route(:html_filters, true, dom_id: dom_id)]
5
+
6
+ .input-prepend.input-append
7
+ span.add-on
8
+ = setup[:label]
9
+
10
+ span.add-on
11
+ - filter_setup_to_options(setup).each_pair do |v,l|
12
+ - attrs[:checked] = values.include?(v.to_s)
13
+ label.checkbox
14
+ input name=('%s[]' % name) type="checkbox" value=v *attrs = l
15
+ | &nbsp;