stimulus_builder-rails 0.1.0.alpha.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +408 -0
- data/Rakefile +3 -0
- data/lib/stimulus_builder/action_attribute.rb +27 -0
- data/lib/stimulus_builder/action_descriptor.rb +38 -0
- data/lib/stimulus_builder/attribute.rb +11 -0
- data/lib/stimulus_builder/class_attribute.rb +16 -0
- data/lib/stimulus_builder/controller.rb +47 -0
- data/lib/stimulus_builder/controller_attribute.rb +27 -0
- data/lib/stimulus_builder/element.rb +14 -0
- data/lib/stimulus_builder/element_representable.rb +78 -0
- data/lib/stimulus_builder/form_builder.rb +36 -0
- data/lib/stimulus_builder/handler.rb +19 -0
- data/lib/stimulus_builder/helper.rb +7 -0
- data/lib/stimulus_builder/helper_delegate.rb +37 -0
- data/lib/stimulus_builder/outlet.rb +23 -0
- data/lib/stimulus_builder/outlet_attribute.rb +17 -0
- data/lib/stimulus_builder/param_attribute.rb +24 -0
- data/lib/stimulus_builder/railtie.rb +9 -0
- data/lib/stimulus_builder/target_attribute.rb +36 -0
- data/lib/stimulus_builder/value_attribute.rb +16 -0
- data/lib/stimulus_builder/version.rb +3 -0
- data/lib/stimulus_builder.rb +22 -0
- data/lib/tasks/stimulus_builder_tasks.rake +4 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5cdeb6e857f28a15b6fe3bdd7051410db4131ae29a8749672400645c788794bf
|
4
|
+
data.tar.gz: fa3d4a4cbeb560f81c463883f42fc4f5f0615084c509cf1941f2657f6b35e1da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a4479b2f7bd616293bb3680dd3d68b1d450d99259cdbe4855ca1fde04c05a30cda8b103c043e012f1a0ae75f73eedbe531eef74fb333ec384d6880c38d4871e
|
7
|
+
data.tar.gz: 06febb06ac7d6515e1c502565168023b85b827526fd9ef82da8dbb3fea2d688beca87395c24525d01386f577385548d436c1f160609faee03d36972384a510df
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2024 Nipun Paradkar
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,408 @@
|
|
1
|
+
# StimulusBuilder
|
2
|
+
|
3
|
+
Manually adding Stimulus Attributes to HTML elements was something that I didn't like because:
|
4
|
+
|
5
|
+
- they were hard to notice and track in the view files
|
6
|
+
- had to write them manually without any structure
|
7
|
+
- didn't look good clubbed with other attributes
|
8
|
+
|
9
|
+
So, I decided to write this gem to provide a syntax that translates to the Stimulus Attributes behind the scene.
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
The gem takes inspiration from `ActionView::Helpers::FormBuilder` and tries to follow the syntax set by it.
|
14
|
+
|
15
|
+
### Connecting controllers
|
16
|
+
|
17
|
+
```erb
|
18
|
+
<%= stimulated.div do |component| %>
|
19
|
+
<% component.connect(:reference) %>
|
20
|
+
<% end %>
|
21
|
+
```
|
22
|
+
|
23
|
+
will output:
|
24
|
+
|
25
|
+
```html
|
26
|
+
<div data-controller="reference"></div>
|
27
|
+
```
|
28
|
+
|
29
|
+
The `#connect` method accepts strings as well:
|
30
|
+
|
31
|
+
```erb
|
32
|
+
<% component.connect("clipboard") %>
|
33
|
+
```
|
34
|
+
|
35
|
+
Using strings, you can also specify a namespaced controller:
|
36
|
+
|
37
|
+
```erb
|
38
|
+
<%= stimulated.div do |component| %>
|
39
|
+
<% component.connect("users/list_item") %>
|
40
|
+
<% end %>
|
41
|
+
```
|
42
|
+
|
43
|
+
which will output:
|
44
|
+
|
45
|
+
```html
|
46
|
+
<div data-controller="users--list-item"></div>
|
47
|
+
```
|
48
|
+
|
49
|
+
If your controller consists of multiple words, you can use `snake_case` notation via symbols:
|
50
|
+
|
51
|
+
```erb
|
52
|
+
<%= stimulated.div do |component| %>
|
53
|
+
<% component.connect(:date_picker) %>
|
54
|
+
<% end %>
|
55
|
+
```
|
56
|
+
|
57
|
+
or via strings:
|
58
|
+
|
59
|
+
```erb
|
60
|
+
<%= stimulated.div do |component| %>
|
61
|
+
<% component.connect("date_picker") %>
|
62
|
+
<% end %>
|
63
|
+
```
|
64
|
+
|
65
|
+
both will output:
|
66
|
+
|
67
|
+
```html
|
68
|
+
<div data-controller="date-picker"></div>
|
69
|
+
```
|
70
|
+
|
71
|
+
#### Connecting multiple controllers
|
72
|
+
|
73
|
+
```erb
|
74
|
+
<%= stimulated.div do |component| %>
|
75
|
+
<% component.connect(:clipboard) %>
|
76
|
+
<% component.connect(:list_item) %>
|
77
|
+
<% end %>
|
78
|
+
```
|
79
|
+
|
80
|
+
will output:
|
81
|
+
|
82
|
+
```html
|
83
|
+
<div data-controller="clipboard list-item"></div>
|
84
|
+
```
|
85
|
+
|
86
|
+
### Attaching actions
|
87
|
+
|
88
|
+
```erb
|
89
|
+
<%= stimulated.div do |component| %>
|
90
|
+
<% gallery = component.connect(:gallery) %>
|
91
|
+
|
92
|
+
<%= stimulated.button do |button| %>
|
93
|
+
<% button.on("click") { gallery.next } %>
|
94
|
+
<% end %>
|
95
|
+
<% end %>
|
96
|
+
```
|
97
|
+
|
98
|
+
will output:
|
99
|
+
|
100
|
+
```html
|
101
|
+
<div data-controller="gallery">
|
102
|
+
<button data-action="click->gallery#next"></button>
|
103
|
+
</div>
|
104
|
+
```
|
105
|
+
|
106
|
+
The `#connect` method returns a representation of the controller that's passed to it. Calling methods on this object inside the block passed to `#on`, and also passing the event will convert it to an action attribute.
|
107
|
+
|
108
|
+
#### Event shorthand
|
109
|
+
|
110
|
+
If you want to fallback to the default event, use the `#fire` method instead of `#on`:
|
111
|
+
|
112
|
+
```erb
|
113
|
+
<%= stimulated.button do |button| %>
|
114
|
+
<% button.fire { gallery.next } %>
|
115
|
+
<% end %>
|
116
|
+
```
|
117
|
+
|
118
|
+
will generate:
|
119
|
+
|
120
|
+
```html
|
121
|
+
<button data-action="gallery#next"></button>
|
122
|
+
```
|
123
|
+
|
124
|
+
#### Global events
|
125
|
+
|
126
|
+
The second parameter to `#on` is where the event should be attached. It can either be `:window`, or `:document`:
|
127
|
+
|
128
|
+
```erb
|
129
|
+
<%= stimulated.div do |component| %>
|
130
|
+
<% gallery = component.connect(:gallery) %>
|
131
|
+
|
132
|
+
<% component.on("resize", :window) { gallery.layout } %>
|
133
|
+
<% end %>
|
134
|
+
```
|
135
|
+
|
136
|
+
will output:
|
137
|
+
|
138
|
+
```html
|
139
|
+
<div data-controller="gallery" data-action="resize@window->gallery#layout"></div>
|
140
|
+
```
|
141
|
+
|
142
|
+
#### Action options
|
143
|
+
|
144
|
+
Action options can be passed via hash parameters to `#on`:
|
145
|
+
|
146
|
+
```erb
|
147
|
+
<%= stimulated.div do |component| %>
|
148
|
+
<% gallery = component.connect(:gallery) %>
|
149
|
+
|
150
|
+
<% component.on("scroll", passive: false) { gallery.layout } %>
|
151
|
+
|
152
|
+
<%= stimulated.img do |image| %>
|
153
|
+
<% image.on("click", capture: true) { gallery.open }
|
154
|
+
<% end %>
|
155
|
+
<% end %>
|
156
|
+
```
|
157
|
+
|
158
|
+
will output:
|
159
|
+
|
160
|
+
```html
|
161
|
+
<div data-controller="gallery" data-action="scroll->gallery#layout:!passive">
|
162
|
+
<img data-action="click->gallery#open:capture">
|
163
|
+
</div>
|
164
|
+
```
|
165
|
+
|
166
|
+
#### Multiple actions
|
167
|
+
|
168
|
+
Calling `#on` more than once will append actions to the action attribute:
|
169
|
+
|
170
|
+
```erb
|
171
|
+
<%= stimulated.div do |component| %>
|
172
|
+
<% field = component.connect(:field) %>
|
173
|
+
<% search = component.connect(:search) %>
|
174
|
+
|
175
|
+
<%= stimulated.input(type: "text") do |input| %>
|
176
|
+
<% input.on("focus") { field.highlight } %>
|
177
|
+
<% input.on("input") { search.update } %>
|
178
|
+
<% end %>
|
179
|
+
<% end
|
180
|
+
```
|
181
|
+
|
182
|
+
will output:
|
183
|
+
|
184
|
+
```html
|
185
|
+
<div data-controller="field search">
|
186
|
+
<input type="text" data-action="focus->field#highlight input->search#update">
|
187
|
+
</div>
|
188
|
+
```
|
189
|
+
|
190
|
+
#### Naming conventions
|
191
|
+
|
192
|
+
If the method calls on the controller representation object is more than one word, it'll `camelCase` it:
|
193
|
+
|
194
|
+
```erb
|
195
|
+
<%= stimulated.div do |component| %>
|
196
|
+
<% profile = component.connect(:profile) %>
|
197
|
+
|
198
|
+
<%= stimulated.button do |input| %>
|
199
|
+
<% input.on("click") { profile.show_dialog } %>
|
200
|
+
<% end %>
|
201
|
+
<% end
|
202
|
+
```
|
203
|
+
|
204
|
+
will output:
|
205
|
+
|
206
|
+
```html
|
207
|
+
<div data-controller="profile">
|
208
|
+
<button data-action="click->profile#showDialog">
|
209
|
+
</div>
|
210
|
+
```
|
211
|
+
|
212
|
+
#### Action parameters
|
213
|
+
|
214
|
+
```erb
|
215
|
+
<%= stimulated.div do |component| %>
|
216
|
+
<% item = component.connect(:item) %>
|
217
|
+
<% spinner = component.connect(:spinner) %>
|
218
|
+
|
219
|
+
<%= stimulated.button do |input| %>
|
220
|
+
<% input.fire do %>
|
221
|
+
<% item.upvote(id: "12345", url: "/votes", active: true) %>
|
222
|
+
<% end %>
|
223
|
+
<% input.fire { spinner.start } %>
|
224
|
+
<% end %>
|
225
|
+
<% end
|
226
|
+
```
|
227
|
+
|
228
|
+
will output:
|
229
|
+
|
230
|
+
```html
|
231
|
+
<div data-controller="item spinner">
|
232
|
+
<button data-action="item#upvote spinner#start"
|
233
|
+
data-item-id-param="12345"
|
234
|
+
data-item-url-param="/votes"
|
235
|
+
data-item-active-param="true">
|
236
|
+
</button>
|
237
|
+
</div>
|
238
|
+
```
|
239
|
+
|
240
|
+
### Referencing targets
|
241
|
+
|
242
|
+
The syntax to an element as a target for a controller is `[controller].[target_name] = [element]`.
|
243
|
+
|
244
|
+
For example:
|
245
|
+
|
246
|
+
```erb
|
247
|
+
<%= stimulated.div do |component| %>
|
248
|
+
<% search = component.connect(:search) %>
|
249
|
+
|
250
|
+
<%= stimulated.input(type: "text") do |input| %>
|
251
|
+
<% search.query = input %>
|
252
|
+
<% end %>
|
253
|
+
|
254
|
+
<%= stimulated.div do |element| %>
|
255
|
+
<% search.error_message = element %>
|
256
|
+
<% end %>
|
257
|
+
|
258
|
+
<%= stimulated.div do |element| %>
|
259
|
+
<% search.results = element %>
|
260
|
+
<% end %>
|
261
|
+
<% end %>
|
262
|
+
```
|
263
|
+
|
264
|
+
will output:
|
265
|
+
|
266
|
+
```html
|
267
|
+
<div data-controller="search">
|
268
|
+
<input type="text" data-search-target="query">
|
269
|
+
<div data-search-target="errorMessage"></div>
|
270
|
+
<div data-search-target="results"></div>
|
271
|
+
</div>
|
272
|
+
```
|
273
|
+
|
274
|
+
#### Shared targets
|
275
|
+
|
276
|
+
You can add same element as a target for different controllers:
|
277
|
+
|
278
|
+
```erb
|
279
|
+
<%= stimulated.form_for(:users, url: "/users") do |form| %>
|
280
|
+
<% search = form.connect(:search) %>
|
281
|
+
<% checkbox = form.connect(:checkbox) %>
|
282
|
+
|
283
|
+
<%= stimulated.check_box do |input| %>
|
284
|
+
<% search.projects = input %>
|
285
|
+
<% checkbox.input = input %>
|
286
|
+
<% end %>
|
287
|
+
|
288
|
+
<%= stimulated.check_box do |input| %>
|
289
|
+
<% search.messages = input %>
|
290
|
+
<% checkbox.input = input %>
|
291
|
+
<% end %>
|
292
|
+
<% end %>
|
293
|
+
```
|
294
|
+
|
295
|
+
will output:
|
296
|
+
|
297
|
+
```html
|
298
|
+
<form action="/users" accept-charset="UTF-8" method="post" data-controller="search checkbox">
|
299
|
+
<input type="checkbox" data-search-target="projects" data-checkbox-target="input">
|
300
|
+
<input type="checkbox" data-search-target="messages" data-checkbox-target="input">
|
301
|
+
</form>
|
302
|
+
```
|
303
|
+
|
304
|
+
#### Naming conventions
|
305
|
+
|
306
|
+
When target names are more than one word, use `snake_case` for the method names on the `[controller]`:
|
307
|
+
|
308
|
+
```erb
|
309
|
+
<%= stimulated.div do |component| %>
|
310
|
+
<% search = component.connect(:search) %>
|
311
|
+
|
312
|
+
<% stimulated.span do |element| %>
|
313
|
+
<% search.snake_case = element %>
|
314
|
+
<% end %>
|
315
|
+
<% end %>
|
316
|
+
```
|
317
|
+
|
318
|
+
### Outlets
|
319
|
+
|
320
|
+
The `#use` method present on the element returns an object that is a logical representation of an outlet controller. We can then assign this outlet object to the controller where this outlet needs to be accessible by using the controller's `#[]=` method.
|
321
|
+
|
322
|
+
```erb
|
323
|
+
<div>
|
324
|
+
<%= stimulated.div(class: "online-user") do |component| %>
|
325
|
+
<% component.connect(:user_status)
|
326
|
+
<% end %>
|
327
|
+
<%= stimulated.div(class: "online-user") do |component| %>
|
328
|
+
<% component.connect(:user_status)
|
329
|
+
<% end %>
|
330
|
+
<%# ... %>
|
331
|
+
</div>
|
332
|
+
|
333
|
+
<%# ... %>
|
334
|
+
|
335
|
+
<%= stimulated.div(id: "chat") do |component| %>
|
336
|
+
<% chat = component.connect(:chat) %>
|
337
|
+
<% user_status = component.use(:user_status) %>
|
338
|
+
|
339
|
+
<% chat[".online-user"] = user_status %>
|
340
|
+
<% end %>
|
341
|
+
```
|
342
|
+
|
343
|
+
The above will add a `data-chat-user-status-outlet=".online-user"` attribute on the `div#chat` element.
|
344
|
+
|
345
|
+
`#use` accepts a name of the controller to be used as an outlet, and it has the same naming convention rules as the `#connect` method. The only difference between the both is that the controller returned by the `#use` method is simpler than the one returned by the `#connect` method, _i.e._ it cannot be used to do anything else like setting up actions, or adding targets, etc.
|
346
|
+
|
347
|
+
### Values
|
348
|
+
|
349
|
+
You can pass values to the controller by passing a hash as the second parameter to `#connect`. This hash needs to have the `:values` key present, containing all the values.
|
350
|
+
|
351
|
+
```html
|
352
|
+
<%= stimulated.div do |component| %>
|
353
|
+
<%
|
354
|
+
component.connect(:loader, {
|
355
|
+
values: { url: "/messages" },
|
356
|
+
})
|
357
|
+
%>
|
358
|
+
<% end %>
|
359
|
+
```
|
360
|
+
|
361
|
+
will output:
|
362
|
+
|
363
|
+
```html
|
364
|
+
<div data-controller="loader" data-loader-url-value="/messages"></div>
|
365
|
+
```
|
366
|
+
|
367
|
+
### Classes
|
368
|
+
|
369
|
+
You can pass classes (Stimulus classes, not HTML classes) to the controller by passing a hash as the second parameter to `#connect`. This hash needs to have the `:classes` key present, containing all the values.
|
370
|
+
|
371
|
+
```html
|
372
|
+
<%= stimulated.form_for(:user, url: "/users") do |form| %>
|
373
|
+
<%
|
374
|
+
form.connect(:search, {
|
375
|
+
classes: { loading: "search--busy" },
|
376
|
+
})
|
377
|
+
%>
|
378
|
+
<% end %>
|
379
|
+
```
|
380
|
+
|
381
|
+
will output:
|
382
|
+
|
383
|
+
```html
|
384
|
+
<form action="/users" data-controller="search" data-search-loading-class="search--busy"></form>
|
385
|
+
```
|
386
|
+
|
387
|
+
## Installation
|
388
|
+
Add this line to your application's Gemfile:
|
389
|
+
|
390
|
+
```ruby
|
391
|
+
gem "stimulus_builder"
|
392
|
+
```
|
393
|
+
|
394
|
+
And then execute:
|
395
|
+
```bash
|
396
|
+
$ bundle
|
397
|
+
```
|
398
|
+
|
399
|
+
Or install it yourself as:
|
400
|
+
```bash
|
401
|
+
$ gem install stimulus_builder
|
402
|
+
```
|
403
|
+
|
404
|
+
## Contributing
|
405
|
+
Contribution directions go here.
|
406
|
+
|
407
|
+
## License
|
408
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ActionAttribute < Attribute
|
3
|
+
def initialize(*action_descriptors)
|
4
|
+
@action_descriptors = action_descriptors
|
5
|
+
end
|
6
|
+
|
7
|
+
def name
|
8
|
+
"data-action"
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
@action_descriptors.join(" ").html_safe
|
13
|
+
end
|
14
|
+
|
15
|
+
def +(action_attribute)
|
16
|
+
self.class.new(*(@action_descriptors + action_attribute.action_descriptors))
|
17
|
+
end
|
18
|
+
|
19
|
+
def multi?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :action_descriptors
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ActionDescriptor
|
3
|
+
def initialize(event, handler, target = nil, **options)
|
4
|
+
@event, @handler = event, handler
|
5
|
+
@target, @options = target, options
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
descriptor = ""
|
10
|
+
|
11
|
+
if @event.present?
|
12
|
+
descriptor += @event
|
13
|
+
|
14
|
+
if @target.present?
|
15
|
+
descriptor += "@#{@target}"
|
16
|
+
end
|
17
|
+
|
18
|
+
descriptor += "->"
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
descriptor + "#{@handler}" + processed_options
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def processed_options
|
28
|
+
@options.inject("") do |processed_options, (option, condition)|
|
29
|
+
processed_options +=
|
30
|
+
if condition == true
|
31
|
+
":#{option}"
|
32
|
+
elsif condition == false
|
33
|
+
":!#{option}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ClassAttribute < Attribute
|
3
|
+
def initialize(identifier, logical_name, klass)
|
4
|
+
@identifier = identifier
|
5
|
+
@logical_name, @klass = logical_name.to_s, klass
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
"data-#{@identifier}-#{@logical_name.dasherize}-class"
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
@klass
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class Controller
|
3
|
+
MODULE_SEPARATOR = "/".freeze
|
4
|
+
IDENTIFIER_SEPARATOR = "--".freeze
|
5
|
+
|
6
|
+
private_constant :MODULE_SEPARATOR, :IDENTIFIER_SEPARATOR
|
7
|
+
|
8
|
+
def initialize(controller_name, element)
|
9
|
+
@controller_name = controller_name.to_s
|
10
|
+
@element = element
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(action_method, *args)
|
14
|
+
if action_method.ends_with?("=".freeze)
|
15
|
+
args[0] << TargetAttribute.new(self, action_method[..-2])
|
16
|
+
else
|
17
|
+
params = args[0] || {}
|
18
|
+
Handler.new(self, action_method, params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](event_name)
|
23
|
+
"#{self}:#{event_name.to_s.dasherize}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(selector, outlet)
|
27
|
+
@element << OutletAttribute.new(self, outlet, selector)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
to_str
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_str
|
35
|
+
controller_identifier
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def controller_identifier
|
41
|
+
@controller_name
|
42
|
+
.split(MODULE_SEPARATOR)
|
43
|
+
.map(&:dasherize)
|
44
|
+
.join(IDENTIFIER_SEPARATOR)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ControllerAttribute < Attribute
|
3
|
+
def initialize(*controllers)
|
4
|
+
@controllers = controllers
|
5
|
+
end
|
6
|
+
|
7
|
+
def name
|
8
|
+
"data-controller"
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
@controllers.join(" ")
|
13
|
+
end
|
14
|
+
|
15
|
+
def +(controller_attribute)
|
16
|
+
self.class.new(*(@controllers + controller_attribute.controllers))
|
17
|
+
end
|
18
|
+
|
19
|
+
def multi?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :controllers
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
module ElementRepresentable
|
3
|
+
def attributes
|
4
|
+
@attributes.inject({}) do |memo, attribute|
|
5
|
+
memo.merge(attribute)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def <<(attribute)
|
10
|
+
update_attributes!(attribute)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connect(identifier_name, props = nil)
|
14
|
+
Controller.new(identifier_name, self).tap do |controller|
|
15
|
+
self << ControllerAttribute.new(controller)
|
16
|
+
|
17
|
+
unless props.nil?
|
18
|
+
create_value_attributes!(controller, props[:values]) if props[:values].present?
|
19
|
+
create_class_attributes!(controller, props[:classes]) if props[:classes].present?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def use(controller_name)
|
25
|
+
Outlet.new(controller_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def fire(**options, &block)
|
29
|
+
on(nil, **options, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on(event, at = nil, **options, &block)
|
33
|
+
block.call.then do |handler|
|
34
|
+
self << ActionAttribute.new(ActionDescriptor.new(event, handler, at, **options))
|
35
|
+
|
36
|
+
handler.param_attributes.each do |param_attribute|
|
37
|
+
self << param_attribute
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# FIXME: This is required so that when this method is called from Ruby files,
|
42
|
+
# it doesn't output the value that gets returned by the above line.
|
43
|
+
''
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def create_value_attributes!(identifier, value_props)
|
49
|
+
value_props.each do |value_name, value_value|
|
50
|
+
self << ValueAttribute.new(identifier, value_name, value_value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_class_attributes!(identifier, class_props)
|
55
|
+
class_props.each do |class_name, class_value|
|
56
|
+
self << ClassAttribute.new(identifier, class_name, class_value)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def update_attributes!(attribute)
|
61
|
+
if attribute.multi?
|
62
|
+
unless (current_index = attribute_index(attribute)).nil?
|
63
|
+
@attributes[current_index] += attribute
|
64
|
+
else
|
65
|
+
@attributes << attribute
|
66
|
+
end
|
67
|
+
else
|
68
|
+
@attributes << attribute
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def attribute_index(attribute)
|
73
|
+
@attributes.index do |iterable_attribute|
|
74
|
+
attribute.name == iterable_attribute.name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
include ElementRepresentable
|
4
|
+
|
5
|
+
def initialize(object_name, object, template, options)
|
6
|
+
@attributes = []
|
7
|
+
|
8
|
+
super(object_name, object, template, options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(attribute)
|
12
|
+
super(attribute)
|
13
|
+
|
14
|
+
options[:html] ||= {}
|
15
|
+
options[:html].merge!(attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
19
|
+
Element.new.then do |element|
|
20
|
+
yield(element)
|
21
|
+
|
22
|
+
super(method, options.merge(element.attributes), checked_value, unchecked_value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def button(value = nil, options = {})
|
27
|
+
Element.new.then do |element|
|
28
|
+
@template.capture do
|
29
|
+
yield(element, value)
|
30
|
+
end.then do |inner_html|
|
31
|
+
super(value, options.merge(element.attributes)) { inner_html }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class Handler
|
3
|
+
def initialize(controller, action_method, params)
|
4
|
+
@controller = controller
|
5
|
+
@action_method = action_method.to_s
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
def param_attributes
|
10
|
+
@params.map do |param_name, param_value|
|
11
|
+
ParamAttribute.new(@controller, param_name, param_value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@controller}##{@action_method.camelize(:lower)}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class HelperDelegate
|
3
|
+
ELEMENT_NAMES = [
|
4
|
+
:div, :span, :input, :button, :img
|
5
|
+
].freeze
|
6
|
+
|
7
|
+
private_constant :ELEMENT_NAMES
|
8
|
+
|
9
|
+
def initialize(view_context)
|
10
|
+
@view_context = view_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def form_for(record, options = {}, &block)
|
14
|
+
options[:builder] ||= FormBuilder
|
15
|
+
|
16
|
+
@view_context.form_for(record, options, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
ELEMENT_NAMES.each do |element_name|
|
20
|
+
define_method(element_name) do |content = nil, **options, &block|
|
21
|
+
Element.new.then do |element|
|
22
|
+
@view_context.capture do
|
23
|
+
block.call(element)
|
24
|
+
end.then do |inner_html|
|
25
|
+
@view_context
|
26
|
+
.tag
|
27
|
+
.public_send(
|
28
|
+
element_name,
|
29
|
+
inner_html,
|
30
|
+
**options.merge(element.attributes)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class StimulusBuilder::Outlet
|
2
|
+
MODULE_SEPARATOR = "/".freeze
|
3
|
+
IDENTIFIER_SEPARATOR = "--".freeze
|
4
|
+
|
5
|
+
private_constant :MODULE_SEPARATOR, :IDENTIFIER_SEPARATOR
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
identifier
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def identifier
|
18
|
+
@name
|
19
|
+
.split(MODULE_SEPARATOR)
|
20
|
+
.map(&:dasherize)
|
21
|
+
.join(IDENTIFIER_SEPARATOR)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class OutletAttribute < Attribute
|
3
|
+
def initialize(identifier, outlet, selector)
|
4
|
+
@identifier = identifier
|
5
|
+
@outlet = outlet
|
6
|
+
@selector = selector
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
"data-#{@identifier}-#{@outlet}-outlet"
|
11
|
+
end
|
12
|
+
|
13
|
+
def value
|
14
|
+
@selector
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ParamAttribute < Attribute
|
3
|
+
attr_reader :value
|
4
|
+
|
5
|
+
def initialize(identifier, name, value)
|
6
|
+
@identifier = identifier
|
7
|
+
@name, @value = name.to_s, value
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
"data-#{@identifier}-#{dasherized_name}-param"
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
{ name => value }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def dasherized_name
|
21
|
+
@name.dasherize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class TargetAttribute < Attribute
|
3
|
+
def initialize(identifier, *target_names)
|
4
|
+
@identifier = identifier
|
5
|
+
@target_names = target_names
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
"data-#{@identifier}-target"
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
properties.join(" ")
|
14
|
+
end
|
15
|
+
|
16
|
+
def multi?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(target_attribute)
|
21
|
+
self.class.new(@identifier, *(@target_names + target_attribute.target_names))
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :target_names
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def properties
|
31
|
+
@target_names.map do |target_name|
|
32
|
+
target_name.camelize(:lower)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module StimulusBuilder
|
2
|
+
class ValueAttribute < Attribute
|
3
|
+
def initialize(identifier, key, value)
|
4
|
+
@identifier = identifier
|
5
|
+
@key, @value = key.to_s, value
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
"data-#{@identifier}-#{@key.dasherize}-value"
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
@value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "stimulus_builder/version"
|
2
|
+
|
3
|
+
require "stimulus_builder/attribute"
|
4
|
+
require "stimulus_builder/action_attribute"
|
5
|
+
require "stimulus_builder/action_descriptor"
|
6
|
+
require "stimulus_builder/class_attribute"
|
7
|
+
require "stimulus_builder/controller_attribute"
|
8
|
+
require "stimulus_builder/element_representable"
|
9
|
+
require "stimulus_builder/form_builder"
|
10
|
+
require "stimulus_builder/handler"
|
11
|
+
require "stimulus_builder/helper_delegate"
|
12
|
+
require "stimulus_builder/outlet"
|
13
|
+
require "stimulus_builder/outlet_attribute"
|
14
|
+
require "stimulus_builder/param_attribute"
|
15
|
+
require "stimulus_builder/target_attribute"
|
16
|
+
require "stimulus_builder/value_attribute"
|
17
|
+
|
18
|
+
require "stimulus_builder/railtie"
|
19
|
+
|
20
|
+
module StimulusBuilder
|
21
|
+
# Your code goes here...
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stimulus_builder-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.alpha.pre.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nipun Paradkar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 7.0.3.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 7.0.3.1
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
- nipunparadkar123@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/stimulus_builder.rb
|
38
|
+
- lib/stimulus_builder/action_attribute.rb
|
39
|
+
- lib/stimulus_builder/action_descriptor.rb
|
40
|
+
- lib/stimulus_builder/attribute.rb
|
41
|
+
- lib/stimulus_builder/class_attribute.rb
|
42
|
+
- lib/stimulus_builder/controller.rb
|
43
|
+
- lib/stimulus_builder/controller_attribute.rb
|
44
|
+
- lib/stimulus_builder/element.rb
|
45
|
+
- lib/stimulus_builder/element_representable.rb
|
46
|
+
- lib/stimulus_builder/form_builder.rb
|
47
|
+
- lib/stimulus_builder/handler.rb
|
48
|
+
- lib/stimulus_builder/helper.rb
|
49
|
+
- lib/stimulus_builder/helper_delegate.rb
|
50
|
+
- lib/stimulus_builder/outlet.rb
|
51
|
+
- lib/stimulus_builder/outlet_attribute.rb
|
52
|
+
- lib/stimulus_builder/param_attribute.rb
|
53
|
+
- lib/stimulus_builder/railtie.rb
|
54
|
+
- lib/stimulus_builder/target_attribute.rb
|
55
|
+
- lib/stimulus_builder/value_attribute.rb
|
56
|
+
- lib/stimulus_builder/version.rb
|
57
|
+
- lib/tasks/stimulus_builder_tasks.rake
|
58
|
+
homepage: https://github.com/radiantshaw/stimulus_builder-rails
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata:
|
62
|
+
homepage_uri: https://github.com/radiantshaw/stimulus_builder-rails
|
63
|
+
source_code_uri: https://github.com/radiantshaw/stimulus_builder-rails
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.3.1
|
78
|
+
requirements: []
|
79
|
+
rubygems_version: 3.3.3
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Add Stimulus attributes using a nicer Ruby syntax.
|
83
|
+
test_files: []
|