stimulus_reflex 1.1.1 → 2.0.0
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.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +105 -59
- data/lib/stimulus_reflex.rb +1 -1
- data/lib/stimulus_reflex/channel.rb +6 -15
- data/lib/stimulus_reflex/{dom_element.rb → element.rb} +1 -1
- data/lib/stimulus_reflex/reflex.rb +3 -2
- data/lib/stimulus_reflex/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8072c82ab1f16ce62c41e6d970952c3762a9e2fe5d87d2036ff2f37af7f3a4fb
|
4
|
+
data.tar.gz: b3549d33023d9c27e878443f48391e13b82562e4dc9d9cbfb64b9744d8fe0702
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ba2646379f9bbe65485d947c55467640e890d516ad571f704e064f0fb78956b977ba405d5698efdb6fd3b7e87f1075a9948d59c4b6ec2855410d3b290137a31
|
7
|
+
data.tar.gz: 756a22a1a4cb5f1d4ad985dd1424ed15edc3d2e4d8efcf080d3c3a9a551a170fc1a76d84543fd105acc104b97c07d7f377348a00ec329222fa6341c0e627051c
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
[](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
|
2
2
|
[](https://codeclimate.com/github/hopsoft/stimulus_reflex/maintainability)
|
3
|
+

|
4
|
+

|
3
5
|
|
4
6
|
# StimulusReflex
|
5
7
|
|
@@ -25,14 +27,19 @@ _Inspired by [Phoenix LiveView](https://youtu.be/Z2DU0qLfPIY?t=670)._ 🙌
|
|
25
27
|
- [How it Works](#how-it-works)
|
26
28
|
- [Setup](#setup)
|
27
29
|
* [JavaScript](#javascript)
|
30
|
+
+ [app/javascript/controllers/index.js](#appjavascriptcontrollersindexjs)
|
28
31
|
* [Gemfile](#gemfile)
|
29
|
-
- [
|
30
|
-
* [
|
31
|
-
|
32
|
-
|
32
|
+
- [Usage](#usage)
|
33
|
+
* [Implicit Declarative Reflexes](#implicit-declarative-reflexes)
|
34
|
+
+ [app/views/pages/example.html.erb](#appviewspagesexamplehtmlerb)
|
35
|
+
+ [app/reflexes/example_reflex.rb](#appreflexesexample_reflexrb)
|
36
|
+
* [Explicitly Defined Reflexes](#explicitly-defined-reflexes)
|
37
|
+
+ [app/views/pages/example.html.erb](#appviewspagesexamplehtmlerb-1)
|
38
|
+
+ [app/javascript/controllers/example.js](#appjavascriptcontrollersexamplejs)
|
39
|
+
+ [app/reflexes/example_reflex.rb](#appreflexesexample_reflexrb-1)
|
40
|
+
- [What Just Happened](#what-just-happened)
|
33
41
|
- [Advanced Usage](#advanced-usage)
|
34
|
-
* [Reflex
|
35
|
-
+ [The `options` Keyword Argument](#the-options-keyword-argument)
|
42
|
+
* [The Reflex `element` property](#the-reflex-element-property)
|
36
43
|
* [ActionCable](#actioncable)
|
37
44
|
+ [Performance](#performance)
|
38
45
|
+ [ActionCable Rooms](#actioncable-rooms)
|
@@ -77,6 +84,21 @@ There are no hidden gotchas.
|
|
77
84
|
```
|
78
85
|
yarn add stimulus_reflex
|
79
86
|
```
|
87
|
+
#### app/javascript/controllers/index.js
|
88
|
+
|
89
|
+
This is the file where Stimulus is initialized in your application.
|
90
|
+
_Note that your file location may be different._
|
91
|
+
|
92
|
+
```javascript
|
93
|
+
import { Application } from 'stimulus';
|
94
|
+
import { definitionsFromContext } from 'stimulus/webpack-helpers';
|
95
|
+
import StimulusReflex from 'stimulus_reflex';
|
96
|
+
|
97
|
+
const application = Application.start();
|
98
|
+
const context = require.context('controllers', true, /_controller\.js$/);
|
99
|
+
application.load(definitionsFromContext(context));
|
100
|
+
StimulusReflex.initialize(application);
|
101
|
+
```
|
80
102
|
|
81
103
|
### Gemfile
|
82
104
|
|
@@ -84,9 +106,47 @@ yarn add stimulus_reflex
|
|
84
106
|
gem "stimulus_reflex"
|
85
107
|
```
|
86
108
|
|
87
|
-
##
|
109
|
+
## Usage
|
110
|
+
|
111
|
+
### Implicit Declarative Reflexes
|
112
|
+
|
113
|
+
This example shows how to create a reactive feature without the need to write any JavaScript
|
114
|
+
other than initializing StimulusReflex itself _([see the setup instructions](#javascript))_. Everything else is managed entirely by HTML and Ruby.
|
88
115
|
|
89
|
-
|
116
|
+
#### app/views/pages/example.html.erb
|
117
|
+
|
118
|
+
```erb
|
119
|
+
<head></head>
|
120
|
+
<body>
|
121
|
+
<a href="#" data-reflex="click->ExampleReflex#increment" data-step="1" data-count="<%= @count.to_i %>">
|
122
|
+
Increment <%= @count.to_i %>
|
123
|
+
</a>
|
124
|
+
</body>
|
125
|
+
</html>
|
126
|
+
```
|
127
|
+
|
128
|
+
#### app/reflexes/example_reflex.rb
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class ExampleReflex < StimulusReflex::Reflex
|
132
|
+
def increment
|
133
|
+
@count = element.dataset[:count].to_i + element.dataset[:step].to_i
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
The code above will automatically update the relevant DOM nodes with the updated count whenever the anchor is clicked.
|
139
|
+
|
140
|
+
__Note that all concerns from managing state to rendering views are handled server side.__
|
141
|
+
This technique works regardless of how complex the UI may become.
|
142
|
+
For example, we could render multiple instances of `@count` in unrelated sections of the page and they will all update.
|
143
|
+
|
144
|
+
### Explicitly Defined Reflexes
|
145
|
+
|
146
|
+
This example shows how to create a reactive feature by defining an explicit client side
|
147
|
+
Stimulus controller to handle the DOM event and trigger the server side reflex.
|
148
|
+
|
149
|
+
#### app/views/pages/example.html.erb
|
90
150
|
|
91
151
|
```erb
|
92
152
|
<head></head>
|
@@ -98,7 +158,7 @@ gem "stimulus_reflex"
|
|
98
158
|
</html>
|
99
159
|
```
|
100
160
|
|
101
|
-
|
161
|
+
#### app/javascript/controllers/example.js
|
102
162
|
|
103
163
|
```javascript
|
104
164
|
import { Controller } from "stimulus"
|
@@ -116,7 +176,7 @@ export default class extends Controller {
|
|
116
176
|
}
|
117
177
|
```
|
118
178
|
|
119
|
-
|
179
|
+
#### app/reflexes/example_reflex.rb
|
120
180
|
|
121
181
|
```ruby
|
122
182
|
class ExampleReflex < StimulusReflex::Reflex
|
@@ -126,65 +186,57 @@ class ExampleReflex < StimulusReflex::Reflex
|
|
126
186
|
end
|
127
187
|
```
|
128
188
|
|
129
|
-
|
189
|
+
## What Just Happened
|
190
|
+
|
191
|
+
The following happens when a `StimulusReflex::Reflex` is invoked.
|
130
192
|
|
131
193
|
1. The page that triggered the reflex is re-rerendered. _Instance variables created in the reflex are available to both the controller and view templates._
|
132
194
|
2. The re-rendered HTML is sent to the client over the ActionCable socket.
|
133
|
-
3. The page is updated via fast DOM diffing courtesy of morphdom.
|
134
|
-
|
135
|
-
## Advanced Usage
|
136
|
-
|
137
|
-
### Reflex Methods
|
195
|
+
3. The page is updated via fast DOM diffing courtesy of morphdom.
|
138
196
|
|
139
|
-
|
197
|
+
_NOTE: While future versions of StimulusReflex may support more granular updates, today the entire body is re-rendered and sent over the socket._
|
140
198
|
|
141
|
-
|
142
|
-
|
143
|
-
- This is the only supported keyword argument.
|
144
|
-
- It must appear after ordinal arguments.
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
class ExampleReflex < StimulusReflex::Reflex
|
148
|
-
def work(options: {})
|
149
|
-
# ...
|
150
|
-
end
|
199
|
+
## Advanced Usage
|
151
200
|
|
152
|
-
|
153
|
-
# ...
|
154
|
-
end
|
155
|
-
end
|
156
|
-
```
|
201
|
+
### The Reflex `element` property
|
157
202
|
|
158
|
-
|
203
|
+
All reflex methods expose an `element` property.
|
204
|
+
This property holds a Hash like data structure that represents the HTML element that triggered the refelx.
|
205
|
+
It contains all of the Stimulus controller's
|
159
206
|
[DOM element attributes](https://developer.mozilla.org/en-US/docs/Web/API/Element/attributes) as well as other properties like `checked` and `value`.
|
160
207
|
_Most of the values will be strings._
|
161
|
-
Here's an example:
|
162
208
|
|
163
209
|
```html
|
164
|
-
<checkbox
|
210
|
+
<checkbox id="example"
|
211
|
+
label="Example"
|
212
|
+
data-controller="checkbox"
|
213
|
+
data-value="123"
|
214
|
+
checked />
|
165
215
|
```
|
166
216
|
|
167
|
-
The markup above produces the following behavior in a reflex method.
|
168
|
-
|
169
217
|
```ruby
|
170
218
|
class ExampleReflex < StimulusReflex::Reflex
|
171
|
-
def work(
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
219
|
+
def work()
|
220
|
+
element[:id] # => the HTML element's id attribute value
|
221
|
+
element.dataset # => a Hash that represents the HTML element's dataset
|
222
|
+
|
223
|
+
element[:id] # => "example"
|
224
|
+
element[:checked] # => true
|
225
|
+
element[:label] # => "Example"
|
226
|
+
element["data-controller"] # => "checkbox"
|
227
|
+
element["data-value"] # => "123"
|
228
|
+
element.dataset[:controller] # => "checkbox"
|
229
|
+
element.dataset[:value] # => "123"
|
178
230
|
end
|
179
231
|
end
|
180
232
|
```
|
181
233
|
|
182
|
-
- `
|
183
|
-
- `
|
184
|
-
- `
|
185
|
-
- `select` elements assign `
|
186
|
-
- `select` elements with _multiselect_ enabled assign `
|
187
|
-
- All other values
|
234
|
+
- `element[:checked]` holds a boolean
|
235
|
+
- `element[:selected]` holds a boolean
|
236
|
+
- `element[:value]` holds the [DOM element's value](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#value)
|
237
|
+
- `select` elements assign `element[:value]` to their selected option's value
|
238
|
+
- `select` elements with _multiselect_ enabled assign `element[:values]` to their selected options values
|
239
|
+
- All other values exposed in `element` are extracted from the DOM element's attributes
|
188
240
|
|
189
241
|
### ActionCable
|
190
242
|
|
@@ -249,11 +301,5 @@ Please run `./bin/standardize` prior submitting pull requests.
|
|
249
301
|
1. Run `rake build`
|
250
302
|
1. Run `rake release`
|
251
303
|
1. Change directories `cd ./javascript`
|
252
|
-
1. Run `yarn publish`
|
253
|
-
1. Assign same version number to the JavaScript package
|
254
|
-
1. Push changes to GitHub
|
255
|
-
1. Push tags to GitHub
|
256
|
-
|
257
|
-
```
|
258
|
-
git push --tags
|
259
|
-
```
|
304
|
+
1. Run `yarn publish --tag GIT_TAG_CREATED_BY_RUBYGEMS`
|
305
|
+
1. Assign same version number to the JavaScript package _Might not be required?_
|
data/lib/stimulus_reflex.rb
CHANGED
@@ -22,11 +22,11 @@ class StimulusReflex::Channel < ActionCable::Channel::Base
|
|
22
22
|
reflex_name, method_name = target.split("#")
|
23
23
|
reflex_name = reflex_name.classify
|
24
24
|
arguments = data["args"] || []
|
25
|
-
|
25
|
+
element = StimulusReflex::Element.new(data["attrs"])
|
26
26
|
|
27
27
|
begin
|
28
|
-
reflex = reflex_name.constantize.new(self, url: url)
|
29
|
-
delegate_call_to_reflex reflex, method_name, arguments
|
28
|
+
reflex = reflex_name.constantize.new(self, url: url, element: element)
|
29
|
+
delegate_call_to_reflex reflex, method_name, arguments
|
30
30
|
rescue => invoke_error
|
31
31
|
logger.error "\e[31mStimulusReflex::Channel Failed to invoke #{target}! #{url} #{invoke_error}\e[0m"
|
32
32
|
end
|
@@ -40,24 +40,15 @@ class StimulusReflex::Channel < ActionCable::Channel::Base
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
-
def delegate_call_to_reflex(reflex, method_name, arguments = []
|
43
|
+
def delegate_call_to_reflex(reflex, method_name, arguments = [])
|
44
44
|
method = reflex.method(method_name)
|
45
45
|
required_params = method.parameters.select { |(kind, _)| kind == :req }
|
46
46
|
optional_params = method.parameters.select { |(kind, _)| kind == :opt }
|
47
|
-
accepts_options_kwarg = method.parameters.select { |(kind, name)| name == :options && kind.to_s.start_with?("key") }.size > 0
|
48
47
|
|
49
48
|
if arguments.size == 0 && required_params.size == 0
|
50
|
-
|
51
|
-
reflex.public_send method_name, {options: options}
|
52
|
-
else
|
53
|
-
reflex.public_send method_name
|
54
|
-
end
|
49
|
+
reflex.public_send method_name
|
55
50
|
elsif arguments.size >= required_params.size && arguments.size <= required_params.size + optional_params.size
|
56
|
-
|
57
|
-
reflex.public_send method_name, *arguments, {options: options}
|
58
|
-
else
|
59
|
-
reflex.public_send method_name, *arguments
|
60
|
-
end
|
51
|
+
reflex.public_send method_name, *arguments
|
61
52
|
else
|
62
53
|
raise ArgumentError.new("wrong number of arguments (given #{arguments.inspect}, expected #{required_params.inspect}, optional #{optional_params.inspect})")
|
63
54
|
end
|
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class StimulusReflex::Reflex
|
4
|
-
attr_reader :channel, :url
|
4
|
+
attr_reader :channel, :url, :element
|
5
5
|
|
6
6
|
delegate :connection, to: :channel
|
7
7
|
delegate :session, to: :request
|
8
8
|
|
9
|
-
def initialize(channel, url: nil)
|
9
|
+
def initialize(channel, url: nil, element: nil)
|
10
10
|
@channel = channel
|
11
11
|
@url = url
|
12
|
+
@element = element
|
12
13
|
end
|
13
14
|
|
14
15
|
def request
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stimulus_reflex
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Hopkins
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-09-
|
12
|
+
date: 2019-09-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -156,7 +156,7 @@ files:
|
|
156
156
|
- bin/standardize
|
157
157
|
- lib/stimulus_reflex.rb
|
158
158
|
- lib/stimulus_reflex/channel.rb
|
159
|
-
- lib/stimulus_reflex/
|
159
|
+
- lib/stimulus_reflex/element.rb
|
160
160
|
- lib/stimulus_reflex/reflex.rb
|
161
161
|
- lib/stimulus_reflex/version.rb
|
162
162
|
homepage: https://github.com/hopsoft/stimulus_reflex
|