hot-glue 0.6.16 → 0.6.17
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +171 -0
- data/lib/generators/hot_glue/field_factory.rb +20 -20
- data/lib/generators/hot_glue/fields/association_field.rb +14 -14
- data/lib/generators/hot_glue/fields/attachment_field.rb +1 -20
- data/lib/generators/hot_glue/fields/boolean_field.rb +13 -13
- data/lib/generators/hot_glue/fields/date_field.rb +4 -2
- data/lib/generators/hot_glue/fields/enum_field.rb +6 -1
- data/lib/generators/hot_glue/fields/field.rb +48 -47
- data/lib/generators/hot_glue/fields/related_set_field.rb +4 -9
- data/lib/generators/hot_glue/markup_templates/erb.rb +22 -7
- data/lib/generators/hot_glue/scaffold_generator.rb +31 -16
- data/lib/generators/hot_glue/templates/erb/_edit.erb +3 -1
- data/lib/generators/hot_glue/templates/erb/_form.erb +2 -1
- data/lib/generators/hot_glue/templates/erb/_new_form.erb +3 -1
- data/lib/hot-glue.rb +12 -0
- data/lib/hotglue/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6abeaf2b7456e5f554928c58bb5deae7fb01a0c664ad0d717572f20c6185537
|
4
|
+
data.tar.gz: da58696031065cf910116374477ce9f8f3b388bef0a2ad756289953b70a31d8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 626a8d361bef7d74109406ba5beae46b0daf4db297c2f95fcd92431bc1b6f9ea97d224acb8ef898bb0281b8cb3bc369d5507f6956751da137c4e5ecb4e3a93d5
|
7
|
+
data.tar.gz: 3a646740022a5c699051ad99c400178f1abdafc911a9c1e40c400b52a16458f3181e98ed73e0e6db8fc64fdaa8829b93f8432efcf4026873dbb2236708c2308e
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hot-glue (0.6.
|
4
|
+
hot-glue (0.6.17)
|
5
5
|
ffaker (~> 2.16)
|
6
6
|
kaminari (~> 1.2)
|
7
7
|
rails (> 5.1)
|
@@ -140,7 +140,7 @@ GEM
|
|
140
140
|
mini_mime (1.1.2)
|
141
141
|
mini_portile2 (2.8.4)
|
142
142
|
minitest (5.16.3)
|
143
|
-
net-imap (0.5.
|
143
|
+
net-imap (0.5.8)
|
144
144
|
date
|
145
145
|
net-protocol
|
146
146
|
net-pop (0.1.2)
|
data/README.md
CHANGED
@@ -868,6 +868,92 @@ Remember, if there's a corresponding `*_able?` method on the policy, it will be
|
|
868
868
|
As shown in the method `name_able?` of the example ThingPolicy above, if this field on your policy returns true, the field will be editable. If it returns false, the field will be viewable (read-only).
|
869
869
|
|
870
870
|
|
871
|
+
### `--hidden`
|
872
|
+
|
873
|
+
Separate list of fields.
|
874
|
+
|
875
|
+
These fields will be hidden from the form but will exist as hidden_field, and so the update will still work.
|
876
|
+
|
877
|
+
|
878
|
+
EXAMPLE:
|
879
|
+
|
880
|
+
```
|
881
|
+
bin/rails generate hot_glue:scaffold Wrapper --namespace='account_dashboard' --no-nav-menu --big-edit --smart-layout --stimmify --hidden=raw_source
|
882
|
+
```
|
883
|
+
|
884
|
+
In the `wrappers` folder, I am using a special sticky partial `_edit_within_form.html.erb`, which contains code preserved from build-to-build and included in the form:
|
885
|
+
|
886
|
+
|
887
|
+
```
|
888
|
+
<div class="row" style="position: relative; width: 100%; overflow: auto;">
|
889
|
+
<div class="col-md-12">
|
890
|
+
<div id="wrapper__raw_source"
|
891
|
+
style="position: static">
|
892
|
+
|
893
|
+
<div id="wrapper__raw_source-toolbar">
|
894
|
+
|
895
|
+
</div>
|
896
|
+
|
897
|
+
|
898
|
+
<div cols="60"
|
899
|
+
data-wrapper-form-target="editor"
|
900
|
+
id="wrapper__raw_source-editor" >
|
901
|
+
</div>
|
902
|
+
</div>
|
903
|
+
|
904
|
+
|
905
|
+
</div>
|
906
|
+
</div>
|
907
|
+
<div class="col-md-2">
|
908
|
+
</div>
|
909
|
+
```
|
910
|
+
|
911
|
+
|
912
|
+
Then, create a `app/javascript/controllers/wrapper_form_controller.js` file with the following code:
|
913
|
+
|
914
|
+
```javascript
|
915
|
+
|
916
|
+
|
917
|
+
import { Controller } from "@hotwired/stimulus"
|
918
|
+
|
919
|
+
import {basicSetup} from "codemirror"
|
920
|
+
import {EditorView} from "@codemirror/view"
|
921
|
+
|
922
|
+
// Connects to data-controller="wrapper-form"
|
923
|
+
export default class extends Controller {
|
924
|
+
static targets = ['rawSource', 'name', 'nameWrapper', 'editor'];
|
925
|
+
|
926
|
+
connect() {
|
927
|
+
console.log("WrapperFormController connected")
|
928
|
+
this.account_id = this.element.dataset['accountId']
|
929
|
+
this.crusade_id = this.element.dataset['crusadeId']
|
930
|
+
this.wrapper_id = this.element.dataset['wrapperId']
|
931
|
+
|
932
|
+
const view = new EditorView({
|
933
|
+
doc: this.rawSourceTarget.value,
|
934
|
+
parent: this.editorTarget,
|
935
|
+
extensions: [basicSetup]
|
936
|
+
})
|
937
|
+
|
938
|
+
this.view = view;
|
939
|
+
this.element.addEventListener('submit', this.formSubmit.bind(this))
|
940
|
+
// this.previewButtonTarget.addEventListener('click', this.previewClick.bind(this))
|
941
|
+
}
|
942
|
+
|
943
|
+
formSubmit(event) {
|
944
|
+
this.rawSourceTarget.value = this.view.state.doc.toString();
|
945
|
+
}
|
946
|
+
}
|
947
|
+
```
|
948
|
+
|
949
|
+
Notice we are also using `--stimmify` to decorate the form with a Stimulus controller.
|
950
|
+
|
951
|
+
The code above uses Code Mirror to act as a code editor, which requires pulling the value off the hidden form element (putting it into the code mirror interface) and pushing it back into the hidden form element when the Submit button is clicked.
|
952
|
+
|
953
|
+
|
954
|
+
|
955
|
+
|
956
|
+
|
871
957
|
### `--ujs_syntax=true` (Default is set automatically based on whether you have turbo-rails installed)
|
872
958
|
|
873
959
|
If you are pre-Turbo (UJS), your delete buttons will come out like this:
|
@@ -1381,6 +1467,54 @@ Then run:
|
|
1381
1467
|
This will 1) copy the dropzone_controller.js file into your app and 2) add the dropzone css into your app's application.css or application.bootstrap.css file.
|
1382
1468
|
|
1383
1469
|
|
1470
|
+
### Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`
|
1471
|
+
|
1472
|
+
Automatically build the new and edit form with `data-controller='xyz'` to attach cooresponding stimulus controllers.
|
1473
|
+
|
1474
|
+
If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
|
1475
|
+
|
1476
|
+
`@singular.gsub("_", "-") + "-form"`
|
1477
|
+
|
1478
|
+
(For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like
|
1479
|
+
|
1480
|
+
```
|
1481
|
+
<%= form_with model: thing,
|
1482
|
+
url: things_path(account,crusade,email_template),
|
1483
|
+
html: {
|
1484
|
+
'data-controller': "thing-form"
|
1485
|
+
}
|
1486
|
+
%>
|
1487
|
+
...
|
1488
|
+
|
1489
|
+
```
|
1490
|
+
|
1491
|
+
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
1492
|
+
(assuming `thing` is the scaffold being built and abc is the field name)
|
1493
|
+
|
1494
|
+
|
1495
|
+
Here, we are building a `thing` scaffold. The field `name` is decorated twice: once for the wrapper span and again for the specific form element itself.
|
1496
|
+
```
|
1497
|
+
<span class="" data-thing-form-target="nameWrapper">
|
1498
|
+
<input value="asdfadf" autocomplete="off" size="40" class="form-control" type="" data-thing-form-target="name" name="thing[name]" id="thing_name">
|
1499
|
+
|
1500
|
+
|
1501
|
+
<label class="text-muted small form-text" for="">Name</label>
|
1502
|
+
</span>
|
1503
|
+
```
|
1504
|
+
|
1505
|
+
Your stimulus controller will need two targets for each field:
|
1506
|
+
|
1507
|
+
```
|
1508
|
+
static targets = ['name', 'nameWrapper'];
|
1509
|
+
```
|
1510
|
+
You can interact with the wrapper for things like clicks or hovers, or to hide/show the entire box surrounding the form element.
|
1511
|
+
|
1512
|
+
Use the form field element itself to affect things like enabled or the value of the field.
|
1513
|
+
|
1514
|
+
|
1515
|
+
For a crash course on Stimulus, see
|
1516
|
+
https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
|
1517
|
+
|
1384
1518
|
|
1385
1519
|
|
1386
1520
|
### `--factory-creation={ ... }`
|
@@ -1768,6 +1902,43 @@ These automatic pickups for partials are detected at build time. This means that
|
|
1768
1902
|
|
1769
1903
|
# VERSION HISTORY
|
1770
1904
|
|
1905
|
+
#### 2025-05-0097 - v0.6.17
|
1906
|
+
|
1907
|
+
|
1908
|
+
• Adds Stimulus JS & `--stimmify` or `--stimmify=xyz`
|
1909
|
+
|
1910
|
+
Automatically build the new and edit form with `data-controller='xyz'` to attach stimulus
|
1911
|
+
|
1912
|
+
If you use the shorthand (specify no `=`) your stimulus controller's name will be inferred from the Singular form of the scaffolding beild built, with dashes for underscores, and ending with `-form`
|
1913
|
+
|
1914
|
+
(For example, `rails g hot_glue:scaffold Thing --stimmy` generates a form that looks like
|
1915
|
+
|
1916
|
+
```
|
1917
|
+
<%= form_with model: thing,
|
1918
|
+
url: things_path(account,crusade,email_template),
|
1919
|
+
html: {
|
1920
|
+
'data-controller': "thing-form"
|
1921
|
+
}
|
1922
|
+
%>
|
1923
|
+
...
|
1924
|
+
|
1925
|
+
```
|
1926
|
+
|
1927
|
+
Note that your fields also appended with `data-thing-target=abc` and also `data-thing-target=abcWrapper`
|
1928
|
+
|
1929
|
+
See section "Attach Stimulus JS Controllers to Your Forms with `--stimmify` or `--stimmify=xyz`"
|
1930
|
+
|
1931
|
+
For a crash course on Stimulus, see
|
1932
|
+
https://jasonfleetwoodboldt.com/courses/rails-7-crash-course/rails-7-stimulus-js-basics-with-importmap-rails/
|
1933
|
+
|
1934
|
+
|
1935
|
+
• Adds `--hidden` option
|
1936
|
+
Pass a list of fields, like include or show-only. This will make the field hidden on the form *but still updated via its submission*
|
1937
|
+
|
1938
|
+
|
1939
|
+
|
1940
|
+
|
1941
|
+
|
1771
1942
|
#### 2025-03-31 v0.6.16
|
1772
1943
|
|
1773
1944
|
• Bootstrap Tab Panes For Downnested Portals
|
@@ -53,25 +53,25 @@ class FieldFactory
|
|
53
53
|
raise "Field type could be identified #{name} "
|
54
54
|
end
|
55
55
|
|
56
|
-
@field = field_class.new(name: name
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
56
|
+
@field = field_class.new(scaffold: generator, name: name)
|
57
|
+
# layout_strategy: generator.layout_strategy,
|
58
|
+
# form_placeholder_labels: generator.form_placeholder_labels,
|
59
|
+
# form_labels_position: generator.form_labels_position,
|
60
|
+
# ownership_field: generator.ownership_field,
|
61
|
+
# hawk_keys: generator.hawk_keys,
|
62
|
+
# auth: generator.auth,
|
63
|
+
# class_name: generator.singular_class,
|
64
|
+
# alt_lookup: generator.alt_lookups[name] || nil,
|
65
|
+
# singular: generator.singular,
|
66
|
+
# self_auth: generator.self_auth,
|
67
|
+
# update_show_only: generator.update_show_only,
|
68
|
+
# attachment_data: generator.attachments[name.to_sym],
|
69
|
+
# sample_file_path: generator.sample_file_path,
|
70
|
+
# modify_as: generator.modify_as[name.to_sym] || nil,
|
71
|
+
# plural: generator.plural,
|
72
|
+
# display_as: generator.display_as[name.to_sym] || nil,
|
73
|
+
# default_boolean_display: generator.default_boolean_display,
|
74
|
+
# namespace: generator.namespace_value,
|
75
|
+
# pundit: generator.pundit )
|
76
76
|
end
|
77
77
|
end
|
@@ -5,17 +5,8 @@ class AssociationField < Field
|
|
5
5
|
|
6
6
|
attr_accessor :assoc_name, :assoc_class, :assoc, :alt_lookup
|
7
7
|
|
8
|
-
def initialize(
|
9
|
-
class_name: ,
|
10
|
-
default_boolean_display:, display_as: ,
|
11
|
-
name: , singular: ,
|
12
|
-
update_show_only: ,
|
13
|
-
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
|
14
|
-
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
|
15
|
-
form_labels_position:, modify_as: , self_auth: , namespace:, pundit: , plural: )
|
8
|
+
def initialize(scaffold: , name: )
|
16
9
|
super
|
17
|
-
|
18
|
-
|
19
10
|
@assoc_model = eval("#{class_name}.reflect_on_association(:#{assoc})")
|
20
11
|
|
21
12
|
if assoc_model.nil?
|
@@ -97,14 +88,14 @@ class AssociationField < Field
|
|
97
88
|
def form_field_output
|
98
89
|
assoc_name = name.to_s.gsub("_id","")
|
99
90
|
assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
|
100
|
-
|
101
|
-
if alt_lookup
|
91
|
+
if alt_lookup.keys.include?(name.to_sym)
|
102
92
|
alt = alt_lookup[:lookup_as]
|
103
93
|
assoc_name = name.to_s.gsub("_id","")
|
104
94
|
assoc = eval("#{class_name}.reflect_on_association(:#{assoc_name})")
|
105
95
|
|
106
96
|
alt = alt_lookup[:lookup_as]
|
107
|
-
|
97
|
+
parts = name.split('_')
|
98
|
+
"<%= f.text_field :__lookup_#{alt}, value: @#{singular}.#{assoc_name}.try(:#{alt}), placeholder: \"search by #{alt}\" " + (stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + "%>"
|
108
99
|
|
109
100
|
# if modify_as
|
110
101
|
# modified_display_output
|
@@ -156,8 +147,17 @@ class AssociationField < Field
|
|
156
147
|
end
|
157
148
|
|
158
149
|
|
150
|
+
if @stimmify
|
151
|
+
col_target = HotGlue.to_camel_case(name.to_s.gsub("_", " "))
|
152
|
+
data_attr = ", data: {'#{@stimmify}-target': '#{col_target}'} "
|
153
|
+
els
|
154
|
+
data_attr = ""
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
|
159
159
|
(is_owner ? "<% unless @#{assoc_name} %>\n" : "") +
|
160
|
-
" <%= f.collection_select(:#{name}, #{hawked_association}, :id, :#{display_column}, {prompt: true, selected: #{singular}.#{name} }, class: 'form-control') %>\n" +
|
160
|
+
" <%= f.collection_select(:#{name}, #{hawked_association}, :id, :#{display_column}, { prompt: true, selected: #{singular}.#{name} }, class: 'form-control'#{data_attr}) %>\n" +
|
161
161
|
(is_owner ? "<% else %>\n <%= @#{assoc_name}.#{display_column} %>" : "") +
|
162
162
|
(is_owner ? "\n<% end %>" : "")
|
163
163
|
end
|
@@ -1,25 +1,6 @@
|
|
1
1
|
class AttachmentField < Field
|
2
2
|
attr_accessor :attachment_data
|
3
|
-
def initialize(
|
4
|
-
attachment_data:,
|
5
|
-
plural:,
|
6
|
-
auth:,
|
7
|
-
class_name:,
|
8
|
-
display_as:, singular:,
|
9
|
-
default_boolean_display: ,
|
10
|
-
form_placeholder_labels: ,
|
11
|
-
form_labels_position:,
|
12
|
-
hawk_keys:,
|
13
|
-
layout_strategy: ,
|
14
|
-
name:,
|
15
|
-
namespace:,
|
16
|
-
modify_as:,
|
17
|
-
ownership_field:,
|
18
|
-
pundit: ,
|
19
|
-
sample_file_path: nil,
|
20
|
-
self_auth:,
|
21
|
-
update_show_only:
|
22
|
-
)
|
3
|
+
def initialize(scaffold:, name:)
|
23
4
|
super
|
24
5
|
|
25
6
|
@attachment_data = attachment_data
|
@@ -23,9 +23,9 @@ class BooleanField < Field
|
|
23
23
|
|
24
24
|
def radio_button_display
|
25
25
|
" <%= f.radio_button(:#{name}, '0', checked: #{singular}.#{name} ? '' : 'checked', class: '#{@layout_strategy.form_checkbox_input_class}') %>\n" +
|
26
|
-
" <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[:binary][:falsy] || 'No'}', for: '#{singular}_#{name}_0') %>\n" +
|
26
|
+
" <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[name.to_sym][:binary][:falsy] || 'No'}', for: '#{singular}_#{name}_0') %>\n" +
|
27
27
|
" <br /> <%= f.radio_button(:#{name}, '1', checked: #{singular}.#{name} ? 'checked' : '' , class: '#{@layout_strategy.form_checkbox_input_class}') %>\n" +
|
28
|
-
" <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[:binary][:truthy] || 'Yes'}', for: '#{singular}_#{name}_1') %>\n"
|
28
|
+
" <%= f.label(:#{name}, value: '#{modify_binary? && modify_as[name.to_sym][:binary][:truthy] || 'Yes'}', for: '#{singular}_#{name}_1') %>\n"
|
29
29
|
end
|
30
30
|
|
31
31
|
def checkbox_display
|
@@ -38,16 +38,16 @@ class BooleanField < Field
|
|
38
38
|
|
39
39
|
def form_field_display
|
40
40
|
if display_boolean_as.nil?
|
41
|
-
|
42
41
|
end
|
42
|
+
|
43
43
|
"<span class='#{@layout_strategy.form_checkbox_wrapper_class} #{'form-switch' if display_boolean_as == 'switch'}'>\n" +
|
44
44
|
(if display_boolean_as == 'radio'
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
radio_button_display
|
46
|
+
elsif display_boolean_as == 'checkbox'
|
47
|
+
checkbox_display
|
48
|
+
elsif display_boolean_as == 'switch'
|
49
|
+
switch_display
|
50
|
+
end) + "</span> \n"
|
51
51
|
end
|
52
52
|
|
53
53
|
def form_field_output
|
@@ -59,9 +59,9 @@ class BooleanField < Field
|
|
59
59
|
"<% if #{singular}.#{name}.nil? %>
|
60
60
|
<span class=''>MISSING</span>
|
61
61
|
<% elsif #{singular}.#{name} %>
|
62
|
-
#{modify_as[:binary][:truthy]}
|
62
|
+
#{modify_as[name.to_sym][:binary][:truthy]}
|
63
63
|
<% else %>
|
64
|
-
#{modify_as[:binary][:falsy]}
|
64
|
+
#{modify_as[name.to_sym][:binary][:falsy]}
|
65
65
|
<% end %>"
|
66
66
|
else
|
67
67
|
"<% if #{singular}.#{name}.nil? %>
|
@@ -75,11 +75,11 @@ class BooleanField < Field
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def truthy_value
|
78
|
-
modify_as[:binary][:truthy] || 'Yes'
|
78
|
+
modify_as[name.to_sym][:binary][:truthy] || 'Yes'
|
79
79
|
end
|
80
80
|
|
81
81
|
def falsy_value
|
82
|
-
modify_as[:binary][:falsy] || 'No'
|
82
|
+
modify_as[name.to_sym][:binary][:falsy] || 'No'
|
83
83
|
end
|
84
84
|
|
85
85
|
def label_class
|
@@ -10,8 +10,10 @@ class DateField < Field
|
|
10
10
|
|
11
11
|
|
12
12
|
def form_field_output
|
13
|
-
|
14
|
-
|
13
|
+
parts = name.to_s.split('_')
|
14
|
+
camelcase_name = parts.map(&:capitalize).join
|
15
|
+
"<%= date_field_localized(f, :#{name}, #{singular}.#{name}, label: '#{ name.to_s.humanize }'" + (stimmify ? ", html: {'data-#{@stimmify}-target': '#{camelcase_name}'}" : "") + ") %>"
|
16
|
+
end
|
15
17
|
|
16
18
|
def line_field_output
|
17
19
|
"<% unless #{singular}.#{name}.nil? %>
|
@@ -31,6 +31,11 @@ class EnumField < Field
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def form_field_output
|
34
|
+
if @stimmify
|
35
|
+
col_target = HotGlue.to_camel_case(name.to_s.gsub("_", " "))
|
36
|
+
data_attr = ", data: {'#{@stimmify}-target': '#{col_target}'} "
|
37
|
+
end
|
38
|
+
|
34
39
|
enum_type = eval("#{class_name}.columns.select{|x| x.name == '#{name}'}[0].sql_type")
|
35
40
|
|
36
41
|
if eval("defined? #{class_name}.#{enum_type}_labels") == "method"
|
@@ -39,7 +44,7 @@ class EnumField < Field
|
|
39
44
|
enum_definer = "#{class_name}.defined_enums['#{name}']"
|
40
45
|
end
|
41
46
|
|
42
|
-
res = "<%= f.collection_select(:#{name},
|
47
|
+
res = "<%= f.collection_select(:#{name}, enum_to_collection_select(#{enum_definer}), :key, :value, {include_blank: true, selected: #{singular}.#{name} }, class: 'form-control' #{data_attr} )%>"
|
43
48
|
|
44
49
|
|
45
50
|
if modify_as && modify_as[:enum] == :partials
|
@@ -5,50 +5,37 @@ class Field
|
|
5
5
|
:hawk_keys, :layout_strategy, :limit, :modify_as, :name, :object, :sample_file_path,
|
6
6
|
:self_auth,
|
7
7
|
:singular_class, :singular, :sql_type, :ownership_field,
|
8
|
-
:update_show_only, :namespace, :pundit, :plural
|
8
|
+
:update_show_only, :namespace, :pundit, :plural,
|
9
|
+
:stimmify, :hidden, :attachment_data
|
10
|
+
|
9
11
|
|
10
12
|
def initialize(
|
11
|
-
|
12
|
-
|
13
|
-
class_name: ,
|
14
|
-
alt_lookup: ,
|
15
|
-
default_boolean_display: ,
|
16
|
-
display_as: ,
|
17
|
-
form_labels_position:,
|
18
|
-
form_placeholder_labels: ,
|
19
|
-
hawk_keys: nil,
|
20
|
-
layout_strategy: ,
|
21
|
-
modify_as: , #note non-standard naming as to avoid collision with Ruby reserved word modify
|
22
|
-
name: ,
|
23
|
-
ownership_field: ,
|
24
|
-
sample_file_path: nil,
|
25
|
-
singular: ,
|
26
|
-
update_show_only:,
|
27
|
-
self_auth:,
|
28
|
-
namespace:,
|
29
|
-
pundit: ,
|
30
|
-
plural:
|
13
|
+
scaffold:, name:
|
14
|
+
|
31
15
|
)
|
32
16
|
@name = name
|
33
|
-
@layout_strategy = layout_strategy
|
34
|
-
@alt_lookup =
|
35
|
-
@singular = singular
|
36
|
-
@class_name =
|
37
|
-
@update_show_only = update_show_only
|
38
|
-
@hawk_keys = hawk_keys
|
39
|
-
@auth = auth
|
40
|
-
@sample_file_path = sample_file_path
|
41
|
-
@form_placeholder_labels = form_placeholder_labels
|
42
|
-
@ownership_field = ownership_field
|
43
|
-
@form_labels_position = form_labels_position
|
44
|
-
@modify_as = modify_as
|
45
|
-
@display_as = display_as
|
46
|
-
@pundit = pundit
|
47
|
-
@plural = plural
|
48
|
-
|
49
|
-
@
|
50
|
-
@
|
51
|
-
@
|
17
|
+
@layout_strategy = scaffold.layout_strategy
|
18
|
+
@alt_lookup = scaffold.alt_lookups
|
19
|
+
@singular = scaffold.singular
|
20
|
+
@class_name = scaffold.singular_class
|
21
|
+
@update_show_only = scaffold.update_show_only
|
22
|
+
@hawk_keys = scaffold.hawk_keys
|
23
|
+
@auth = scaffold.auth
|
24
|
+
@sample_file_path = scaffold.sample_file_path
|
25
|
+
@form_placeholder_labels = scaffold.form_placeholder_labels
|
26
|
+
@ownership_field = scaffold.ownership_field
|
27
|
+
@form_labels_position = scaffold.form_labels_position
|
28
|
+
@modify_as = scaffold.modify_as
|
29
|
+
@display_as = scaffold.display_as
|
30
|
+
@pundit = scaffold.pundit
|
31
|
+
@plural = scaffold.plural
|
32
|
+
@self_auth = scaffold.self_auth
|
33
|
+
@default_boolean_display = scaffold.default_boolean_display
|
34
|
+
@namespace = scaffold.namespace_value
|
35
|
+
@stimmify = scaffold.stimmify
|
36
|
+
@hidden = scaffold.hidden
|
37
|
+
@attachment_data = scaffold.attachments[name.to_sym]
|
38
|
+
|
52
39
|
|
53
40
|
# TODO: remove knowledge of subclasses from Field
|
54
41
|
unless self.class == AttachmentField || self.class == RelatedSetField
|
@@ -125,7 +112,7 @@ class Field
|
|
125
112
|
end
|
126
113
|
|
127
114
|
def viewable_output
|
128
|
-
if modify_as
|
115
|
+
if modify_as[:modify]
|
129
116
|
modified_display_output(show_only: true)
|
130
117
|
else
|
131
118
|
field_view_output
|
@@ -179,20 +166,34 @@ class Field
|
|
179
166
|
if modify_as && modify_as[:timezone]
|
180
167
|
"<%= f.time_zone_select :#{name}, ActiveSupport::TimeZone.all, {}, {class: 'form-control'} %>"
|
181
168
|
else
|
182
|
-
|
169
|
+
parts = name.split('_')
|
170
|
+
camelcase_name = parts.first + parts[1..].map(&:capitalize).join
|
171
|
+
" <%= f.text_field :#{name}, value: #{singular}.#{name}, autocomplete: 'off', size: #{width}, class: 'form-control', type: '#{type}'" + (form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") + (stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + " %>\n " + "\n"
|
183
172
|
end
|
184
173
|
end
|
185
174
|
|
175
|
+
def hidden_output
|
176
|
+
parts = name.split('_')
|
177
|
+
camelcase_name = parts.first + parts[1..].map(&:capitalize).join
|
178
|
+
"<%= f.hidden_field :#{name}, value: #{singular}.#{name} " +
|
179
|
+
(@stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") +
|
180
|
+
" %>"
|
181
|
+
end
|
182
|
+
|
186
183
|
def text_area_output(field_length, extra_classes: "")
|
187
184
|
lines = field_length % 40
|
188
185
|
if lines > 5
|
189
186
|
lines = 5
|
190
187
|
end
|
191
|
-
|
188
|
+
|
189
|
+
parts = name.split('_')
|
190
|
+
camelcase_name = parts.first + parts[1..].map(&:capitalize).join
|
191
|
+
"<%= f.text_area :#{name}, class: 'form-control#{extra_classes}', autocomplete: 'off', cols: 40, rows: '#{lines}'" + ( form_placeholder_labels ? ", placeholder: '#{name.to_s.humanize}'" : "") +
|
192
|
+
(@stimmify ? ", 'data-#{@stimmify}-target': '#{camelcase_name}' " : "") + " %>"
|
192
193
|
end
|
193
194
|
|
194
|
-
def modify_binary?
|
195
|
-
!!(modify_as && modify_as[:binary])
|
195
|
+
def modify_binary?
|
196
|
+
!!(modify_as && modify_as[name.to_sym] && modify_as[name.to_sym][:binary])
|
196
197
|
end
|
197
198
|
|
198
199
|
def display_boolean_as
|
@@ -201,8 +202,8 @@ class Field
|
|
201
202
|
@default_boolean_display = "radio"
|
202
203
|
end
|
203
204
|
|
204
|
-
if
|
205
|
-
return display_as[:boolean] || "radio"
|
205
|
+
if display_as[name.to_sym]
|
206
|
+
return display_as[name.to_sym][:boolean] || "radio"
|
206
207
|
else
|
207
208
|
return @default_boolean_display
|
208
209
|
end
|
@@ -2,13 +2,7 @@ class RelatedSetField < Field
|
|
2
2
|
|
3
3
|
attr_accessor :assoc_name, :assoc_class, :assoc
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
name: , singular: , plural:,
|
7
|
-
alt_lookup: ,
|
8
|
-
update_show_only: ,
|
9
|
-
hawk_keys: , auth: , sample_file_path:, ownership_field: ,
|
10
|
-
attachment_data: nil , layout_strategy: , form_placeholder_labels: nil,
|
11
|
-
form_labels_position:, modify_as: , self_auth: , namespace:, pundit:)
|
5
|
+
def initialize( scaffold: , name: )
|
12
6
|
super
|
13
7
|
|
14
8
|
@related_set_model = eval("#{class_name}.reflect_on_association(:#{name})")
|
@@ -37,12 +31,13 @@ class RelatedSetField < Field
|
|
37
31
|
|
38
32
|
def form_field_output
|
39
33
|
disabled_syntax = +""
|
34
|
+
|
40
35
|
if pundit
|
41
36
|
disabled_syntax << ", {disabled: ! #{class_name}Policy.new(#{auth}, @#{singular}).role_ids_able?}"
|
42
37
|
end
|
43
38
|
" <%= f.collection_check_boxes :#{association_ids_method}, #{association_class_name}.all, :id, :label, {}#{disabled_syntax} do |m| %>
|
44
|
-
|
45
|
-
|
39
|
+
<%= m.check_box %> <%= m.label %><br />
|
40
|
+
<% end %>"
|
46
41
|
end
|
47
42
|
|
48
43
|
def association_ids_method
|
@@ -10,7 +10,8 @@ module HotGlue
|
|
10
10
|
:form_placeholder_labels, :hawk_keys, :update_show_only,
|
11
11
|
:attachments, :show_only, :columns_map, :pundit, :related_sets,
|
12
12
|
:search, :search_fields, :search_query_fields, :search_position,
|
13
|
-
:form_path, :layout_object, :search_clear_button, :search_autosearch
|
13
|
+
:form_path, :layout_object, :search_clear_button, :search_autosearch,
|
14
|
+
:stimmify, :stimmify_camel, :hidden
|
14
15
|
|
15
16
|
|
16
17
|
def initialize(singular:, singular_class: ,
|
@@ -22,7 +23,7 @@ module HotGlue
|
|
22
23
|
update_show_only:, attachments: , columns_map:, pundit:, related_sets:,
|
23
24
|
search:, search_fields:, search_query_fields: , search_position:,
|
24
25
|
search_clear_button:, search_autosearch:, layout_object:,
|
25
|
-
form_path: )
|
26
|
+
form_path: , stimmify: , stimmify_camel:, hidden: )
|
26
27
|
|
27
28
|
|
28
29
|
@form_path = form_path
|
@@ -31,6 +32,9 @@ module HotGlue
|
|
31
32
|
@search_by_query = search_query_fields
|
32
33
|
@search_position = search_position
|
33
34
|
@layout_object = layout_object
|
35
|
+
@stimmify = stimmify
|
36
|
+
@stimmify_camel = stimmify_camel
|
37
|
+
@hidden = hidden
|
34
38
|
|
35
39
|
@singular = singular
|
36
40
|
@singular_class = singular_class
|
@@ -169,11 +173,21 @@ module HotGlue
|
|
169
173
|
|
170
174
|
@tinymce_stimulus_controller = (columns_map[col].modify_as == {tinymce: 1} ? "data-controller='tiny-mce' " : "")
|
171
175
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
176
|
+
if @stimmify
|
177
|
+
col_target = HotGlue.to_camel_case(col.to_s.gsub("_", " "))
|
178
|
+
data_attr = " data-#{@stimmify}-target='#{col_target}Wrapper'"
|
179
|
+
end
|
180
|
+
|
181
|
+
unless hidden.include?(col.to_sym)
|
182
|
+
add_spaces_each_line( "\n <span #{@tinymce_stimulus_controller}class='<%= \"alert alert-danger\" if #{singular}.errors.details.keys.include?(:#{field_error_name}) %>' #{data_attr} >\n" +
|
183
|
+
add_spaces_each_line( (form_labels_position == 'before' ? (the_label || "") + "<br />\n" : "") +
|
184
|
+
+ field_result +
|
185
|
+
(form_labels_position == 'after' ? ( columns_map[col].newline_after_field? ? "<br />\n" : "") + (the_label || "") : "") , 4) +
|
186
|
+
"\n </span>\n ", 2)
|
187
|
+
else
|
188
|
+
columns_map[col].hidden_output
|
189
|
+
end
|
190
|
+
|
177
191
|
|
178
192
|
}.join("") + "\n </div>"
|
179
193
|
}.join("\n")
|
@@ -214,6 +228,7 @@ module HotGlue
|
|
214
228
|
if eval("#{singular_class}.columns_hash['#{col}']").nil? && !attachments.keys.include?(col) && !related_sets.include?(col)
|
215
229
|
raise "Can't find column '#{col}' on #{singular_class}, are you sure that is the column name?"
|
216
230
|
end
|
231
|
+
|
217
232
|
field_output = columns_map[col].line_field_output
|
218
233
|
|
219
234
|
label = "<label class='small form-text text-muted'>#{col.to_s.humanize}</label>"
|
@@ -28,7 +28,8 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
28
28
|
:layout_strategy, :form_placeholder_labels,
|
29
29
|
:form_labels_position, :no_nav_menu, :pundit,
|
30
30
|
:self_auth, :namespace_value, :record_scope, :related_sets,
|
31
|
-
:search_clear_button, :search_autosearch, :include_object_names
|
31
|
+
:search_clear_button, :search_autosearch, :include_object_names,
|
32
|
+
:stimmify, :stimmify_camel, :hidden
|
32
33
|
# important: using an attr_accessor called :namespace indirectly causes a conflict with Rails class_name method
|
33
34
|
# so we use namespace_value instead
|
34
35
|
|
@@ -56,6 +57,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
56
57
|
class_option :big_edit, type: :boolean, default: false
|
57
58
|
class_option :show_only, type: :string, default: ""
|
58
59
|
class_option :update_show_only, type: :string, default: ""
|
60
|
+
class_option :hidden, type: :string, default: ""
|
59
61
|
class_option :ujs_syntax, type: :boolean, default: nil
|
60
62
|
class_option :downnest, type: :string, default: nil
|
61
63
|
class_option :magic_buttons, type: :string, default: nil
|
@@ -102,6 +104,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
102
104
|
class_option :include_object_names, type: :boolean, default: false
|
103
105
|
class_option :new_button_position, type: :string, default: 'above'
|
104
106
|
class_option :downnest_shows_headings, type: :boolean, default: nil
|
107
|
+
class_option :stimmify, type: :string, default: nil
|
105
108
|
|
106
109
|
|
107
110
|
# SEARCH OPTIONS
|
@@ -221,25 +224,18 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
221
224
|
@include_fields += options['include'].split(":").collect { |x| x.split(",") }.flatten.collect(&:to_sym)
|
222
225
|
end
|
223
226
|
|
224
|
-
|
225
|
-
# if !options['show_only'].empty?
|
226
|
-
# show_only_input = options['show_only'].split(",")
|
227
|
-
# show_only_input.each do |setting|
|
228
|
-
# if setting.include?("[")
|
229
|
-
# setting =~ /(.*)\[(.*)\]/
|
230
|
-
# key, lookup_as = $1, $2
|
231
|
-
# @show_only_data[key.to_sym] = {cast: $2 }
|
232
|
-
# else
|
233
|
-
# @show_only_data[setting.to_sym] = {cast: nil}
|
234
|
-
# end
|
235
|
-
# end
|
236
|
-
# end
|
227
|
+
|
237
228
|
|
238
229
|
@show_only = options['show_only'].split(",").collect(&:to_sym)
|
239
230
|
if @show_only.any?
|
240
231
|
puts "show only field #{@show_only}}"
|
241
232
|
end
|
242
233
|
|
234
|
+
@hidden = options['hidden'].split(",").collect(&:to_sym)
|
235
|
+
if @hidden.any?
|
236
|
+
puts "hidden fields #{@hidden}}"
|
237
|
+
end
|
238
|
+
|
243
239
|
@modify_as = {}
|
244
240
|
if !options['modify'].empty?
|
245
241
|
modify_input = options['modify'].split(",")
|
@@ -441,6 +437,7 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
441
437
|
@related_sets = {}
|
442
438
|
related_set_input.each do |setting|
|
443
439
|
name = setting.to_sym
|
440
|
+
byebug
|
444
441
|
association_ids_method = eval("#{singular_class}.reflect_on_association(:#{setting.to_sym})").class_name.underscore + "_ids"
|
445
442
|
class_name = eval("#{singular_class}.reflect_on_association(:#{setting.to_sym})").class_name
|
446
443
|
|
@@ -524,7 +521,11 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
524
521
|
# { key: value }
|
525
522
|
# : nil}.compact
|
526
523
|
|
527
|
-
|
524
|
+
@stimmify = options['stimmify']
|
525
|
+
if @stimmify === "stimmify"
|
526
|
+
@stimmify = @singular.gsub("_", "-") + "-form"
|
527
|
+
@stimify_camel = @stimmify.camelize
|
528
|
+
end
|
528
529
|
|
529
530
|
# build a new polymorphic object
|
530
531
|
@associations = []
|
@@ -649,7 +650,10 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
649
650
|
search_position: @search_position,
|
650
651
|
search_clear_button: @search_clear_button,
|
651
652
|
search_autosearch: @search_autosearch,
|
652
|
-
form_path: form_path_new_helper
|
653
|
+
form_path: form_path_new_helper,
|
654
|
+
stimmify: @stimmify,
|
655
|
+
stimmify_camel: @stimmify_camel,
|
656
|
+
hidden: @hidden
|
653
657
|
)
|
654
658
|
elsif @markup == "slim"
|
655
659
|
raise(HotGlue::Error, "SLIM IS NOT IMPLEMENTED")
|
@@ -1085,6 +1089,17 @@ class HotGlue::ScaffoldGenerator < Erb::Generators::ScaffoldGenerator
|
|
1085
1089
|
top_level: top_level)
|
1086
1090
|
end
|
1087
1091
|
|
1092
|
+
def edit_parent_path_helper
|
1093
|
+
# the path to the edit route of the PARENT
|
1094
|
+
if @nested_set.any? && @nested
|
1095
|
+
"edit_#{@namespace + "_" if @namespace}#{(@nested_set.collect { |x| x[:singular] }.join("_") + "_" if @nested_set.any?)}path(" +
|
1096
|
+
"#{@nested_set.collect { |x| x[:singular] }.join(", ")}" + ")"
|
1097
|
+
|
1098
|
+
else
|
1099
|
+
"edit_#{@namespace + "_" if @namespace}path"
|
1100
|
+
end
|
1101
|
+
end
|
1102
|
+
|
1088
1103
|
def datetime_fields_list
|
1089
1104
|
@columns.select do |col|
|
1090
1105
|
if @the_object.columns_hash[col.to_s]
|
@@ -4,7 +4,9 @@
|
|
4
4
|
<\%= render(partial: "<%= namespace_with_trailing_dash %>errors", locals: {resource: <%= singular %> }) %>
|
5
5
|
<\% end %>
|
6
6
|
<h2>Editing <%= singular + " " if @include_object_names %><\%= <%= singular %>.<%= display_class %> %></h2>
|
7
|
-
<\%= form_with model: <%= singular %>,
|
7
|
+
<\%= form_with model: <%= singular %>,
|
8
|
+
<% if @stimmify %> data: {controller: '<%= @stimmify %>' },
|
9
|
+
<% end %>url: <%= form_path_edit_helper %><%= ", html: {'data-turbo': false}" if @big_edit %> do |f| %>
|
8
10
|
<\%= render partial: "<%= namespace_with_trailing_dash + @controller_build_folder + "/" %>form", locals: {:<%= singular %> => <%= singular %>, f: f}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> \%>
|
9
11
|
<% if @edit_within_form_partial %><\%= render partial: "edit_within_form", locals: {f: f, <%= singular %>: <%= singular %>}<%= @nested_set.collect{|arg| ".merge(#{arg[:singular]} ? {#{arg[:singular]}: #{arg[:singular]}} : {})" }.join %> %><% end %>
|
10
12
|
<\% end %>
|
@@ -2,7 +2,8 @@
|
|
2
2
|
<%= form_fields_html %>
|
3
3
|
|
4
4
|
<div class="<%= @layout_strategy.column_classes_for_button_column %>">
|
5
|
-
<\%= link_to "Cancel", <%=
|
5
|
+
<\%= link_to "Cancel", <%=
|
6
|
+
@nested_set.none? ? path_helper_plural : edit_parent_path_helper %>, {class: "btn btn-secondary"} %><% if @no_field_form %>
|
6
7
|
<\%= f.hidden_field "_________" %><% end %>
|
7
8
|
<\%= f.submit "Save", class: "btn btn-primary pull-right" %>
|
8
9
|
</div>
|
@@ -2,7 +2,9 @@
|
|
2
2
|
<h3>
|
3
3
|
<%= @new_form_heading %>
|
4
4
|
</h3>
|
5
|
-
<\%= form_with model: <%= singular %>,
|
5
|
+
<\%= form_with model: <%= singular %>,
|
6
|
+
<% if @stimmify %>data: {controller: '<%= @stimmify %>' },
|
7
|
+
<% end %>url: <%= form_path_new_helper %>, method: :post<%= @display_edit_after_create ? ", html: {'data-turbo': false}" : "" %> do |f| \%>
|
6
8
|
<\%= render partial: "<%= namespace_with_slash + @controller_build_folder %>/form",
|
7
9
|
locals: { <%= singular %>: <%= singular %>, f: f}<%= @nested_set.collect{|arg| ".merge(defined?(#{arg[:singular]}) ? {#{arg[:singular]}: #{arg[:singular]}}: {})" }.join %> \%>
|
8
10
|
|
data/lib/hot-glue.rb
CHANGED
@@ -10,6 +10,18 @@ module HotGlue
|
|
10
10
|
class Error < StandardError
|
11
11
|
end
|
12
12
|
|
13
|
+
|
14
|
+
def self.to_camel_case(str)
|
15
|
+
words = str.split(/[^a-zA-Z0-9]/) # split by non-alphanumeric characters
|
16
|
+
return '' if words.empty?
|
17
|
+
|
18
|
+
first_word = words.first.downcase
|
19
|
+
rest_words = words[1..-1].map { |w| w.capitalize }
|
20
|
+
|
21
|
+
first_word + rest_words.join
|
22
|
+
end
|
23
|
+
|
24
|
+
|
13
25
|
def self.construct_downnest_object(input)
|
14
26
|
res = input.split(",").map { |child|
|
15
27
|
child_name = child.gsub("+","")
|
data/lib/hotglue/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hot-glue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Fleetwood-Boldt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|