stimulus_reflex 3.3.0.pre4 → 3.4.0.pre1
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/CHANGELOG.md +100 -4
- data/Gemfile.lock +72 -69
- data/README.md +5 -2
- data/{lib → app/channels}/stimulus_reflex/channel.rb +17 -7
- data/lib/generators/stimulus_reflex/config_generator.rb +14 -0
- data/lib/generators/{stimulus_reflex_generator.rb → stimulus_reflex/stimulus_reflex_generator.rb} +0 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +89 -0
- data/lib/generators/{templates → stimulus_reflex/templates}/app/javascript/controllers/application_controller.js.tt +11 -8
- data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/%file_name%_reflex.rb.tt +0 -0
- data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/application_reflex.rb.tt +0 -0
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +9 -0
- data/lib/stimulus_reflex.rb +5 -2
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +3 -7
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +2 -2
- data/lib/stimulus_reflex/configuration.rb +24 -0
- data/lib/stimulus_reflex/element.rb +8 -0
- data/lib/stimulus_reflex/reflex.rb +21 -18
- data/lib/stimulus_reflex/sanity_checker.rb +98 -0
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/tasks/stimulus_reflex/install.rake +23 -8
- data/package.json +63 -0
- data/stimulus_reflex.gemspec +40 -0
- data/tags +98 -0
- data/test/generators/stimulus_reflex_generator_test.rb +3 -2
- data/test/tmp/app/reflexes/application_reflex.rb +12 -0
- data/test/tmp/app/reflexes/user_reflex.rb +33 -0
- data/yarn.lock +6261 -0
- metadata +27 -15
- data/lib/generators/templates/app/javascript/controllers/%file_name%_controller.js.tt +0 -57
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators"
|
4
|
+
|
5
|
+
module StimulusReflex
|
6
|
+
class ConfigGenerator < Rails::Generators::Base
|
7
|
+
desc "Creates an StimulusReflex configuration file in config/initializers"
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
|
10
|
+
def copy_config_file
|
11
|
+
copy_file "config/initializers/stimulus_reflex.rb"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/generators/{stimulus_reflex_generator.rb → stimulus_reflex/stimulus_reflex_generator.rb}
RENAMED
File without changes
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import ApplicationController from './application_controller'
|
2
|
+
|
3
|
+
/* This is the custom StimulusReflex controller for the <%= class_name %> Reflex.
|
4
|
+
* Learn more at: https://docs.stimulusreflex.com
|
5
|
+
*/
|
6
|
+
export default class extends ApplicationController {
|
7
|
+
/*
|
8
|
+
* Regular Stimulus lifecycle methods
|
9
|
+
* Learn more at: https://stimulusjs.org/reference/lifecycle-callbacks
|
10
|
+
*
|
11
|
+
* If you intend to use this controller as a regular stimulus controller as well,
|
12
|
+
* make sure any Stimulus lifecycle methods overridden in ApplicationController call super.
|
13
|
+
*
|
14
|
+
* Important:
|
15
|
+
* By default, StimulusReflex overrides the -connect- method so make sure you
|
16
|
+
* call super if you intend to do anything else when this controller connects.
|
17
|
+
*/
|
18
|
+
|
19
|
+
connect () {
|
20
|
+
super.connect()
|
21
|
+
// add your code here, if applicable
|
22
|
+
}
|
23
|
+
|
24
|
+
/* Reflex specific lifecycle methods.
|
25
|
+
*
|
26
|
+
* For every method defined in your Reflex class, a matching set of lifecycle methods become available
|
27
|
+
* in this javascript controller. These are optional, so feel free to delete these stubs if you don't
|
28
|
+
* need them.
|
29
|
+
*
|
30
|
+
* Important:
|
31
|
+
* Make sure to add data-controller="<%= class_name.underscore.dasherize %>" to your markup alongside
|
32
|
+
* data-reflex="<%= class_name %>#dance" for the lifecycle methods to fire properly.
|
33
|
+
*
|
34
|
+
* Example:
|
35
|
+
*
|
36
|
+
* <a href="#" data-reflex="click-><%= class_name %>#dance" data-controller="<%= class_name.underscore.dasherize %>">Dance!</a>
|
37
|
+
*
|
38
|
+
* Arguments:
|
39
|
+
*
|
40
|
+
* element - the element that triggered the reflex
|
41
|
+
* may be different than the Stimulus controller's this.element
|
42
|
+
*
|
43
|
+
* reflex - the name of the reflex e.g. "<%= class_name %>#dance"
|
44
|
+
*
|
45
|
+
* error/noop - the error message (for reflexError), otherwise null
|
46
|
+
*
|
47
|
+
* reflexId - a UUID4 or developer-provided unique identifier for each Reflex
|
48
|
+
*/
|
49
|
+
|
50
|
+
<% if actions.empty? -%>
|
51
|
+
// Assuming you create a "<%= class_name %>#dance" action in your Reflex class
|
52
|
+
// you'll be able to use the following lifecycle methods:
|
53
|
+
|
54
|
+
// beforeDance(element, reflex, noop, reflexId) {
|
55
|
+
// element.innerText = 'Putting dance shoes on...'
|
56
|
+
// }
|
57
|
+
|
58
|
+
// danceSuccess(element, reflex, noop, reflexId) {
|
59
|
+
// element.innerText = 'Danced like no one was watching! Was someone watching?'
|
60
|
+
// }
|
61
|
+
|
62
|
+
// danceError(element, reflex, error, reflexId) {
|
63
|
+
// console.error('danceError', error);
|
64
|
+
// element.innerText = "Couldn't dance!"
|
65
|
+
// }
|
66
|
+
<% end -%>
|
67
|
+
<% actions.each do |action| -%>
|
68
|
+
// <%= "before_#{action}".camelize(:lower) %>(element, reflex, noop, reflexId) {
|
69
|
+
// console.log("before <%= action %>", element, reflex, reflexId)
|
70
|
+
// }
|
71
|
+
|
72
|
+
// <%= "#{action}_success".camelize(:lower) %>(element, reflex, noop, reflexId) {
|
73
|
+
// console.log("<%= action %> success", element, reflex, reflexId)
|
74
|
+
// }
|
75
|
+
|
76
|
+
// <%= "#{action}_error".camelize(:lower) %>(element, reflex, error, reflexId) {
|
77
|
+
// console.error("<%= action %> error", element, reflex, error, reflexId)
|
78
|
+
// }
|
79
|
+
|
80
|
+
// <%= "#{action}_halted".camelize(:lower) %>(element, reflex, noop, reflexId) {
|
81
|
+
// console.warn("<%= action %> halted", element, reflex, reflexId)
|
82
|
+
// }
|
83
|
+
|
84
|
+
// <%= "after_#{action}".camelize(:lower) %>(element, reflex, noop, reflexId) {
|
85
|
+
// console.log("after <%= action %>", element, reflex, reflexId)
|
86
|
+
// }
|
87
|
+
<%= "\n" unless action == actions.last -%>
|
88
|
+
<% end -%>
|
89
|
+
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Controller } from 'stimulus'
|
2
2
|
import StimulusReflex from 'stimulus_reflex'
|
3
3
|
|
4
|
-
/* This is your
|
4
|
+
/* This is your ApplicationController.
|
5
5
|
* All StimulusReflex controllers should inherit from this class.
|
6
6
|
*
|
7
7
|
* Example:
|
@@ -17,7 +17,8 @@ export default class extends Controller {
|
|
17
17
|
StimulusReflex.register(this)
|
18
18
|
}
|
19
19
|
|
20
|
-
/* Application
|
20
|
+
/* Application-wide lifecycle methods
|
21
|
+
*
|
21
22
|
* Use these methods to handle lifecycle concerns for the entire application.
|
22
23
|
* Using the lifecycle is optional, so feel free to delete these stubs if you don't need them.
|
23
24
|
*
|
@@ -26,24 +27,26 @@ export default class extends Controller {
|
|
26
27
|
* element - the element that triggered the reflex
|
27
28
|
* may be different than the Stimulus controller's this.element
|
28
29
|
*
|
29
|
-
* reflex - the name of the reflex e.g. "
|
30
|
+
* reflex - the name of the reflex e.g. "Example#demo"
|
31
|
+
*
|
32
|
+
* error/noop - the error message (for reflexError), otherwise null
|
30
33
|
*
|
31
|
-
*
|
34
|
+
* reflexId - a UUID4 or developer-provided unique identifier for each Reflex
|
32
35
|
*/
|
33
36
|
|
34
|
-
beforeReflex (element, reflex) {
|
37
|
+
beforeReflex (element, reflex, noop, reflexId) {
|
35
38
|
// document.body.classList.add('wait')
|
36
39
|
}
|
37
40
|
|
38
|
-
reflexSuccess (element, reflex,
|
41
|
+
reflexSuccess (element, reflex, noop, reflexId) {
|
39
42
|
// show success message etc...
|
40
43
|
}
|
41
44
|
|
42
|
-
reflexError (element, reflex, error) {
|
45
|
+
reflexError (element, reflex, error, reflexId) {
|
43
46
|
// show error message etc...
|
44
47
|
}
|
45
48
|
|
46
|
-
afterReflex (element, reflex) {
|
49
|
+
afterReflex (element, reflex, noop, reflexId) {
|
47
50
|
// document.body.classList.remove('wait')
|
48
51
|
}
|
49
52
|
}
|
data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/%file_name%_reflex.rb.tt
RENAMED
File without changes
|
data/lib/generators/{templates → stimulus_reflex/templates}/app/reflexes/application_reflex.rb.tt
RENAMED
File without changes
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
StimulusReflex.configure do |config|
|
4
|
+
# Enable/disable whether startup should be aborted when the sanity checks fail
|
5
|
+
# config.exit_on_failed_sanity_checks = true
|
6
|
+
|
7
|
+
# Override the parent class that the StimulusReflex ActionCable channel inherits from
|
8
|
+
# config.parent_channel = "ApplicationCable::Channel"
|
9
|
+
end
|
data/lib/stimulus_reflex.rb
CHANGED
@@ -9,16 +9,19 @@ require "action_cable"
|
|
9
9
|
require "nokogiri"
|
10
10
|
require "cable_ready"
|
11
11
|
require "stimulus_reflex/version"
|
12
|
+
require "stimulus_reflex/configuration"
|
12
13
|
require "stimulus_reflex/reflex"
|
13
14
|
require "stimulus_reflex/element"
|
14
|
-
require "stimulus_reflex/
|
15
|
+
require "stimulus_reflex/sanity_checker"
|
15
16
|
require "stimulus_reflex/broadcasters/broadcaster"
|
16
17
|
require "stimulus_reflex/broadcasters/nothing_broadcaster"
|
17
18
|
require "stimulus_reflex/broadcasters/page_broadcaster"
|
18
19
|
require "stimulus_reflex/broadcasters/selector_broadcaster"
|
19
|
-
require "generators/stimulus_reflex_generator"
|
20
20
|
|
21
21
|
module StimulusReflex
|
22
22
|
class Engine < Rails::Engine
|
23
|
+
initializer "stimulus_reflex.sanity_check" do
|
24
|
+
SanityChecker.check!
|
25
|
+
end
|
23
26
|
end
|
24
27
|
end
|
@@ -24,22 +24,18 @@ module StimulusReflex
|
|
24
24
|
false
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def broadcast_message(subject:, body: nil, data: {}, error: nil)
|
28
28
|
logger.error "\e[31m#{body}\e[0m" if subject == "error"
|
29
29
|
cable_ready[stream_name].dispatch_event(
|
30
30
|
name: "stimulus-reflex:server-message",
|
31
31
|
detail: {
|
32
32
|
reflexId: data["reflexId"],
|
33
33
|
stimulus_reflex: data.merge(
|
34
|
-
|
35
|
-
server_message: {subject: subject, body:
|
34
|
+
morph: to_sym,
|
35
|
+
server_message: {subject: subject, body: error&.to_s}
|
36
36
|
)
|
37
37
|
}
|
38
38
|
)
|
39
|
-
end
|
40
|
-
|
41
|
-
def broadcast_message(subject:, body: nil, data: {})
|
42
|
-
enqueue_message subject: subject, body: body, data: data
|
43
39
|
cable_ready.broadcast
|
44
40
|
end
|
45
41
|
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module StimulusReflex
|
4
4
|
class PageBroadcaster < Broadcaster
|
5
5
|
def broadcast(selectors, data)
|
6
|
-
reflex.controller.process reflex.
|
6
|
+
reflex.controller.process reflex.params[:action]
|
7
7
|
page_html = reflex.controller.response.body
|
8
8
|
|
9
9
|
return unless page_html.present?
|
@@ -18,7 +18,7 @@ module StimulusReflex
|
|
18
18
|
children_only: true,
|
19
19
|
permanent_attribute_name: permanent_attribute_name,
|
20
20
|
stimulus_reflex: data.merge({
|
21
|
-
|
21
|
+
morph: to_sym
|
22
22
|
})
|
23
23
|
)
|
24
24
|
end
|
@@ -17,7 +17,7 @@ module StimulusReflex
|
|
17
17
|
children_only: true,
|
18
18
|
permanent_attribute_name: permanent_attribute_name,
|
19
19
|
stimulus_reflex: data.merge({
|
20
|
-
|
20
|
+
morph: to_sym
|
21
21
|
})
|
22
22
|
)
|
23
23
|
else
|
@@ -25,7 +25,7 @@ module StimulusReflex
|
|
25
25
|
selector: selector,
|
26
26
|
html: fragment.to_html,
|
27
27
|
stimulus_reflex: data.merge({
|
28
|
-
|
28
|
+
morph: to_sym
|
29
29
|
})
|
30
30
|
)
|
31
31
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StimulusReflex
|
4
|
+
class << self
|
5
|
+
def configure
|
6
|
+
yield configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def configuration
|
10
|
+
@configuration ||= Configuration.new
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :config, :configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
class Configuration
|
17
|
+
attr_accessor :exit_on_failed_sanity_checks, :parent_channel
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@exit_on_failed_sanity_checks = true
|
21
|
+
@parent_channel = "ApplicationCable::Channel"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -11,6 +11,14 @@ class StimulusReflex::Element < OpenStruct
|
|
11
11
|
@data_attributes.transform_keys! { |key| key.delete_prefix "data-" }
|
12
12
|
end
|
13
13
|
|
14
|
+
def signed
|
15
|
+
@signed ||= ->(accessor) { GlobalID::Locator.locate_signed(dataset[accessor]) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def unsigned
|
19
|
+
@unsigned ||= ->(accessor) { GlobalID::Locator.locate(dataset[accessor]) }
|
20
|
+
end
|
21
|
+
|
14
22
|
def dataset
|
15
23
|
@dataset ||= OpenStruct.new(data_attributes.merge(data_attributes.transform_keys(&:underscore)))
|
16
24
|
end
|
@@ -45,8 +45,10 @@ class StimulusReflex::Reflex
|
|
45
45
|
|
46
46
|
attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :permanent_attribute_name
|
47
47
|
|
48
|
+
alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
|
49
|
+
|
48
50
|
delegate :connection, :stream_name, to: :channel
|
49
|
-
delegate :session, to: :request
|
51
|
+
delegate :flash, :session, to: :request
|
50
52
|
delegate :broadcast, :broadcast_message, to: :broadcaster
|
51
53
|
|
52
54
|
def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, permanent_attribute_name: nil, params: {})
|
@@ -66,21 +68,26 @@ class StimulusReflex::Reflex
|
|
66
68
|
uri = URI.parse(url)
|
67
69
|
path = ActionDispatch::Journey::Router::Utils.normalize_path(uri.path)
|
68
70
|
query_hash = Rack::Utils.parse_nested_query(uri.query)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
)
|
81
|
-
)
|
71
|
+
mock_env = Rack::MockRequest.env_for(uri.to_s)
|
72
|
+
|
73
|
+
mock_env.merge!(
|
74
|
+
"rack.request.query_hash" => query_hash,
|
75
|
+
"rack.request.query_string" => uri.query,
|
76
|
+
"ORIGINAL_SCRIPT_NAME" => "",
|
77
|
+
"ORIGINAL_FULLPATH" => path,
|
78
|
+
Rack::SCRIPT_NAME => "",
|
79
|
+
Rack::PATH_INFO => path,
|
80
|
+
Rack::REQUEST_PATH => path,
|
81
|
+
Rack::QUERY_STRING => uri.query
|
82
82
|
)
|
83
|
+
|
84
|
+
env = connection.env.merge(mock_env)
|
85
|
+
req = ActionDispatch::Request.new(env)
|
86
|
+
|
83
87
|
path_params = Rails.application.routes.recognize_path_with_request(req, url, req.env[:extras] || {})
|
88
|
+
path_params[:controller] = path_params[:controller].force_encoding("UTF-8")
|
89
|
+
path_params[:action] = path_params[:action].force_encoding("UTF-8")
|
90
|
+
|
84
91
|
req.env.merge(ActionDispatch::Http::Parameters::PARAMETERS_KEY => path_params)
|
85
92
|
req.env["action_dispatch.request.parameters"] = req.parameters.merge(@params)
|
86
93
|
req.tap { |r| r.session.send :load! }
|
@@ -112,10 +119,6 @@ class StimulusReflex::Reflex
|
|
112
119
|
end
|
113
120
|
end
|
114
121
|
|
115
|
-
def url_params
|
116
|
-
@url_params ||= Rails.application.routes.recognize_path_with_request(request, request.path, request.env[:extras] || {})
|
117
|
-
end
|
118
|
-
|
119
122
|
def process(name, *args)
|
120
123
|
reflex_invoked = false
|
121
124
|
result = run_callbacks(:process) {
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class StimulusReflex::SanityChecker
|
4
|
+
JSON_VERSION_FORMAT = /(\d+\.\d+\.\d+.*)"/
|
5
|
+
|
6
|
+
def self.check!
|
7
|
+
instance = new
|
8
|
+
instance.check_caching_enabled
|
9
|
+
instance.check_javascript_package_version
|
10
|
+
end
|
11
|
+
|
12
|
+
def check_caching_enabled
|
13
|
+
unless caching_enabled?
|
14
|
+
warn_and_exit <<~WARN
|
15
|
+
Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
|
16
|
+
To enable caching in development, run:
|
17
|
+
rails dev:cache
|
18
|
+
WARN
|
19
|
+
end
|
20
|
+
|
21
|
+
unless not_null_store?
|
22
|
+
warn_and_exit <<~WARN
|
23
|
+
Stimulus Reflex requires caching to be enabled. Caching allows the session to be modified during ActionCable requests.
|
24
|
+
But your config.cache_store is set to :null_store, so it won't work.
|
25
|
+
WARN
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_javascript_package_version
|
30
|
+
if javascript_package_version.nil?
|
31
|
+
warn_and_exit <<~WARN
|
32
|
+
Can't locate the stimulus_reflex NPM package.
|
33
|
+
Either add it to your package.json as a dependency or use "yarn link stimulus_reflex" if you are doing development.
|
34
|
+
WARN
|
35
|
+
end
|
36
|
+
|
37
|
+
unless javascript_version_matches?
|
38
|
+
warn_and_exit <<~WARN
|
39
|
+
The Stimulus Reflex javascript package version (#{javascript_package_version}) does not match the Rubygem version (#{gem_version}).
|
40
|
+
To update the Stimulus Reflex npm package:
|
41
|
+
yarn upgrade stimulus_reflex@#{gem_version}
|
42
|
+
WARN
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def caching_enabled?
|
49
|
+
Rails.application.config.action_controller.perform_caching
|
50
|
+
end
|
51
|
+
|
52
|
+
def not_null_store?
|
53
|
+
Rails.application.config.cache_store != :null_store
|
54
|
+
end
|
55
|
+
|
56
|
+
def javascript_version_matches?
|
57
|
+
javascript_package_version == gem_version
|
58
|
+
end
|
59
|
+
|
60
|
+
def gem_version
|
61
|
+
@_gem_version ||= StimulusReflex::VERSION.gsub(".pre", "-pre")
|
62
|
+
end
|
63
|
+
|
64
|
+
def javascript_package_version
|
65
|
+
@_js_version ||= find_javascript_package_version
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_javascript_package_version
|
69
|
+
if (match = search_file(package_json_path, regex: /version/))
|
70
|
+
match[JSON_VERSION_FORMAT, 1]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def search_file(path, regex:)
|
75
|
+
return unless File.exist?(path)
|
76
|
+
File.foreach(path).grep(regex).first
|
77
|
+
end
|
78
|
+
|
79
|
+
def package_json_path
|
80
|
+
Rails.root.join("node_modules", "stimulus_reflex", "package.json")
|
81
|
+
end
|
82
|
+
|
83
|
+
def warn_and_exit(text)
|
84
|
+
puts "WARNING:"
|
85
|
+
puts text
|
86
|
+
exit_with_info if StimulusReflex.config.exit_on_failed_sanity_checks
|
87
|
+
end
|
88
|
+
|
89
|
+
def exit_with_info
|
90
|
+
puts
|
91
|
+
puts <<~INFO
|
92
|
+
If you know what you are doing and you want to start the application anyway,
|
93
|
+
you can add the following directive to an initializer:
|
94
|
+
StimulusReflex.config.exit_on_failed_sanity_checks = false
|
95
|
+
INFO
|
96
|
+
exit
|
97
|
+
end
|
98
|
+
end
|