opal_stimulus 0.1.3 → 0.1.4
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/CHANGELOG.md +10 -0
- data/README.md +114 -0
- data/lib/opal_stimulus/stimulus_controller.rb +72 -92
- data/lib/opal_stimulus/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb159569bc40b9490b5433925d2b07e0a4cbd05d8c5f8c1ad86547efedd38361
|
4
|
+
data.tar.gz: 2d06de0ea3b604943301fd118de40c41b010378d7bf505878b0a99c1af51cffe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d543dccccdb4c2fa6c0a1422681ac5181547e4f28e00f0fa7085015f8abefeaa37a96839bf9001335bd1fb2ff90ed3a91404eb515fb0e907e969324824914e34
|
7
|
+
data.tar.gz: 0c8822447330dbeef2dd40bd43bba1110dd458e5269675a9f6c08ca96c4177a5b2caa77dd307c3ece0665f260008111a3bab31a52bbfdaf9588c565fdbec4cfc
|
data/CHANGELOG.md
CHANGED
@@ -16,3 +16,13 @@
|
|
16
16
|
|
17
17
|
- Add missing ignore for app/assets/builds/opal.js.map
|
18
18
|
- Replace opal-browser with opal_proxy, check it out here.
|
19
|
+
|
20
|
+
## [0.1.4] - 2025-08-03
|
21
|
+
|
22
|
+
Fix Targets
|
23
|
+
Fix Outlets
|
24
|
+
Fix Values
|
25
|
+
Fix `element`
|
26
|
+
Fix Classes
|
27
|
+
Updare readme
|
28
|
+
Add `document` and `window` private methods
|
data/README.md
CHANGED
@@ -61,9 +61,123 @@ end
|
|
61
61
|
|
62
62
|
https://github.com/user-attachments/assets/c51ed28c-13d2-4e06-b882-1cc997e9627b
|
63
63
|
|
64
|
+
**Notes:**
|
64
65
|
|
66
|
+
- In general any reference method is snake cased, so `container` target will produce `container_target` and not ~`containerTarget`~
|
67
|
+
- Any `target`, `element`, `document`, `window` and `event` is a `JS::Proxy` instance, that provides a dynamic interface to JavaScript objects in Opal
|
68
|
+
- The frontend definition part will remain the same
|
65
69
|
|
66
70
|
|
71
|
+
## Some examples based on [Stimulus Reference](https://stimulus.hotwired.dev/reference/controllers)
|
72
|
+
|
73
|
+
### Lifecycle Callbacks
|
74
|
+
```ruby
|
75
|
+
class AlertController < StimulusController
|
76
|
+
def initialize; end
|
77
|
+
def connect; end
|
78
|
+
def disconnect; end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### Actions
|
83
|
+
```ruby
|
84
|
+
class WindowResizeController < StimulusController
|
85
|
+
def resized(event)
|
86
|
+
if !@resized && event.target.inner_width >= 1080
|
87
|
+
puts "Full HD? Nice!"
|
88
|
+
@resized = true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Targets
|
95
|
+
```ruby
|
96
|
+
class ContainerController < StimulusController
|
97
|
+
self.targets = ["container"]
|
98
|
+
|
99
|
+
def container_target_connected
|
100
|
+
container_target.inner_html = <<~HTML
|
101
|
+
<h1>Test connected!</h1>
|
102
|
+
HTML
|
103
|
+
end
|
104
|
+
|
105
|
+
def container_target_disconnected
|
106
|
+
puts "Container disconnected!"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
### Outlets
|
112
|
+
```ruby
|
113
|
+
class ChatController < StimulusController
|
114
|
+
self.outlets = [ "user-status" ]
|
115
|
+
|
116
|
+
def connect
|
117
|
+
user_status_outlets.each do |status|
|
118
|
+
puts status
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Values
|
125
|
+
```ruby
|
126
|
+
class LoaderController < StimulusController
|
127
|
+
self.values = { url: :string }
|
128
|
+
|
129
|
+
def connect
|
130
|
+
window.fetch(url_value).then do |response|
|
131
|
+
response.json().then do |data|
|
132
|
+
load_data(data)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def load_data(data)
|
140
|
+
# ...
|
141
|
+
end
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
### CSS Classes
|
146
|
+
```ruby
|
147
|
+
class SearchController < StimulusController
|
148
|
+
self.classes = [ "loading" ]
|
149
|
+
|
150
|
+
def loadResults
|
151
|
+
element.class_list.add loading_class
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
## Extra tools
|
157
|
+
### Window
|
158
|
+
```ruby
|
159
|
+
class WindowController < StimulusController
|
160
|
+
def connect
|
161
|
+
window.alert "Hello world!"
|
162
|
+
window.set_timeout(-> {
|
163
|
+
puts "1. Timeout test OK (1s delay)"
|
164
|
+
}, 1000)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
### Document
|
170
|
+
```ruby
|
171
|
+
class DocumentController < StimulusController
|
172
|
+
def connect
|
173
|
+
headers = document.querySelectorAll("h1")
|
174
|
+
headers.each do |h1|
|
175
|
+
h1.text_content = "Opal is great!"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
```
|
180
|
+
|
67
181
|
## Contributing
|
68
182
|
|
69
183
|
Bug reports and pull requests are welcome on GitHub at https://github.com/josephschito/opal_stimulus.
|
@@ -30,7 +30,13 @@ class StimulusController < `Controller`
|
|
30
30
|
%x{
|
31
31
|
#{self.stimulus_controller}.prototype[name] = function (...args) {
|
32
32
|
try {
|
33
|
-
|
33
|
+
var wrappedArgs = args.map(function(arg) {
|
34
|
+
if (arg && typeof arg === "object" && !arg.$$class) {
|
35
|
+
return Opal.JS.Proxy.$new(arg);
|
36
|
+
}
|
37
|
+
return arg;
|
38
|
+
});
|
39
|
+
return this['$' + name].apply(this, wrappedArgs);
|
34
40
|
} catch (e) {
|
35
41
|
console.error("Uncaught", e);
|
36
42
|
}
|
@@ -42,22 +48,25 @@ class StimulusController < `Controller`
|
|
42
48
|
`#{self.stimulus_controller}.targets = targets`
|
43
49
|
|
44
50
|
targets.each do |target|
|
45
|
-
|
46
|
-
|
51
|
+
js_name = target.to_s
|
52
|
+
ruby_name = self.to_ruby_name(target)
|
53
|
+
|
54
|
+
define_method(ruby_name + "_target") do
|
55
|
+
JS::Proxy.new(`this[#{js_name + "Target"}]`)
|
47
56
|
end
|
48
57
|
|
49
|
-
define_method(
|
50
|
-
`this[#{
|
58
|
+
define_method(ruby_name + "_targets") do
|
59
|
+
`this[#{js_name + "Targets"}]`.map do |el|
|
51
60
|
JS::Proxy.new(el)
|
52
61
|
end
|
53
62
|
end
|
54
63
|
|
55
|
-
define_method("has_" +
|
56
|
-
`
|
64
|
+
define_method("has_" + ruby_name + "_target") do
|
65
|
+
`this[#{"has" + js_name.capitalize + "Target"}]`
|
57
66
|
end
|
58
67
|
|
59
|
-
snake_case_connected =
|
60
|
-
camel_case_connected =
|
68
|
+
snake_case_connected = ruby_name + "_target_connected"
|
69
|
+
camel_case_connected = js_name + "TargetConnected"
|
61
70
|
%x{
|
62
71
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
63
72
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -66,8 +75,8 @@ class StimulusController < `Controller`
|
|
66
75
|
}
|
67
76
|
}
|
68
77
|
|
69
|
-
snake_case_disconnected =
|
70
|
-
camel_case_disconnected =
|
78
|
+
snake_case_disconnected = ruby_name + "_target_disconnected"
|
79
|
+
camel_case_disconnected = js_name + "TargetDisconnected"
|
71
80
|
%x{
|
72
81
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
73
82
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -82,22 +91,23 @@ class StimulusController < `Controller`
|
|
82
91
|
`#{self.stimulus_controller}.outlets = outlets`
|
83
92
|
|
84
93
|
outlets.each do |outlet|
|
85
|
-
|
86
|
-
|
94
|
+
js_name = outlet.to_s
|
95
|
+
ruby_name = self.to_ruby_name(outlet)
|
96
|
+
|
97
|
+
define_method(ruby_name + "_outlet") do
|
98
|
+
`return this[#{js_name + "Outlet"}]`
|
87
99
|
end
|
88
100
|
|
89
|
-
define_method(
|
90
|
-
`this[#{
|
91
|
-
JS::Proxy.new(outlet)
|
92
|
-
end
|
101
|
+
define_method(ruby_name + "_outlets") do
|
102
|
+
`this[#{js_name + "Outlets"}]`
|
93
103
|
end
|
94
104
|
|
95
|
-
define_method("has_" +
|
96
|
-
`return this[#{"has" +
|
105
|
+
define_method("has_" + ruby_name + "_outlet") do
|
106
|
+
`return this[#{"has" + js_name.capitalize + "Outlet"}]`
|
97
107
|
end
|
98
108
|
|
99
|
-
snake_case_connected =
|
100
|
-
camel_case_connected =
|
109
|
+
snake_case_connected = ruby_name + "_outlet_connected"
|
110
|
+
camel_case_connected = js_name + "OutletConnected"
|
101
111
|
%x{
|
102
112
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
103
113
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -106,8 +116,8 @@ class StimulusController < `Controller`
|
|
106
116
|
}
|
107
117
|
}
|
108
118
|
|
109
|
-
snake_case_disconnected =
|
110
|
-
camel_case_disconnected =
|
119
|
+
snake_case_disconnected = ruby_name + "_outlet_disconnected"
|
120
|
+
camel_case_disconnected = js_name + "OutletDisconnected"
|
111
121
|
%x{
|
112
122
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
113
123
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -122,37 +132,29 @@ class StimulusController < `Controller`
|
|
122
132
|
js_values = {}
|
123
133
|
|
124
134
|
values_hash.each do |name, type|
|
135
|
+
name = self.to_ruby_name(name)
|
136
|
+
|
125
137
|
js_type = case type
|
126
|
-
when
|
127
|
-
when
|
128
|
-
when
|
129
|
-
when
|
130
|
-
when
|
131
|
-
else
|
138
|
+
when :string then `String`
|
139
|
+
when :number then `Number`
|
140
|
+
when :boolean then `Boolean`
|
141
|
+
when :array then `Array`
|
142
|
+
when :object then `Object`
|
143
|
+
else
|
144
|
+
raise ArgumentError,
|
145
|
+
"Unsupported value type: #{type}, please use :string, :number, :boolean, :array, or :object"
|
132
146
|
end
|
133
147
|
|
134
148
|
js_values[name] = js_type
|
135
149
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
js_value.to_i
|
145
|
-
when Float
|
146
|
-
js_value.to_f
|
147
|
-
when TrueClass, FalseClass, Boolean
|
148
|
-
!!js_value
|
149
|
-
when Array
|
150
|
-
Native::Array.new(js_value)
|
151
|
-
when Hash, Object
|
152
|
-
Native::Object.new(js_value)
|
153
|
-
else
|
154
|
-
js_value
|
155
|
-
end
|
150
|
+
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
151
|
+
|
152
|
+
define_method(name + "_value") do
|
153
|
+
`return this[#{name + "Value"}]`
|
154
|
+
end
|
155
|
+
|
156
|
+
define_method(name + "_value=") do |value|
|
157
|
+
`this[#{name + "Value"}]= #{value.to_n}`
|
156
158
|
end
|
157
159
|
|
158
160
|
define_method("has_#{name}") do
|
@@ -169,69 +171,47 @@ class StimulusController < `Controller`
|
|
169
171
|
}
|
170
172
|
}
|
171
173
|
end
|
172
|
-
|
173
|
-
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
174
174
|
end
|
175
175
|
|
176
176
|
def self.classes=(class_names = [])
|
177
|
-
`#{self.stimulus_controller}.classes =
|
177
|
+
`#{self.stimulus_controller}.classes = class_names`
|
178
178
|
|
179
179
|
class_names.each do |class_name|
|
180
|
-
|
181
|
-
|
182
|
-
end
|
180
|
+
js_name = class_name.to_s
|
181
|
+
ruby_name = self.to_ruby_name(class_name)
|
183
182
|
|
184
|
-
define_method("
|
185
|
-
`this
|
183
|
+
define_method("#{ruby_name}_class") do
|
184
|
+
`return this[#{js_name + "Class"}]`
|
186
185
|
end
|
187
186
|
|
188
|
-
define_method("
|
189
|
-
`return this
|
187
|
+
define_method("#{ruby_name}_classes") do
|
188
|
+
`return this[#{js_name + "Classes"}]`
|
190
189
|
end
|
191
190
|
|
192
|
-
define_method("
|
193
|
-
`this
|
191
|
+
define_method("has_#{ruby_name}_class") do
|
192
|
+
`return this[#{"has" + js_name.capitalize + "Class"}]`
|
194
193
|
end
|
195
194
|
end
|
196
195
|
end
|
197
196
|
|
198
|
-
def
|
199
|
-
|
200
|
-
`this.addClass(#{class_name}, #{element})`
|
201
|
-
else
|
202
|
-
`this.addClass(#{class_name})`
|
203
|
-
end
|
197
|
+
def element
|
198
|
+
JS::Proxy.new(`this.element`)
|
204
199
|
end
|
205
200
|
|
206
|
-
|
207
|
-
if element
|
208
|
-
`this.removeClass(#{class_name}, #{element})`
|
209
|
-
else
|
210
|
-
`this.removeClass(#{class_name})`
|
211
|
-
end
|
212
|
-
end
|
201
|
+
private
|
213
202
|
|
214
|
-
def
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
203
|
+
def self.to_ruby_name(name)
|
204
|
+
name
|
205
|
+
.to_s
|
206
|
+
.gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
|
207
|
+
.sub(/^_/, '')
|
220
208
|
end
|
221
209
|
|
222
|
-
def
|
223
|
-
|
224
|
-
`this.toggleClass(#{class_name}, #{force}, #{element})`
|
225
|
-
elsif element
|
226
|
-
`this.toggleClass(#{class_name}, #{element})`
|
227
|
-
elsif force != nil
|
228
|
-
`this.toggleClass(#{class_name}, #{force})`
|
229
|
-
else
|
230
|
-
`this.toggleClass(#{class_name})`
|
231
|
-
end
|
210
|
+
def window
|
211
|
+
@window ||= JS::Proxy.new($$.window)
|
232
212
|
end
|
233
213
|
|
234
|
-
def
|
235
|
-
|
214
|
+
def document
|
215
|
+
@document ||= JS::Proxy.new($$.document)
|
236
216
|
end
|
237
217
|
end
|