opal_stimulus 0.1.3 → 0.1.5
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/.rspec-opal +2 -0
- data/CHANGELOG.md +16 -0
- data/README.md +126 -2
- data/Rakefile +12 -1
- data/lib/generators/USAGE +14 -0
- data/lib/generators/opal_stimulus_generator.rb +9 -0
- data/lib/generators/templates/controller.rb.tt +14 -0
- data/lib/install/Procfile.dev +1 -1
- data/lib/install/application.rb +2 -7
- data/lib/install/install_opal_stimulus.rb +2 -13
- data/lib/opal_stimulus/stimulus_controller.rb +86 -97
- data/lib/opal_stimulus/version.rb +1 -1
- data/lib/tasks/build.rake +53 -0
- data/lib/tasks/install.rake +1 -1
- data/shared_fixtures/stimulus@3.2.2.umd.js +2588 -0
- data/spec-opal/spec_helper.rb +24 -0
- data/spec-opal/stimulus_controller_spec.rb +215 -0
- metadata +11 -6
- data/lib/install/controllers/my_opal_controller.rb +0 -5
- data/lib/install/controllers_requires.rb +0 -4
- data/lib/install/opal +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f328bd466f89d10419369095e006b923c4a389c58fa14abd7c57ae2c57fb804d
|
4
|
+
data.tar.gz: 7b6a16a7adc502cdedcdad7e0e71f152652022a42d5a674fc033f6f13e514660
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 775c06738928c9c1584d0515064cf7e5984ff1259cb7def3c56cd9bc34942a7da50a39a761400d9f80a824a178d3333887fedfcfec4274adfa261a3819614d59
|
7
|
+
data.tar.gz: f3da082b6361dc0281e743a7ded76c953504c044abe2fc5b59febbd2d68e65138bb4eea4ef2acaab08f8a3a3073c655c3e325c0084f15bce58bd8d23f30d0b2e
|
data/.rspec-opal
ADDED
data/CHANGELOG.md
CHANGED
@@ -16,3 +16,19 @@
|
|
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
|
+
- Add `document` and `window` private methods
|
28
|
+
|
29
|
+
## [0.1.5] - 2025-08-14
|
30
|
+
|
31
|
+
- Add `StimulusController` unit and system tests
|
32
|
+
- Reduce installed new lines for Rails
|
33
|
+
- Skip importmap in production
|
34
|
+
- Add `bin/rails generate opal_stimulus <CONTROLLER_NAME>`, usage: `bin/rails g opal_stimulus -h`
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
**Opal Stimulus** is a Stimulus wrapper made with Opal (a source-to-source Ruby to JavaScript compiler) that allows you to write Stimulus controllers in Ruby instead of JavaScript (It works only with Rails).
|
4
4
|
|
5
|
+
[](https://github.com/josephschito/opal_stimulus/actions/workflows/main.yml)
|
6
|
+
|
5
7
|
## Installation
|
6
8
|
|
7
9
|
Add this line to your Gemfile:
|
@@ -23,6 +25,13 @@ Start application:
|
|
23
25
|
bin/dev
|
24
26
|
```
|
25
27
|
|
28
|
+
Create a new controller:
|
29
|
+
|
30
|
+
```bash
|
31
|
+
bin/rails opal_stimulus hello
|
32
|
+
```
|
33
|
+
This command will create `app/opal/controllers/hello_controller.rb`
|
34
|
+
|
26
35
|
## Basic Example
|
27
36
|
|
28
37
|
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):
|
@@ -31,8 +40,6 @@ Here's a Hello World example with OpalStimulus. Compare with the [original JavaS
|
|
31
40
|
|
32
41
|
```ruby
|
33
42
|
# app/opal/controllers/hello_controller.rb
|
34
|
-
# new controllers will be automatically added to app/opal/controllers_requires.rb
|
35
|
-
# (ordered files load is not supported yet)
|
36
43
|
class HelloController < StimulusController
|
37
44
|
self.targets = ["name", "output"]
|
38
45
|
|
@@ -61,8 +68,125 @@ end
|
|
61
68
|
|
62
69
|
https://github.com/user-attachments/assets/c51ed28c-13d2-4e06-b882-1cc997e9627b
|
63
70
|
|
71
|
+
**Notes:**
|
72
|
+
|
73
|
+
- In general any reference method is snake cased, so `container` target will produce `container_target` and not ~`containerTarget`~
|
74
|
+
- Any `target`, `element`, `document`, `window` and `event` is a `JS::Proxy` instance, that provides a dynamic interface to JavaScript objects in Opal
|
75
|
+
- The frontend definition part will remain the same
|
76
|
+
|
77
|
+
|
78
|
+
## Some examples based on [Stimulus Reference](https://stimulus.hotwired.dev/reference/controllers)
|
79
|
+
|
80
|
+
### Lifecycle Callbacks
|
81
|
+
```ruby
|
82
|
+
class AlertController < StimulusController
|
83
|
+
def initialize; end
|
84
|
+
def connect; end
|
85
|
+
def disconnect; end
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
### Actions
|
90
|
+
```ruby
|
91
|
+
class WindowResizeController < StimulusController
|
92
|
+
def resized(event)
|
93
|
+
if !@resized && event.target.inner_width >= 1080
|
94
|
+
puts "Full HD? Nice!"
|
95
|
+
@resized = true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### Targets
|
102
|
+
```ruby
|
103
|
+
class ContainerController < StimulusController
|
104
|
+
self.targets = ["container"]
|
105
|
+
|
106
|
+
def container_target_connected
|
107
|
+
container_target.inner_html = <<~HTML
|
108
|
+
<h1>Test connected!</h1>
|
109
|
+
HTML
|
110
|
+
end
|
111
|
+
|
112
|
+
def container_target_disconnected
|
113
|
+
puts "Container disconnected!"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
### Outlets
|
119
|
+
```ruby
|
120
|
+
class ChatController < StimulusController
|
121
|
+
self.outlets = [ "user-status" ]
|
122
|
+
|
123
|
+
def connect
|
124
|
+
user_status_outlets.each do |status|
|
125
|
+
puts status
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
### Values
|
132
|
+
```ruby
|
133
|
+
class LoaderController < StimulusController
|
134
|
+
self.values = { url: :string }
|
135
|
+
|
136
|
+
def connect
|
137
|
+
window.fetch(url_value).then do |response|
|
138
|
+
response.json().then do |data|
|
139
|
+
load_data(data)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def load_data(data)
|
147
|
+
# ...
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### CSS Classes
|
153
|
+
```ruby
|
154
|
+
class SearchController < StimulusController
|
155
|
+
self.classes = [ "loading" ]
|
64
156
|
|
157
|
+
def loadResults
|
158
|
+
element.class_list.add loading_class
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
## Extra tools
|
164
|
+
### Window
|
165
|
+
```ruby
|
166
|
+
class WindowController < StimulusController
|
167
|
+
def connect
|
168
|
+
window.alert "Hello world!"
|
169
|
+
window.set_timeout(-> {
|
170
|
+
puts "1. Timeout test OK (1s delay)"
|
171
|
+
}, 1000)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
### Document
|
177
|
+
```ruby
|
178
|
+
class DocumentController < StimulusController
|
179
|
+
def connect
|
180
|
+
headers = document.querySelectorAll("h1")
|
181
|
+
headers.each do |h1|
|
182
|
+
h1.text_content = "Opal is great!"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
```
|
65
187
|
|
188
|
+
## Run tests
|
189
|
+
`bundle exec rake`
|
66
190
|
|
67
191
|
## Contributing
|
68
192
|
|
data/Rakefile
CHANGED
@@ -9,4 +9,15 @@ require "rubocop/rake_task"
|
|
9
9
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
|
-
|
12
|
+
require 'opal/rspec/rake_task'
|
13
|
+
require "opal"
|
14
|
+
|
15
|
+
Opal.use_gem("opal_proxy")
|
16
|
+
Opal.append_path File.expand_path('../lib', __FILE__)
|
17
|
+
Opal.append_path File.expand_path('../shared_fixtures', __FILE__)
|
18
|
+
|
19
|
+
Opal::RSpec::RakeTask.new("rspec-opal") do |server, task|
|
20
|
+
task.runner = :chrome
|
21
|
+
end
|
22
|
+
|
23
|
+
task default: %i[test rspec-opal]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Description:
|
2
|
+
============
|
3
|
+
Generates a new Opal Stimulus controller at the passed path.
|
4
|
+
|
5
|
+
Examples:
|
6
|
+
=========
|
7
|
+
bin/rails generate opal_stimulus chat
|
8
|
+
|
9
|
+
creates: app/opal/controllers/chat_controller.rb
|
10
|
+
|
11
|
+
|
12
|
+
bin/rails generate stimulus nested/chat
|
13
|
+
|
14
|
+
creates: app/opal/controllers/nested/chat_controller.rb
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "rails/generators/named_base"
|
2
|
+
|
3
|
+
class OpalStimulusGenerator < Rails::Generators::NamedBase
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
5
|
+
|
6
|
+
def create_controller_file
|
7
|
+
template "controller.rb.tt", File.join("app/opal/controllers", class_path, "#{file_name}_controller.rb")
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Connects to data-controller="<%= file_name.dasherize %>"
|
2
|
+
<% constants = class_name.split("::") -%>
|
3
|
+
<% modules = constants[0..-2] -%>
|
4
|
+
<% indentation = " " * (modules.length) -%>
|
5
|
+
<% modules.each do |constant| -%>
|
6
|
+
module <%= constant %>
|
7
|
+
<% end -%>
|
8
|
+
<%= indentation %>class <%= constants.last %>Controller < StimulusController
|
9
|
+
<%= indentation %> def connect
|
10
|
+
<%= indentation %> end
|
11
|
+
<%= indentation %>end
|
12
|
+
<% modules.each.with_index do |constant, i| -%>
|
13
|
+
<%= " " * i %>end
|
14
|
+
<% end -%>
|
data/lib/install/Procfile.dev
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
web: bin/rails server
|
2
|
-
opal: bin/
|
2
|
+
opal: bin/rails opal_stimulus:watch
|
data/lib/install/application.rb
CHANGED
@@ -1,9 +1,4 @@
|
|
1
1
|
require "opal_stimulus/stimulus_controller"
|
2
|
-
|
2
|
+
require_tree "controllers"
|
3
3
|
|
4
|
-
StimulusController.
|
5
|
-
controller.define_method(:dummy) { }
|
6
|
-
|
7
|
-
return if `Stimulus.controllers`.include?(`#{controller.stimulus_name}`)
|
8
|
-
`Stimulus.register(#{controller.stimulus_name}, #{controller.stimulus_controller})`
|
9
|
-
end
|
4
|
+
StimulusController.register_all!
|
@@ -13,16 +13,9 @@ if File.exist? APPLICATION_LAYOUT_PATH
|
|
13
13
|
ERB
|
14
14
|
end
|
15
15
|
|
16
|
-
insert_into_file APPLICATION_LAYOUT_PATH, after: "<body>\n" do
|
17
|
-
say "Adding `my-opal` to the application layout", :green
|
18
|
-
<<-ERB
|
19
|
-
<span data-controller="my-opal"></span>
|
20
|
-
ERB
|
21
|
-
end
|
22
|
-
|
23
16
|
say "Creating Opal Stimulus files", :green
|
24
17
|
if Rails.root.join("Procfile.dev").exist?
|
25
|
-
append_to_file "Procfile.dev", "opal: bin/
|
18
|
+
append_to_file "Procfile.dev", "opal: bin/rails opal_stimulus:watch\n"
|
26
19
|
else
|
27
20
|
say "Add default Procfile.dev"
|
28
21
|
copy_file "#{__dir__}/Procfile.dev", "Procfile.dev"
|
@@ -44,13 +37,9 @@ window.Controller = Controller;
|
|
44
37
|
empty_directory APPLICATION_OPAL_STIMULUS_BIN_PATH
|
45
38
|
empty_directory APPLICATION_OPAL_STIMULUS_PATH
|
46
39
|
empty_directory "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers"
|
47
|
-
|
40
|
+
create_file "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers/.keep"
|
48
41
|
create_file "app/assets/builds/.keep"
|
49
|
-
copy_file "#{__dir__}/opal", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/opal"
|
50
|
-
FileUtils.chmod("+x", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/opal")
|
51
42
|
copy_file "#{__dir__}/dev", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/dev"
|
52
43
|
FileUtils.chmod("+x", "#{APPLICATION_OPAL_STIMULUS_BIN_PATH}/dev")
|
53
44
|
copy_file "#{__dir__}/application.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/application.rb"
|
54
|
-
copy_file "#{__dir__}/controllers_requires.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers_requires.rb"
|
55
|
-
copy_file "#{__dir__}/controllers/my_opal_controller.rb", "#{APPLICATION_OPAL_STIMULUS_PATH}/controllers/my_opal_controller.rb"
|
56
45
|
end
|
@@ -5,8 +5,6 @@ require "native"
|
|
5
5
|
require "js/proxy"
|
6
6
|
|
7
7
|
class StimulusController < `Controller`
|
8
|
-
include Native::Wrapper
|
9
|
-
|
10
8
|
DEFAULT_METHODS = %i[initialize connect disconnect dispatch]
|
11
9
|
DEFAULT_GETTERS = %i[element]
|
12
10
|
|
@@ -30,7 +28,13 @@ class StimulusController < `Controller`
|
|
30
28
|
%x{
|
31
29
|
#{self.stimulus_controller}.prototype[name] = function (...args) {
|
32
30
|
try {
|
33
|
-
|
31
|
+
var wrappedArgs = args.map(function(arg) {
|
32
|
+
if (arg && typeof arg === "object" && !arg.$$class) {
|
33
|
+
return Opal.JS.Proxy.$new(arg);
|
34
|
+
}
|
35
|
+
return arg;
|
36
|
+
});
|
37
|
+
return this['$' + name].apply(this, wrappedArgs);
|
34
38
|
} catch (e) {
|
35
39
|
console.error("Uncaught", e);
|
36
40
|
}
|
@@ -38,26 +42,45 @@ class StimulusController < `Controller`
|
|
38
42
|
}
|
39
43
|
end
|
40
44
|
|
45
|
+
def self.to_ruby_name(name)
|
46
|
+
name
|
47
|
+
.to_s
|
48
|
+
.gsub(/([A-Z]+)/) { "_#{$1.downcase}" }
|
49
|
+
.sub(/^_/, '')
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.register_all!
|
53
|
+
subclasses.each do |controller|
|
54
|
+
controller.define_method(:dummy) {}
|
55
|
+
|
56
|
+
return if `application.controllers`.include?(`#{controller.stimulus_name}`)
|
57
|
+
`application.register(#{controller.stimulus_name}, #{controller.stimulus_controller})`
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
41
61
|
def self.targets=(targets = [])
|
42
62
|
`#{self.stimulus_controller}.targets = targets`
|
43
63
|
|
44
64
|
targets.each do |target|
|
45
|
-
|
46
|
-
|
65
|
+
js_name = target.to_s
|
66
|
+
ruby_name = self.to_ruby_name(target)
|
67
|
+
|
68
|
+
define_method(ruby_name + "_target") do
|
69
|
+
JS::Proxy.new(`this[#{js_name + "Target"}]`)
|
47
70
|
end
|
48
71
|
|
49
|
-
define_method(
|
50
|
-
`this[#{
|
72
|
+
define_method(ruby_name + "_targets") do
|
73
|
+
`this[#{js_name + "Targets"}]`.map do |el|
|
51
74
|
JS::Proxy.new(el)
|
52
75
|
end
|
53
76
|
end
|
54
77
|
|
55
|
-
define_method("has_" +
|
56
|
-
`
|
78
|
+
define_method("has_" + ruby_name + "_target") do
|
79
|
+
`this[#{"has" + js_name.capitalize + "Target"}]`
|
57
80
|
end
|
58
81
|
|
59
|
-
snake_case_connected =
|
60
|
-
camel_case_connected =
|
82
|
+
snake_case_connected = ruby_name + "_target_connected"
|
83
|
+
camel_case_connected = js_name + "TargetConnected"
|
61
84
|
%x{
|
62
85
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
63
86
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -66,8 +89,8 @@ class StimulusController < `Controller`
|
|
66
89
|
}
|
67
90
|
}
|
68
91
|
|
69
|
-
snake_case_disconnected =
|
70
|
-
camel_case_disconnected =
|
92
|
+
snake_case_disconnected = ruby_name + "_target_disconnected"
|
93
|
+
camel_case_disconnected = js_name + "TargetDisconnected"
|
71
94
|
%x{
|
72
95
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
73
96
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -82,22 +105,23 @@ class StimulusController < `Controller`
|
|
82
105
|
`#{self.stimulus_controller}.outlets = outlets`
|
83
106
|
|
84
107
|
outlets.each do |outlet|
|
85
|
-
|
86
|
-
|
108
|
+
js_name = outlet.to_s
|
109
|
+
ruby_name = self.to_ruby_name(outlet)
|
110
|
+
|
111
|
+
define_method(ruby_name + "_outlet") do
|
112
|
+
`return this[#{js_name + "Outlet"}]`
|
87
113
|
end
|
88
114
|
|
89
|
-
define_method(
|
90
|
-
`this[#{
|
91
|
-
JS::Proxy.new(outlet)
|
92
|
-
end
|
115
|
+
define_method(ruby_name + "_outlets") do
|
116
|
+
`this[#{js_name + "Outlets"}]`
|
93
117
|
end
|
94
118
|
|
95
|
-
define_method("has_" +
|
96
|
-
`return this[#{"has" +
|
119
|
+
define_method("has_" + ruby_name + "_outlet") do
|
120
|
+
`return this[#{"has" + js_name.capitalize + "Outlet"}]`
|
97
121
|
end
|
98
122
|
|
99
|
-
snake_case_connected =
|
100
|
-
camel_case_connected =
|
123
|
+
snake_case_connected = ruby_name + "_outlet_connected"
|
124
|
+
camel_case_connected = js_name + "OutletConnected"
|
101
125
|
%x{
|
102
126
|
#{self.stimulus_controller}.prototype[#{camel_case_connected}] = function() {
|
103
127
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_connected})) {
|
@@ -106,8 +130,8 @@ class StimulusController < `Controller`
|
|
106
130
|
}
|
107
131
|
}
|
108
132
|
|
109
|
-
snake_case_disconnected =
|
110
|
-
camel_case_disconnected =
|
133
|
+
snake_case_disconnected = ruby_name + "_outlet_disconnected"
|
134
|
+
camel_case_disconnected = js_name + "OutletDisconnected"
|
111
135
|
%x{
|
112
136
|
#{self.stimulus_controller}.prototype[#{camel_case_disconnected}] = function() {
|
113
137
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_disconnected})) {
|
@@ -122,37 +146,29 @@ class StimulusController < `Controller`
|
|
122
146
|
js_values = {}
|
123
147
|
|
124
148
|
values_hash.each do |name, type|
|
149
|
+
name = self.to_ruby_name(name)
|
150
|
+
|
125
151
|
js_type = case type
|
126
|
-
when
|
127
|
-
when
|
128
|
-
when
|
129
|
-
when
|
130
|
-
when
|
131
|
-
else
|
152
|
+
when :string then `String`
|
153
|
+
when :number then `Number`
|
154
|
+
when :boolean then `Boolean`
|
155
|
+
when :array then `Array`
|
156
|
+
when :object then `Object`
|
157
|
+
else
|
158
|
+
raise ArgumentError,
|
159
|
+
"Unsupported value type: #{type}, please use :string, :number, :boolean, :array, or :object"
|
132
160
|
end
|
133
161
|
|
134
162
|
js_values[name] = js_type
|
135
163
|
|
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
|
164
|
+
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
165
|
+
|
166
|
+
define_method(name + "_value") do
|
167
|
+
Native(`this[#{name + "Value"}]`)
|
168
|
+
end
|
169
|
+
|
170
|
+
define_method(name + "_value=") do |value|
|
171
|
+
Native(`this[#{name + "Value"}]= #{value}`)
|
156
172
|
end
|
157
173
|
|
158
174
|
define_method("has_#{name}") do
|
@@ -163,75 +179,48 @@ class StimulusController < `Controller`
|
|
163
179
|
camel_case_changed = "#{name}ValueChanged"
|
164
180
|
%x{
|
165
181
|
#{self.stimulus_controller}.prototype[#{camel_case_changed}] = function(value, previousValue) {
|
182
|
+
if (#{type == :object}) {
|
183
|
+
value = JSON.stringify(value)
|
184
|
+
previousValue = JSON.stringify(previousValue)
|
185
|
+
}
|
166
186
|
if (this['$respond_to?'] && this['$respond_to?'](#{snake_case_changed})) {
|
167
187
|
return this['$' + #{snake_case_changed}](value, previousValue);
|
168
188
|
}
|
169
189
|
}
|
170
190
|
}
|
171
191
|
end
|
172
|
-
|
173
|
-
`#{self.stimulus_controller}.values = #{js_values.to_n}`
|
174
192
|
end
|
175
193
|
|
176
194
|
def self.classes=(class_names = [])
|
177
|
-
`#{self.stimulus_controller}.classes =
|
195
|
+
`#{self.stimulus_controller}.classes = class_names`
|
178
196
|
|
179
197
|
class_names.each do |class_name|
|
180
|
-
|
181
|
-
|
182
|
-
end
|
198
|
+
js_name = class_name.to_s
|
199
|
+
ruby_name = self.to_ruby_name(class_name)
|
183
200
|
|
184
|
-
define_method("
|
185
|
-
`this
|
201
|
+
define_method("#{ruby_name}_class") do
|
202
|
+
`return this[#{js_name + "Class"}]`
|
186
203
|
end
|
187
204
|
|
188
|
-
define_method("
|
189
|
-
`return this
|
205
|
+
define_method("#{ruby_name}_classes") do
|
206
|
+
`return this[#{js_name + "Classes"}]`
|
190
207
|
end
|
191
208
|
|
192
|
-
define_method("
|
193
|
-
`this
|
209
|
+
define_method("has_#{ruby_name}_class") do
|
210
|
+
`return this[#{"has" + js_name.capitalize + "Class"}]`
|
194
211
|
end
|
195
212
|
end
|
196
213
|
end
|
197
214
|
|
198
|
-
def
|
199
|
-
|
200
|
-
`this.addClass(#{class_name}, #{element})`
|
201
|
-
else
|
202
|
-
`this.addClass(#{class_name})`
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
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
|
213
|
-
|
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
|
215
|
+
def element
|
216
|
+
JS::Proxy.new(`this.element`)
|
220
217
|
end
|
221
218
|
|
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
|
219
|
+
def window
|
220
|
+
@window ||= JS::Proxy.new($$.window)
|
232
221
|
end
|
233
222
|
|
234
|
-
def
|
235
|
-
|
223
|
+
def document
|
224
|
+
@document ||= JS::Proxy.new($$.document)
|
236
225
|
end
|
237
226
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "listen"
|
3
|
+
require "opal"
|
4
|
+
|
5
|
+
namespace :opal_stimulus do
|
6
|
+
def prepare_paths
|
7
|
+
[ "opal_proxy", "opal_stimulus" ].each do |gem_name|
|
8
|
+
Opal.use_gem(gem_name) rescue Opal.append_path(File.expand_path("lib", Bundler.rubygems.find_name(gem_name).first.full_gem_path))
|
9
|
+
end
|
10
|
+
|
11
|
+
Opal.append_path(Rails.root.join("app/opal"))
|
12
|
+
end
|
13
|
+
|
14
|
+
def compile
|
15
|
+
puts "🔨 Compiling Opal..."
|
16
|
+
|
17
|
+
builder = Opal::Builder.new
|
18
|
+
result = builder.build("application")
|
19
|
+
output_path = Rails.root.join("app/assets/builds/opal.js")
|
20
|
+
code = result.to_s
|
21
|
+
|
22
|
+
if Rails.env.development?
|
23
|
+
code += "//# sourceMappingURL=/assets/opal.js.map"
|
24
|
+
sourcemap_path = "#{output_path}.map"
|
25
|
+
source_map_json = result.source_map.to_json
|
26
|
+
File.write(sourcemap_path, source_map_json)
|
27
|
+
end
|
28
|
+
|
29
|
+
File.write(output_path, code)
|
30
|
+
|
31
|
+
puts "✅ Compiled to #{output_path}"
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Build Opal Stimulus controllers"
|
35
|
+
task build: [:environment] do
|
36
|
+
prepare_paths
|
37
|
+
compile
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Watch and build Opal Stimulus controllers"
|
41
|
+
task watch: [:environment] do
|
42
|
+
prepare_paths
|
43
|
+
|
44
|
+
compile
|
45
|
+
|
46
|
+
listen = Listen.to(Rails.root.join("app/opal")) { compile }
|
47
|
+
|
48
|
+
puts "👀 Watching app/opal for changes..."
|
49
|
+
listen.start
|
50
|
+
Signal.trap("INT") { listen.stop }
|
51
|
+
sleep
|
52
|
+
end
|
53
|
+
end
|
data/lib/tasks/install.rake
CHANGED