opal_stimulus 0.1.2 → 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 +15 -0
- data/README.md +116 -2
- data/lib/install/install_opal_stimulus.rb +1 -0
- data/lib/install/opal +4 -3
- data/lib/opal_stimulus/stimulus_controller.rb +74 -95
- data/lib/opal_stimulus/version.rb +1 -1
- metadata +4 -4
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
@@ -11,3 +11,18 @@
|
|
11
11
|
## [0.1.2] - 2025-07-29
|
12
12
|
|
13
13
|
- Implement Opal Source maps https://opalrb.com/docs/guides/v1.4.1/source_maps.html
|
14
|
+
|
15
|
+
## [0.1.3] - 2025-07-29
|
16
|
+
|
17
|
+
- Add missing ignore for app/assets/builds/opal.js.map
|
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
@@ -25,7 +25,7 @@ bin/dev
|
|
25
25
|
|
26
26
|
## Basic Example
|
27
27
|
|
28
|
-
Here's a Hello World example with OpalStimulus. Compare with the [original JavaScript example](https://stimulus.hotwired.dev
|
28
|
+
Here's a Hello World example with OpalStimulus. Compare with the [original JavaScript example](https://stimulus.hotwired.dev/#:~:text=%2F%2F%20hello_controller.js%0Aimport%20%7B%20Controller%20%7D%20from%20%22stimulus%22%0A%0Aexport%20default%20class%20extends%20Controller%20%7B%0A%20%20static%20targets%20%3D%20%5B%20%22name%22%2C%20%22output%22%20%5D%0A%0A%20%20greet()%20%7B%0A%20%20%20%20this.outputTarget.textContent%20%3D%0A%20%20%20%20%20%20%60Hello%2C%20%24%7Bthis.nameTarget.value%7D!%60%0A%20%20%7D%0A%7D):
|
29
29
|
|
30
30
|
**Ruby Controller:**
|
31
31
|
|
@@ -37,7 +37,7 @@ class HelloController < StimulusController
|
|
37
37
|
self.targets = ["name", "output"]
|
38
38
|
|
39
39
|
def greet
|
40
|
-
output_target.
|
40
|
+
output_target.text_content = "Hello, #{name_target.value}!"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
```
|
@@ -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.
|
@@ -40,6 +40,7 @@ window.Controller = Controller;
|
|
40
40
|
end
|
41
41
|
append_to_file ".gitignore", "/.opal-cache\n"
|
42
42
|
append_to_file ".gitignore", "app/assets/builds/opal.js\n"
|
43
|
+
append_to_file ".gitignore", "app/assets/builds/opal.js.map\n"
|
43
44
|
empty_directory APPLICATION_OPAL_STIMULUS_BIN_PATH
|
44
45
|
empty_directory APPLICATION_OPAL_STIMULUS_PATH
|
45
46
|
empty_directory "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers"
|
data/lib/install/opal
CHANGED
@@ -4,11 +4,12 @@
|
|
4
4
|
require "bundler/setup"
|
5
5
|
require "listen"
|
6
6
|
require "opal"
|
7
|
-
require "opal-browser"
|
8
7
|
|
9
|
-
|
8
|
+
GEMS = ["opal_proxy", "opal_stimulus"]
|
10
9
|
|
11
|
-
|
10
|
+
GEMS.each do |gem_name|
|
11
|
+
Opal.use_gem(gem_name) rescue Opal.append_path(File.expand_path("lib", Bundler.rubygems.find_name(gem_name).first.full_gem_path))
|
12
|
+
end
|
12
13
|
|
13
14
|
Opal.append_path("app/opal")
|
14
15
|
|
@@ -2,8 +2,7 @@
|
|
2
2
|
|
3
3
|
require "opal"
|
4
4
|
require "native"
|
5
|
-
require "
|
6
|
-
require "browser/setup/full"
|
5
|
+
require "js/proxy"
|
7
6
|
|
8
7
|
class StimulusController < `Controller`
|
9
8
|
include Native::Wrapper
|
@@ -31,7 +30,13 @@ class StimulusController < `Controller`
|
|
31
30
|
%x{
|
32
31
|
#{self.stimulus_controller}.prototype[name] = function (...args) {
|
33
32
|
try {
|
34
|
-
|
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);
|
35
40
|
} catch (e) {
|
36
41
|
console.error("Uncaught", e);
|
37
42
|
}
|
@@ -43,22 +48,25 @@ class StimulusController < `Controller`
|
|
43
48
|
`#{self.stimulus_controller}.targets = targets`
|
44
49
|
|
45
50
|
targets.each do |target|
|
46
|
-
|
47
|
-
|
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"}]`)
|
48
56
|
end
|
49
57
|
|
50
|
-
define_method(
|
51
|
-
`this[#{
|
52
|
-
|
58
|
+
define_method(ruby_name + "_targets") do
|
59
|
+
`this[#{js_name + "Targets"}]`.map do |el|
|
60
|
+
JS::Proxy.new(el)
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
56
|
-
define_method("has_" +
|
57
|
-
`
|
64
|
+
define_method("has_" + ruby_name + "_target") do
|
65
|
+
`this[#{"has" + js_name.capitalize + "Target"}]`
|
58
66
|
end
|
59
67
|
|
60
|
-
snake_case_connected =
|
61
|
-
camel_case_connected =
|
68
|
+
snake_case_connected = ruby_name + "_target_connected"
|
69
|
+
camel_case_connected = js_name + "TargetConnected"
|
62
70
|
%x{
|
63
71
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
64
72
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -67,8 +75,8 @@ class StimulusController < `Controller`
|
|
67
75
|
}
|
68
76
|
}
|
69
77
|
|
70
|
-
snake_case_disconnected =
|
71
|
-
camel_case_disconnected =
|
78
|
+
snake_case_disconnected = ruby_name + "_target_disconnected"
|
79
|
+
camel_case_disconnected = js_name + "TargetDisconnected"
|
72
80
|
%x{
|
73
81
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
74
82
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -83,22 +91,23 @@ class StimulusController < `Controller`
|
|
83
91
|
`#{self.stimulus_controller}.outlets = outlets`
|
84
92
|
|
85
93
|
outlets.each do |outlet|
|
86
|
-
|
87
|
-
|
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"}]`
|
88
99
|
end
|
89
100
|
|
90
|
-
define_method(
|
91
|
-
`this[#{
|
92
|
-
Browser::DOM::Element.new(outlet)
|
93
|
-
end
|
101
|
+
define_method(ruby_name + "_outlets") do
|
102
|
+
`this[#{js_name + "Outlets"}]`
|
94
103
|
end
|
95
104
|
|
96
|
-
define_method("has_" +
|
97
|
-
`return this[#{"has" +
|
105
|
+
define_method("has_" + ruby_name + "_outlet") do
|
106
|
+
`return this[#{"has" + js_name.capitalize + "Outlet"}]`
|
98
107
|
end
|
99
108
|
|
100
|
-
snake_case_connected =
|
101
|
-
camel_case_connected =
|
109
|
+
snake_case_connected = ruby_name + "_outlet_connected"
|
110
|
+
camel_case_connected = js_name + "OutletConnected"
|
102
111
|
%x{
|
103
112
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
104
113
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -107,8 +116,8 @@ class StimulusController < `Controller`
|
|
107
116
|
}
|
108
117
|
}
|
109
118
|
|
110
|
-
snake_case_disconnected =
|
111
|
-
camel_case_disconnected =
|
119
|
+
snake_case_disconnected = ruby_name + "_outlet_disconnected"
|
120
|
+
camel_case_disconnected = js_name + "OutletDisconnected"
|
112
121
|
%x{
|
113
122
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
114
123
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -123,37 +132,29 @@ class StimulusController < `Controller`
|
|
123
132
|
js_values = {}
|
124
133
|
|
125
134
|
values_hash.each do |name, type|
|
135
|
+
name = self.to_ruby_name(name)
|
136
|
+
|
126
137
|
js_type = case type
|
127
|
-
when
|
128
|
-
when
|
129
|
-
when
|
130
|
-
when
|
131
|
-
when
|
132
|
-
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"
|
133
146
|
end
|
134
147
|
|
135
148
|
js_values[name] = js_type
|
136
149
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
js_value.to_i
|
146
|
-
when Float
|
147
|
-
js_value.to_f
|
148
|
-
when TrueClass, FalseClass, Boolean
|
149
|
-
!!js_value
|
150
|
-
when Array
|
151
|
-
Native::Array.new(js_value)
|
152
|
-
when Hash, Object
|
153
|
-
Native::Object.new(js_value)
|
154
|
-
else
|
155
|
-
js_value
|
156
|
-
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}`
|
157
158
|
end
|
158
159
|
|
159
160
|
define_method("has_#{name}") do
|
@@ -170,69 +171,47 @@ class StimulusController < `Controller`
|
|
170
171
|
}
|
171
172
|
}
|
172
173
|
end
|
173
|
-
|
174
|
-
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
175
174
|
end
|
176
175
|
|
177
176
|
def self.classes=(class_names = [])
|
178
|
-
`#{self.stimulus_controller}.classes =
|
177
|
+
`#{self.stimulus_controller}.classes = class_names`
|
179
178
|
|
180
179
|
class_names.each do |class_name|
|
181
|
-
|
182
|
-
|
183
|
-
end
|
180
|
+
js_name = class_name.to_s
|
181
|
+
ruby_name = self.to_ruby_name(class_name)
|
184
182
|
|
185
|
-
define_method("
|
186
|
-
`this
|
183
|
+
define_method("#{ruby_name}_class") do
|
184
|
+
`return this[#{js_name + "Class"}]`
|
187
185
|
end
|
188
186
|
|
189
|
-
define_method("
|
190
|
-
`return this
|
187
|
+
define_method("#{ruby_name}_classes") do
|
188
|
+
`return this[#{js_name + "Classes"}]`
|
191
189
|
end
|
192
190
|
|
193
|
-
define_method("
|
194
|
-
`this
|
191
|
+
define_method("has_#{ruby_name}_class") do
|
192
|
+
`return this[#{"has" + js_name.capitalize + "Class"}]`
|
195
193
|
end
|
196
194
|
end
|
197
195
|
end
|
198
196
|
|
199
|
-
def
|
200
|
-
|
201
|
-
`this.addClass(#{class_name}, #{element})`
|
202
|
-
else
|
203
|
-
`this.addClass(#{class_name})`
|
204
|
-
end
|
197
|
+
def element
|
198
|
+
JS::Proxy.new(`this.element`)
|
205
199
|
end
|
206
200
|
|
207
|
-
|
208
|
-
if element
|
209
|
-
`this.removeClass(#{class_name}, #{element})`
|
210
|
-
else
|
211
|
-
`this.removeClass(#{class_name})`
|
212
|
-
end
|
213
|
-
end
|
201
|
+
private
|
214
202
|
|
215
|
-
def
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
end
|
203
|
+
def self.to_ruby_name(name)
|
204
|
+
name
|
205
|
+
.to_s
|
206
|
+
.gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
|
207
|
+
.sub(/^_/, '')
|
221
208
|
end
|
222
209
|
|
223
|
-
def
|
224
|
-
|
225
|
-
`this.toggleClass(#{class_name}, #{force}, #{element})`
|
226
|
-
elsif element
|
227
|
-
`this.toggleClass(#{class_name}, #{element})`
|
228
|
-
elsif force != nil
|
229
|
-
`this.toggleClass(#{class_name}, #{force})`
|
230
|
-
else
|
231
|
-
`this.toggleClass(#{class_name})`
|
232
|
-
end
|
210
|
+
def window
|
211
|
+
@window ||= JS::Proxy.new($$.window)
|
233
212
|
end
|
234
213
|
|
235
|
-
def
|
236
|
-
|
214
|
+
def document
|
215
|
+
@document ||= JS::Proxy.new($$.document)
|
237
216
|
end
|
238
217
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opal_stimulus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joseph Schito
|
@@ -38,19 +38,19 @@ dependencies:
|
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: 3.9.0
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name:
|
41
|
+
name: opal_proxy
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0.
|
46
|
+
version: 0.1.0
|
47
47
|
type: :runtime
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.
|
53
|
+
version: 0.1.0
|
54
54
|
description: Opal Stimulus provides a way to write Stimulus controllers in Ruby, leveraging
|
55
55
|
the Opal compiler to convert Ruby code into JavaScript. This allows developers familiar
|
56
56
|
with Ruby to use the Stimulus framework without needing to write JavaScript directly.
|