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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 123ac1d5e6112d3896cd623518b80732351357cfa11443dd8142fd07f32bec49
4
- data.tar.gz: 41bbd48c083fc12359f979f8c8c5d5de2486acd56cf969c0af417edf9576c1d0
3
+ metadata.gz: cb159569bc40b9490b5433925d2b07e0a4cbd05d8c5f8c1ad86547efedd38361
4
+ data.tar.gz: 2d06de0ea3b604943301fd118de40c41b010378d7bf505878b0a99c1af51cffe
5
5
  SHA512:
6
- metadata.gz: fa36ee6800f4e0795640c29c061be9b5c49cdc639c9b976381af2ea41863a40a22cac80368956edb1ca569712191f016764d4c4fa16ceb9583f5fca164b391f5
7
- data.tar.gz: 9cea24bad6cbf6bfba34ab79026b8589b6f25c180ee210f263568e275a91a1da16314539ecda837e222637657e4534a4d8a421ec8e38aa9c6a90b1c67cca1605
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
- return this['$' + name].apply(this, args);
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
- define_method(target + "_target") do
46
- JS::Proxy.new(`this[#{target + "Target"}]`)
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(target + "_targets") do
50
- `this[#{target + "Targets"}]`.map do |el|
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_" + target + "_target") do
56
- `return this[#{"has" + target.capitalize + "Target"}]`
64
+ define_method("has_" + ruby_name + "_target") do
65
+ `this[#{"has" + js_name.capitalize + "Target"}]`
57
66
  end
58
67
 
59
- snake_case_connected = target + "_target_connected"
60
- camel_case_connected = target + "TargetConnected"
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 = target + "_target_disconnected"
70
- camel_case_disconnected = target + "TargetDisconnected"
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
- define_method(outlet + "_outlet") do
86
- JS::Proxy.new(`this[#{outlet + "Outlet"}]`)
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(outlet + "_outlets") do
90
- `this[#{outlet + "Outlets"}]`.map do |outlet|
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_" + outlet + "_outlet") do
96
- `return this[#{"has" + outlet.capitalize + "Outlet"}]`
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 = outlet + "_outlet_connected"
100
- camel_case_connected = outlet + "OutletConnected"
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 = outlet + "_outlet_disconnected"
110
- camel_case_disconnected = outlet + "OutletDisconnected"
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 String then "String"
127
- when Integer, Float, Numeric then "Number"
128
- when TrueClass, FalseClass, Boolean then "Boolean"
129
- when Array then "Array"
130
- when Hash, Object then "Object"
131
- else "String" # Default to String for unknown types
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
- # Define value getter method (snake_case)
137
- define_method(name.to_s) do
138
- # Convert JavaScript value to appropriate Ruby type
139
- js_value = `this[#{name + "Value"}]`
140
- case type
141
- when String
142
- js_value.to_s
143
- when Integer
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 = #{class_names.to_n}`
177
+ `#{self.stimulus_controller}.classes = class_names`
178
178
 
179
179
  class_names.each do |class_name|
180
- define_method("add_#{class_name}_class") do
181
- `this.#{class_name}Classes.add()`
182
- end
180
+ js_name = class_name.to_s
181
+ ruby_name = self.to_ruby_name(class_name)
183
182
 
184
- define_method("remove_#{class_name}_class") do
185
- `this.#{class_name}Classes.remove()`
183
+ define_method("#{ruby_name}_class") do
184
+ `return this[#{js_name + "Class"}]`
186
185
  end
187
186
 
188
- define_method("has_#{class_name}_class?") do
189
- `return this.#{class_name}Classes.has()`
187
+ define_method("#{ruby_name}_classes") do
188
+ `return this[#{js_name + "Classes"}]`
190
189
  end
191
190
 
192
- define_method("toggle_#{class_name}_class") do
193
- `this.#{class_name}Classes.toggle()`
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 add_class(class_name, element = nil)
199
- if element
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
- def remove_class(class_name, element = nil)
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 has_class?(class_name, element = nil)
215
- if element
216
- `return this.hasClass(#{class_name}, #{element})`
217
- else
218
- `return this.hasClass(#{class_name})`
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 toggle_class(class_name, force = nil, element = nil)
223
- if element && force != nil
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 element
235
- `this.element`
214
+ def document
215
+ @document ||= JS::Proxy.new($$.document)
236
216
  end
237
217
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpalStimulus
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  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.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Schito