Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lambda slots #2

Merged
merged 1 commit into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,55 @@ class BlogComponent < Phlex::HTML
end
```

#### Lambda slots
Lambda slots are valuable when you prefer not to create another component for straightforward structures or when you need to render another view with specific parameters
```ruby

class BlogComponent < Phlex::HTML
include Phlex::Slotable

slot :header, ->(size:, &content) { render HeaderComponent.new(size: size, color: "blue"), &content }
slot :post, ->(featured:, &content) { span(class: featured ? "featured" : nil, &content) }, many: true
end

class MyPage < Phlex::HTML
def template
render BlogComponent.new do |blog|
blog.with_header(size: :lg) { "Hello World!" }

blog.with_post(featured: true) { "Post A" }
blog.with_post { "Post B" }
blog.with_post { "Post C" }
end
end
end
```

You can access the internal view state within lambda slots.For example:
```ruby
class BlogComponent < Phlex::HTML
include Phlex::Slotable

slot :header, ->(size:, &content) { render HeaderComponent.new(size: size, color: @header_color), &content }

def initialize(header_color:)
@header_color = header_color
end
end

class MyPage < Phlex::HTML
def template
render BlogComponent.new(header_color: "red") do |blog|
blog.with_header(size: :lg) { "Hello World!" }
end
end
end
```

## Roadmap
- Accepts Strings as view class name
- Allow lambda slots
- Allow polymorphic slots
- ✅ ~~Accept Strings as view class name~~
- ✅ ~~Allow lambda slots~~
- 🕐 Allow polymorphic slots

## Development

Expand Down
9 changes: 9 additions & 0 deletions lib/phlex/slotable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ module ClassMethods
def slot(slot_name, callable = nil, many: false)
include Phlex::DeferredRender

if callable.is_a?(Proc)
define_method :"__call_#{slot_name}__", &callable
private :"__call_#{slot_name}__"
end

if many
define_method :"with_#{slot_name}" do |*args, **kwargs, &block|
instance_variable_set(:"@#{slot_name}_slots", []) unless instance_variable_defined?(:"@#{slot_name}_slots")
Expand All @@ -21,6 +26,8 @@ def slot(slot_name, callable = nil, many: false)
block
when String
self.class.const_get(callable).new(*args, **kwargs, &block)
when Proc
-> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) }
else
callable.new(*args, **kwargs, &block)
end
Expand All @@ -44,6 +51,8 @@ def slot(slot_name, callable = nil, many: false)
block
when String
self.class.const_get(callable).new(*args, **kwargs, &block)
when Proc
-> { self.class.instance_method(:"__call_#{slot_name}__").bind_call(self, *args, **kwargs, &block) }
else
callable.new(*args, **kwargs, &block)
end
Expand Down
98 changes: 98 additions & 0 deletions test/phlex/test_multi_slot_with_lambda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

require "test_helper"

class Phlex::TestMultiSlotWithLambda < Minitest::Test
class HeadlineComponent < Phlex::HTML
include Phlex::Slotable

slot :icon
slot :title

def initialize(size:, bg_color:)
@size = size
@bg_color = bg_color
end

def template
div class: "headline text-#{@size} bg-#{@bg_color}" do
render icon_slot
render title_slot
end
end
end

class Blog < Phlex::HTML
include Phlex::Slotable

slot :post, ->(featured: false, &content) { p(class: featured ? "featured" : nil, &content) }, many: true
slot :headline, ->(size:, &content) do
render HeadlineComponent.new(size: size, bg_color: @headline_bg_color), &content
end, many: true

def initialize(headline_bg_color: nil)
@headline_bg_color = headline_bg_color
end

def template
if post_slots?
main do
headline_slots.each do |slot|
render slot
end
post_slots.each do |slot|
render slot
end
end
end

footer { post_slots.size }
end
end

def test_slots
output = Blog.new(headline_bg_color: "blue").call do |c|
c.with_post(featured: true) { "Post A" }
c.with_post { "Post B" }
c.with_post { "Post C" }

c.with_headline(size: "lg") do |h|
h.with_title { "Headline A" }
h.with_icon { h.i(class: "star") }
end
c.with_headline(size: "md") do |h|
h.with_title { "Headline B" }
h.with_icon { h.i(class: "heart") }
end
end

expected_html = <<~HTML.join_lines
<main>
<div class="headline text-lg bg-blue">
<i class="star"></i>
Headline A
</div>
<div class="headline text-md bg-blue">
<i class="heart"></i>
Headline B
</div>

<p class="featured">Post A</p>
<p>Post B</p>
<p>Post C</p>
</main>

<footer>
3
</footer>
HTML

assert_equal expected_html, output
end

def test_empty_slots
output = Blog.new.call

assert_equal output, "<footer>0</footer>"
end
end
78 changes: 78 additions & 0 deletions test/phlex/test_single_slot_with_lambda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require "test_helper"

class Phlex::TestSingleSlotWithLambda < Minitest::Test
class SubtitleComponent < Phlex::HTML
include Phlex::Slotable

slot :icon
slot :content

def initialize(size:, bg_color:)
@size = size
@bg_color = bg_color
end

def template(&content)
h3(class: "text-#{@size} bg-#{@bg_color}") do
render icon_slot
render content_slot
end
end
end

class Blog < Phlex::HTML
include Phlex::Slotable

slot :title, ->(size:, &content) { h1(class: "text-#{size}", &content) }
slot :subtitle, ->(size:, &content) do
render SubtitleComponent.new(size: size, bg_color: @subtitle_bg_color), &content
end

def initialize(subtitle_bg_color: nil)
@subtitle_bg_color = subtitle_bg_color
end

def template
div id: "header" do
render title_slot if title_slot?
render subtitle_slot if subtitle_slot?
end

main { "My posts" }
end
end

def test_slot
output = Blog.new(subtitle_bg_color: "gray").call do |c|
c.with_title(size: :lg) { "Hello World!" }
c.with_subtitle(size: :sm) do |s|
s.with_icon { s.i(class: "home") }
s.with_content { "Welcome to your posts" }
end
end

assert_equal output, <<~HTML.join_lines
<div id="header">
<h1 class="text-lg">Hello World!</h1>
<h3 class="text-sm bg-gray">
<i class="home"></i>
Welcome to your posts
</h3>
</div>
<main>
My posts
</main>
HTML
end

def test_empty_slot
output = Blog.new.call

assert_equal output, <<~HTML.join_lines
<div id="header"></div>
<main>My posts</main>
HTML
end
end