diff --git a/Gemfile.lock b/Gemfile.lock index 96504a4..efeb7be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,6 +36,7 @@ GEM zeitwerk (2.6.6) PLATFORMS + arm64-darwin-20 x86_64-darwin-19 x86_64-linux diff --git a/README.md b/README.md index c8f42c0..8ed2162 100644 --- a/README.md +++ b/README.md @@ -12,27 +12,25 @@ If bundler is not being used to manage dependencies, install the gem by executin ## Note: Usage in Rails -In order to use `turbo-ruby` in Rails with the Rails `render` method you have to install the `phlex-rails` gem in your app. +In order to use `turbo-ruby` in Rails with the Rails `render` method you have to install the `phlex-rails` gem in your app. ### Regular Element ```ruby # Ruby -Turbo::Elements::TurboStream.new(action: "console_log", message: "Hello World").to_html +Turbo.stream(action: "console_log", message: "Hello World").to_html ``` ```html+erb -<%= render Turbo::Elements::TurboStream.new(action: "console_log", message: "Hello World") %> +<%= render Turbo.stream(action: "console_log", message: "Hello World") %> ``` - - ### Blocks ```ruby # Ruby -Turbo::Elements::TurboStream.new(action: "morph", target: "post_1") do +Turbo.stream(action: "morph", target: "post_1") do %(

Post 1

) @@ -41,18 +39,51 @@ end.to_html ```html+erb -<%= render Turbo::Elements::TurboStream.new(action: "morph", target: "post_1") do %> +<%= render Turbo.stream(action: "morph", target: "post_1") do %>

Post 1

<% end %> ``` +### Registering custom stream actions + +It's also possible to register custom stream actions: + +```ruby +Turbo::Ruby.stream_actions do + # Can either register via the shorthand helper: + register :morph + + def log(message, **options, &block) + stream(action: "console_log", message: message, **options, &block) + end + + # Or define a custom action that must convert any positional arguments into the + # appropriate keyword arguments and must call `stream`. + def custom_action(*arguments, **options, &block) + stream(**options, &block) + end +end +``` + +Now the examples from above can be: + +```ruby +Turbo.log("Hello world").to_html + +Turbo.morph target: "post_1" do + %(
+

Post 1

+
) +end.to_html +``` + ### Partials (Rails only) ```html+erb -<%= render Turbo::Elements::TurboStream.new(action: "morph", target: "post_1", view_context: self, partial: "posts/post", locals: { post: @post } %> +<%= render turbo.morph(@post, partial: "posts/post") %> ``` ## Development diff --git a/lib/turbo/elements/turbo_stream.rb b/lib/turbo/elements/turbo_stream.rb index 16b1d83..955af8b 100644 --- a/lib/turbo/elements/turbo_stream.rb +++ b/lib/turbo/elements/turbo_stream.rb @@ -39,7 +39,7 @@ def render_template(&block) @view_context.capture(&block) elsif @rendering.any? throw "no view_context error" if @view_context.nil? - @view_context.render(formats: [:html], **@rendering) + @view_context.render(formats: [:html], object: @target, **@rendering) elsif @allow_inferred_rendering render_record(@target) end diff --git a/lib/turbo/ruby.rb b/lib/turbo/ruby.rb index 238d736..6ed4abc 100644 --- a/lib/turbo/ruby.rb +++ b/lib/turbo/ruby.rb @@ -10,5 +10,104 @@ module Turbo module Ruby + module StreamActionsContext + def self.register(name, action = name) + define_method name do |*arguments, **options, &block| + stream(*arguments, action: action, **options, &block) + end + end + + # turbo.targets(@posts).morph + def targets(targets) + if block_given? + targets.each { yield Turbo::Ruby::Target.new(self, _1) } + else + Turbo::Ruby::Targets.new(self, targets) + end + end + alias for targets # Turbo.for(@post).morph or Turbo.for(@posts).morph + alias call targets # Turbo.(@post).morph or Turbo.(@posts).morph + + def target(record) + Turbo::Ruby::Target.new(self, record).tap { yield _1 if block_given? } + end + + # <%= turbo.frame "post_1" do %> + # <% end %> + def frame(record) + Turbo::Elements::TurboFrame.new(to_dom_id(record)).tap { yield record if block_given? } + end + + def to_dom_id(record) + record + end + + def stream(**options, &block) + Turbo::Elements::TurboStream.new(**options, &block) + end + end + + class << self + def stream_actions_context + StreamActionsContext + end + + def stream_actions(&block) + stream_actions_context.module_eval(&block) + end + end + + stream_actions do + register :morph + + def log(message, **options, &block) + stream(action: "console_log", message: message, **options, &block) + end + end + end + + # Make `Turbo.morph` etc. and `Turbo.stream` available. + include Ruby.stream_actions_context + + class Targets + include Turbo::Ruby.stream_actions_context + + undef_method :targets + undef_method :target + + def initialize(context, targets) + @context = context + @targets = targets.map { context.to_dom_id(_1) } + end + + def frame(&block) + @targets.map { @context.frame(_1, &block) } + end + + def stream(**options, &block) + @context.stream(targets: @targets, **options, &block) + end + end + + class Target + include Turbo::Ruby.stream_actions_context + + undef_method :targets + undef_method :target + + def initialize(context, target) + @context = context + @target = context.to_dom_id(target) + end + + def frame(&block) + @context.frame(@target, &block) + end + + def stream(**options, &block) + @context.stream(target: @target, **options, &block) + end end end + +require_relative "ruby/railtie" if defined?(Rails::Railtie) diff --git a/lib/turbo/ruby/context.rb b/lib/turbo/ruby/context.rb new file mode 100644 index 0000000..27373be --- /dev/null +++ b/lib/turbo/ruby/context.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Turbo + module Ruby + class Context + include Turbo::Ruby.stream_actions_context + + attr_reader :view_context + + def initialize(view_context) + @view_context = view_context + end + + def to_dom_id(record) + record.respond_to?(:to_key) ? view_context.dom_id(record) : record + end + + # turbo.morph(@posts) or turbo.morph(@post) + def stream(records, **options, &block) + if records.respond_to?(:each) + super(targets: records, view_context: view_context, **options, &block) + else + super(target: records, view_context: view_context, **options, &block) + end + end + end + end +end diff --git a/lib/turbo/ruby/railtie.rb b/lib/turbo/ruby/railtie.rb new file mode 100644 index 0000000..c777da3 --- /dev/null +++ b/lib/turbo/ruby/railtie.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Turbo + module Ruby + class Railtie < Rails::Railtie + initializer "turbo.ruby.view_helpers" do + ActiveSupport.on_load :action_view do + include Turbo::Ruby::ViewContextHelper + end + end + end + end +end diff --git a/lib/turbo/ruby/view_context_helper.rb b/lib/turbo/ruby/view_context_helper.rb new file mode 100644 index 0000000..23c95ec --- /dev/null +++ b/lib/turbo/ruby/view_context_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Turbo + module Ruby + module ViewContextHelper + def turbo + @turbo ||= Turbo::Ruby::Context.new(self) + end + end + end +end