stimulus_reflex 3.4.0 → 3.5.0.pre2
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 +639 -484
- data/CODE_OF_CONDUCT.md +6 -0
- data/Gemfile.lock +99 -96
- data/LATEST +1 -0
- data/README.md +15 -14
- data/app/channels/stimulus_reflex/channel.rb +42 -73
- data/lib/generators/USAGE +1 -1
- data/lib/generators/stimulus_reflex/{config_generator.rb → initializer_generator.rb} +3 -3
- data/lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt +3 -2
- data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +11 -4
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +16 -1
- data/lib/stimulus_reflex.rb +11 -2
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +7 -4
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +2 -2
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +20 -10
- data/lib/stimulus_reflex/broadcasters/update.rb +23 -0
- data/lib/stimulus_reflex/cable_ready_channels.rb +18 -3
- data/lib/stimulus_reflex/callbacks.rb +98 -0
- data/lib/stimulus_reflex/concern_enhancer.rb +37 -0
- data/lib/stimulus_reflex/configuration.rb +3 -1
- data/lib/stimulus_reflex/element.rb +48 -7
- data/lib/stimulus_reflex/policies/reflex_invocation_policy.rb +28 -0
- data/lib/stimulus_reflex/reflex.rb +49 -58
- data/lib/stimulus_reflex/reflex_data.rb +79 -0
- data/lib/stimulus_reflex/reflex_factory.rb +31 -0
- data/lib/stimulus_reflex/request_parameters.rb +19 -0
- data/lib/stimulus_reflex/{logger.rb → utils/logger.rb} +6 -8
- data/lib/stimulus_reflex/utils/sanity_checker.rb +210 -0
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/tasks/stimulus_reflex/install.rake +54 -15
- data/package.json +7 -6
- data/stimulus_reflex.gemspec +8 -8
- data/test/broadcasters/broadcaster_test_case.rb +1 -1
- data/test/broadcasters/nothing_broadcaster_test.rb +5 -3
- data/test/broadcasters/page_broadcaster_test.rb +8 -4
- data/test/broadcasters/selector_broadcaster_test.rb +171 -55
- data/test/callbacks_test.rb +652 -0
- data/test/concern_enhancer_test.rb +54 -0
- data/test/element_test.rb +181 -0
- data/test/reflex_test.rb +2 -2
- data/test/test_helper.rb +22 -0
- data/test/tmp/app/reflexes/application_reflex.rb +10 -3
- data/test/tmp/app/reflexes/demo_reflex.rb +4 -2
- data/yarn.lock +1280 -1284
- metadata +47 -33
- data/lib/stimulus_reflex/sanity_checker.rb +0 -154
- data/tags +0 -156
data/lib/generators/USAGE
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
require "rails/generators"
|
4
4
|
|
5
5
|
module StimulusReflex
|
6
|
-
class
|
7
|
-
desc "Creates
|
6
|
+
class InitializerGenerator < Rails::Generators::Base
|
7
|
+
desc "Creates a StimulusReflex initializer in config/initializers"
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
9
9
|
|
10
|
-
def
|
10
|
+
def copy_initializer_file
|
11
11
|
copy_file "config/initializers/stimulus_reflex.rb"
|
12
12
|
end
|
13
13
|
end
|
@@ -17,12 +17,13 @@ class <%= class_name %>Reflex < ApplicationReflex
|
|
17
17
|
# - unsigned - use an unsigned Global ID to map dataset attribute to a model eg. element.unsigned[:foo]
|
18
18
|
# - cable_ready - a special cable_ready that can broadcast to the current visitor (no brackets needed)
|
19
19
|
# - reflex_id - a UUIDv4 that uniquely identies each Reflex
|
20
|
+
# - tab_id - a UUIDv4 that uniquely identifies the browser tab
|
20
21
|
#
|
21
22
|
# Example:
|
22
23
|
#
|
23
24
|
# before_reflex do
|
24
25
|
# # throw :abort # this will prevent the Reflex from continuing
|
25
|
-
# # learn more about callbacks at https://docs.stimulusreflex.com/lifecycle
|
26
|
+
# # learn more about callbacks at https://docs.stimulusreflex.com/rtfm/lifecycle
|
26
27
|
# end
|
27
28
|
#
|
28
29
|
# def example(argument=true)
|
@@ -30,7 +31,7 @@ class <%= class_name %>Reflex < ApplicationReflex
|
|
30
31
|
# # Any declared instance variables will be made available to the Rails controller and view.
|
31
32
|
# end
|
32
33
|
#
|
33
|
-
# Learn more at: https://docs.stimulusreflex.com/
|
34
|
+
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
|
34
35
|
|
35
36
|
<% actions.each do |action| -%>
|
36
37
|
def <%= action %>
|
@@ -1,12 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ApplicationReflex < StimulusReflex::Reflex
|
4
|
-
# Put application-wide Reflex behavior and callbacks in this file.
|
4
|
+
# Put application-wide Reflex behavior and callbacks in this file.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# If your ActionCable connection is: `identified_by :current_user`
|
9
9
|
# delegate :current_user, to: :connection
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# If you need to localize your Reflexes, you can set the I18n locale here:
|
12
|
+
#
|
13
|
+
# before_reflex do
|
14
|
+
# I18n.locale = :fr
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# For code examples, considerations and caveats, see:
|
18
|
+
# https://docs.stimulusreflex.com/rtfm/patterns#internationalization
|
12
19
|
end
|
@@ -1,11 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# The ActionCable logger is REALLY noisy, and might even impact performance.
|
4
|
+
# Uncomment the line below to silence the ActionCable logger.
|
5
|
+
|
6
|
+
# ActionCable.server.config.logger = Logger.new(nil)
|
7
|
+
|
3
8
|
StimulusReflex.configure do |config|
|
4
9
|
# Enable/disable exiting / warning when the sanity checks fail options:
|
5
10
|
# `:exit` or `:warn` or `:ignore`
|
6
11
|
|
7
12
|
# config.on_failed_sanity_checks = :exit
|
8
13
|
|
14
|
+
# Enable/disable exiting / warning when there's a new StimulusReflex release
|
15
|
+
# `:exit` or `:warn` or `:ignore`
|
16
|
+
|
17
|
+
# config.on_new_version_available = :ignore
|
18
|
+
|
19
|
+
# Enable/disable exiting / warning when there is no default URLs specified in environment config
|
20
|
+
# `:warn` or `:ignore`
|
21
|
+
|
22
|
+
# config.on_missing_default_urls = :warn
|
23
|
+
|
9
24
|
# Override the parent class that the StimulusReflex ActionCable channel inherits from
|
10
25
|
|
11
26
|
# config.parent_channel = "ApplicationCable::Channel"
|
@@ -15,7 +30,7 @@ StimulusReflex.configure do |config|
|
|
15
30
|
# Available colors: red, green, yellow, blue, magenta, cyan, white
|
16
31
|
# You can also use attributes from your ActionCable Connection's identifiers that resolve to valid ActiveRecord models
|
17
32
|
# eg. if your connection is `identified_by :current_user` and your User model has an email attribute, you can access r.email (it will display `-` if the user isn't logged in)
|
18
|
-
# Learn more at: https://docs.stimulusreflex.com/troubleshooting#stimulusreflex-logging
|
33
|
+
# Learn more at: https://docs.stimulusreflex.com/appendices/troubleshooting#stimulusreflex-logging
|
19
34
|
|
20
35
|
# config.logging = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
21
36
|
|
data/lib/stimulus_reflex.rb
CHANGED
@@ -1,25 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "uri"
|
4
|
+
require "open-uri"
|
4
5
|
require "rack"
|
5
6
|
require "rails/engine"
|
6
7
|
require "active_support/all"
|
7
8
|
require "action_dispatch"
|
8
9
|
require "action_cable"
|
10
|
+
require "action_view"
|
9
11
|
require "nokogiri"
|
10
12
|
require "cable_ready"
|
11
13
|
require "stimulus_reflex/version"
|
12
14
|
require "stimulus_reflex/cable_ready_channels"
|
15
|
+
require "stimulus_reflex/concern_enhancer"
|
13
16
|
require "stimulus_reflex/configuration"
|
17
|
+
require "stimulus_reflex/callbacks"
|
18
|
+
require "stimulus_reflex/request_parameters"
|
14
19
|
require "stimulus_reflex/reflex"
|
20
|
+
require "stimulus_reflex/reflex_data"
|
21
|
+
require "stimulus_reflex/reflex_factory"
|
15
22
|
require "stimulus_reflex/element"
|
16
|
-
require "stimulus_reflex/sanity_checker"
|
17
23
|
require "stimulus_reflex/broadcasters/broadcaster"
|
18
24
|
require "stimulus_reflex/broadcasters/nothing_broadcaster"
|
19
25
|
require "stimulus_reflex/broadcasters/page_broadcaster"
|
20
26
|
require "stimulus_reflex/broadcasters/selector_broadcaster"
|
27
|
+
require "stimulus_reflex/broadcasters/update"
|
28
|
+
require "stimulus_reflex/policies/reflex_invocation_policy"
|
21
29
|
require "stimulus_reflex/utils/colorize"
|
22
|
-
require "stimulus_reflex/logger"
|
30
|
+
require "stimulus_reflex/utils/logger"
|
31
|
+
require "stimulus_reflex/utils/sanity_checker"
|
23
32
|
|
24
33
|
module StimulusReflex
|
25
34
|
class Engine < Rails::Engine
|
@@ -3,7 +3,10 @@
|
|
3
3
|
module StimulusReflex
|
4
4
|
class Broadcaster
|
5
5
|
attr_reader :reflex, :logger, :operations
|
6
|
-
delegate :cable_ready, :permanent_attribute_name, to: :reflex
|
6
|
+
delegate :cable_ready, :permanent_attribute_name, :payload, to: :reflex
|
7
|
+
|
8
|
+
DEFAULT_HTML_WITHOUT_FORMAT = Nokogiri::XML::Node::SaveOptions::DEFAULT_HTML &
|
9
|
+
~Nokogiri::XML::Node::SaveOptions::FORMAT
|
7
10
|
|
8
11
|
def initialize(reflex)
|
9
12
|
@reflex = reflex
|
@@ -23,13 +26,13 @@ module StimulusReflex
|
|
23
26
|
false
|
24
27
|
end
|
25
28
|
|
26
|
-
def broadcast_message(subject:,
|
27
|
-
logger.error "\e[31m#{body}\e[0m" if subject == "error"
|
29
|
+
def broadcast_message(subject:, data: {}, error: nil)
|
28
30
|
operations << ["document", :dispatch_event]
|
29
31
|
cable_ready.dispatch_event(
|
30
32
|
name: "stimulus-reflex:server-message",
|
31
33
|
detail: {
|
32
|
-
reflexId: data
|
34
|
+
reflexId: data.delete("reflexId"),
|
35
|
+
payload: payload,
|
33
36
|
stimulus_reflex: data.merge(
|
34
37
|
morph: to_sym,
|
35
38
|
server_message: {subject: subject, body: error&.to_s}
|
@@ -12,10 +12,11 @@ module StimulusReflex
|
|
12
12
|
selectors = selectors.select { |s| document.css(s).present? }
|
13
13
|
selectors.each do |selector|
|
14
14
|
operations << [selector, :morph]
|
15
|
-
html = document.css(selector).inner_html
|
15
|
+
html = document.css(selector).inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT)
|
16
16
|
cable_ready.morph(
|
17
17
|
selector: selector,
|
18
18
|
html: html,
|
19
|
+
payload: payload,
|
19
20
|
children_only: true,
|
20
21
|
permanent_attribute_name: permanent_attribute_name,
|
21
22
|
stimulus_reflex: data.merge({
|
@@ -23,7 +24,6 @@ module StimulusReflex
|
|
23
24
|
})
|
24
25
|
)
|
25
26
|
end
|
26
|
-
|
27
27
|
cable_ready.broadcast
|
28
28
|
end
|
29
29
|
|
@@ -5,16 +5,16 @@ module StimulusReflex
|
|
5
5
|
def broadcast(_, data = {})
|
6
6
|
morphs.each do |morph|
|
7
7
|
selectors, html = morph
|
8
|
-
updates =
|
9
|
-
updates.each do |
|
10
|
-
|
11
|
-
|
12
|
-
match = fragment.at_css(selector)
|
8
|
+
updates = create_update_collection(selectors, html)
|
9
|
+
updates.each do |update|
|
10
|
+
fragment = Nokogiri::HTML.fragment(update.html.to_s)
|
11
|
+
match = fragment.at_css(update.selector)
|
13
12
|
if match.present?
|
14
|
-
operations << [selector, :morph]
|
13
|
+
operations << [update.selector, :morph]
|
15
14
|
cable_ready.morph(
|
16
|
-
selector: selector,
|
17
|
-
html: match.inner_html,
|
15
|
+
selector: update.selector,
|
16
|
+
html: match.inner_html(save_with: Broadcaster::DEFAULT_HTML_WITHOUT_FORMAT),
|
17
|
+
payload: payload,
|
18
18
|
children_only: true,
|
19
19
|
permanent_attribute_name: permanent_attribute_name,
|
20
20
|
stimulus_reflex: data.merge({
|
@@ -22,10 +22,11 @@ module StimulusReflex
|
|
22
22
|
})
|
23
23
|
)
|
24
24
|
else
|
25
|
-
operations << [selector, :inner_html]
|
25
|
+
operations << [update.selector, :inner_html]
|
26
26
|
cable_ready.inner_html(
|
27
|
-
selector: selector,
|
27
|
+
selector: update.selector,
|
28
28
|
html: fragment.to_html,
|
29
|
+
payload: payload,
|
29
30
|
stimulus_reflex: data.merge({
|
30
31
|
morph: to_sym
|
31
32
|
})
|
@@ -57,5 +58,14 @@ module StimulusReflex
|
|
57
58
|
def to_s
|
58
59
|
"Selector"
|
59
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def create_update_collection(selectors, html)
|
65
|
+
updates = selectors.is_a?(Hash) ? selectors : {selectors => html}
|
66
|
+
updates.map do |key, value|
|
67
|
+
StimulusReflex::Broadcasters::Update.new(key, value, reflex)
|
68
|
+
end
|
69
|
+
end
|
60
70
|
end
|
61
71
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module StimulusReflex
|
2
|
+
module Broadcasters
|
3
|
+
class Update
|
4
|
+
include CableReady::Identifiable
|
5
|
+
|
6
|
+
def initialize(key, value, reflex)
|
7
|
+
@key = key
|
8
|
+
@value = value
|
9
|
+
@reflex = reflex
|
10
|
+
end
|
11
|
+
|
12
|
+
def selector
|
13
|
+
@selector ||= identifiable?(@key) ? dom_id(@key) : @key.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def html
|
17
|
+
html = @reflex.render(@key) if @key.is_a?(ActiveRecord::Base) && @value.nil?
|
18
|
+
html = @reflex.render_collection(@key) if @key.is_a?(ActiveRecord::Relation) && @value.nil?
|
19
|
+
html || @value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -2,12 +2,11 @@
|
|
2
2
|
|
3
3
|
module StimulusReflex
|
4
4
|
class CableReadyChannels
|
5
|
-
stimulus_reflex_channel_methods = CableReady::Channels.instance.operations.keys + [:broadcast, :broadcast_to]
|
6
|
-
delegate(*stimulus_reflex_channel_methods, to: "stimulus_reflex_channel")
|
7
5
|
delegate :[], to: "cable_ready_channels"
|
8
6
|
|
9
|
-
def initialize(stream_name)
|
7
|
+
def initialize(stream_name, reflex_id)
|
10
8
|
@stream_name = stream_name
|
9
|
+
@reflex_id = reflex_id
|
11
10
|
end
|
12
11
|
|
13
12
|
def cable_ready_channels
|
@@ -17,5 +16,21 @@ module StimulusReflex
|
|
17
16
|
def stimulus_reflex_channel
|
18
17
|
CableReady::Channels.instance[@stream_name]
|
19
18
|
end
|
19
|
+
|
20
|
+
def method_missing(name, *args)
|
21
|
+
if stimulus_reflex_channel.respond_to?(name)
|
22
|
+
if (options = args.find_index { |a| a.is_a? Hash })
|
23
|
+
args[options][:reflex_id] = @reflex_id
|
24
|
+
elsif args.any?
|
25
|
+
args << {reflex_id: @reflex_id}
|
26
|
+
end
|
27
|
+
return stimulus_reflex_channel.public_send(name, *args)
|
28
|
+
end
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def respond_to_missing?(name, include_all)
|
33
|
+
stimulus_reflex_channel.respond_to?(name) || super
|
34
|
+
end
|
20
35
|
end
|
21
36
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module StimulusReflex
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
define_callbacks :process, skip_after_callbacks_if_terminated: true
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def before_reflex(*args, &block)
|
14
|
+
add_callback(:before, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_reflex(*args, &block)
|
18
|
+
add_callback(:after, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def around_reflex(*args, &block)
|
22
|
+
add_callback(:around, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepend_before_reflex(*args, &block)
|
26
|
+
prepend_callback(:before, *args, &block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend_after_reflex(*args, &block)
|
30
|
+
prepend_callback(:after, *args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def prepend_around_reflex(*args, &block)
|
34
|
+
prepend_callback(:around, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def skip_before_reflex(*args, &block)
|
38
|
+
omit_callback(:before, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def skip_after_reflex(*args, &block)
|
42
|
+
omit_callback(:after, *args, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def skip_around_reflex(*args, &block)
|
46
|
+
omit_callback(:around, *args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :append_before_reflex, :before_reflex
|
50
|
+
alias_method :append_around_reflex, :around_reflex
|
51
|
+
alias_method :append_after_reflex, :after_reflex
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def add_callback(kind, *args, &block)
|
56
|
+
insert_callbacks(args, block) do |name, options|
|
57
|
+
set_callback(:process, kind, name, options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepend_callback(kind, *args, &block)
|
62
|
+
insert_callbacks(args, block) do |name, options|
|
63
|
+
set_callback(:process, kind, name, options.merge(prepend: true))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def omit_callback(kind, *args, &block)
|
68
|
+
insert_callbacks(args) do |name, options|
|
69
|
+
skip_callback(:process, kind, name, options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def insert_callbacks(callbacks, block = nil)
|
74
|
+
options = callbacks.extract_options!
|
75
|
+
normalize_callback_options!(options)
|
76
|
+
|
77
|
+
callbacks.push(block) if block
|
78
|
+
|
79
|
+
callbacks.each do |callback|
|
80
|
+
yield callback, options
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def normalize_callback_options!(options)
|
85
|
+
normalize_callback_option! options, :only, :if
|
86
|
+
normalize_callback_option! options, :except, :unless
|
87
|
+
end
|
88
|
+
|
89
|
+
def normalize_callback_option!(options, from, to)
|
90
|
+
if (from = options.delete(from))
|
91
|
+
from_set = Array(from).map(&:to_s).to_set
|
92
|
+
from = proc { |reflex| from_set.include? reflex.method_name.to_s }
|
93
|
+
options[to] = Array(options[to]).unshift(from)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StimulusReflex
|
2
|
+
module ConcernEnhancer
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def method_missing(name, *args)
|
7
|
+
case ancestors
|
8
|
+
when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
|
9
|
+
if (ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include? name
|
10
|
+
nil
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
|
15
|
+
if StimulusReflex::Reflex.public_methods.include? name
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond_to_missing?(name, include_all = false)
|
26
|
+
case ancestors
|
27
|
+
when ->(a) { !(a & [StimulusReflex::Reflex]).empty? }
|
28
|
+
(ActiveRecord::Base.public_methods + ActionController::Base.public_methods).include?(name) || super
|
29
|
+
when ->(a) { !(a & [ActiveRecord::Base, ActionController::Base]).empty? }
|
30
|
+
StimulusReflex::Reflex.public_methods.include?(name) || super
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|