stimulus_reflex 3.4.0 → 3.5.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
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
|