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

Selective rendering #667

Merged
merged 9 commits into from
Mar 2, 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
5 changes: 4 additions & 1 deletion lib/phlex/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ class Phlex::Context
def initialize
@buffer = +""
@capturing = false
@fragment = nil
@in_target_fragment = false
@found_target_fragment = false
end

attr_accessor :buffer, :capturing
attr_accessor :buffer, :capturing, :fragment, :in_target_fragment, :found_target_fragment

# Added for backwards compatibility with phlex-rails. We can remove this with 2.0
def target
Expand Down
40 changes: 38 additions & 2 deletions lib/phlex/elements.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,26 @@ def register_element(method_name, tag: method_name.name.tr("_", "-"), deprecated

def #{method_name}(**attributes, &block)
#{deprecation}
buffer = @_context.buffer

context = @_context
buffer = context.buffer
fragment = context.fragment
end_find = false

if fragment
in_target_fragment = context.in_target_fragment

if !in_target_fragment
if !context.found_target_fragment && attributes[:id] == fragment
context.in_target_fragment = true
context.found_target_fragment = true
end_find = true
else
yield if block
return nil
end
end
end

if attributes.length > 0 # with attributes
if block # with content block
Expand All @@ -64,6 +83,9 @@ def #{method_name}(**attributes, &block)

#{'flush' if tag == 'head'}

# I think we can actually throw from here.
context.in_target_fragment = false if end_find

nil
end

Expand All @@ -90,7 +112,21 @@ def register_void_element(method_name, tag: method_name.name.tr("_", "-"), depre

def #{method_name}(**attributes)
#{deprecation}
buffer = @_context.buffer
context = @_context
buffer = context.buffer
fragment = context.fragment

if fragment
in_target_fragment = context.in_target_fragment

if !in_target_fragment
if !context.found_target_fragment && attributes[:id] == fragment
context.found_target_fragment = true
else
return nil
end
end
end

if attributes.length > 0 # with attributes
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[respond_to?(:process_attributes) ? (attributes.hash + self.class.hash) : attributes.hash] || __attributes__(**attributes)) << ">"
Expand Down
5 changes: 4 additions & 1 deletion lib/phlex/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ def __unbuffered_class__

# Output an HTML doctype.
def doctype
@_context.buffer << "<!DOCTYPE html>"
context = @_context
return if context.fragment && !context.in_target_fragment

context.buffer << "<!DOCTYPE html>"
nil
end

Expand Down
25 changes: 19 additions & 6 deletions lib/phlex/sgml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,19 @@ def await(task)
end

# Renders the view and returns the buffer. The default buffer is a mutable String.
def call(buffer = +"", context: Phlex::Context.new, view_context: nil, parent: nil, &block)
__final_call__(buffer, context: context, view_context: view_context, parent: parent, &block).tap do
def call(buffer = +"", context: Phlex::Context.new, view_context: nil, parent: nil, fragment: nil, &block)
__final_call__(buffer, context: context, view_context: view_context, parent: parent, fragment: fragment, &block).tap do
self.class.rendered_at_least_once!
end
end

# @api private
def __final_call__(buffer = +"", context: Phlex::Context.new, view_context: nil, parent: nil, &block)
def __final_call__(buffer = +"", context: Phlex::Context.new, view_context: nil, parent: nil, fragment: nil, &block)
@_buffer = buffer
@_context = context
@_view_context = view_context
@_parent = parent
@_context.fragment = fragment if fragment

block ||= @_content_block

Expand Down Expand Up @@ -139,6 +140,9 @@ def __final_call__(buffer = +"", context: Phlex::Context.new, view_context: nil,
# @return [nil]
# @see #format_object
def plain(content)
context = @_context
return if context.fragment && !context.in_target_fragment

unless __text__(content)
raise ArgumentError, "You've passed an object to plain that is not handled by format_object. See https://rubydoc.info/gems/phlex/Phlex/SGML#format_object-instance_method for more information"
end
Expand All @@ -150,7 +154,10 @@ def plain(content)
# @return [nil]
# @yield If a block is given, it yields the block with no arguments.
def whitespace(&block)
buffer = @_context.buffer
context = @_context
return if context.fragment && !context.in_target_fragment

buffer = context.buffer

buffer << " "

Expand All @@ -165,7 +172,10 @@ def whitespace(&block)
# Output an HTML comment.
# @return [nil]
def comment(&block)
buffer = @_context.buffer
context = @_context
return if context.fragment && !context.in_target_fragment

buffer = context.buffer

buffer << "<!-- "
yield_content(&block)
Expand All @@ -180,7 +190,10 @@ def comment(&block)
def unsafe_raw(content = nil)
return nil unless content

@_context.buffer << content
context = @_context
return if context.fragment && !context.in_target_fragment

context.buffer << content
nil
end

Expand Down
55 changes: 55 additions & 0 deletions test/phlex/selective_rendering.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

class StandardElementExample < Phlex::HTML
def view_template
doctype
div {
comment { h1(id: "target") }
h1 { "Before" }
img(src: "before.jpg")
whitespace
comment { "This is a comment" }
h1(id: "target") {
plain "Hello"
strong { "World" }
img(src: "image.jpg")
}
img(src: "after.jpg")
h1(id: "target") { "After" }
}
end
end

class VoidElementExample < Phlex::HTML
def view_template
doctype
div {
comment { h1(id: "target") }
h1 { "Before" }
img(src: "before.jpg")
whitespace
comment { "This is a comment" }
h1 {
plain "Hello"
strong { "World" }
img(id: "target", src: "image.jpg")
}
img(src: "after.jpg")
h1(id: "target") { "After" }
}
end
end

describe Phlex::HTML do
it "renders the just the target fragment" do
expect(
StandardElementExample.new.call(fragment: "target")
).to be == %(<h1 id="target">Hello<strong>World</strong><img src="image.jpg"></h1>)
end

it "works with void elements" do
expect(
VoidElementExample.new.call(fragment: "target")
).to be == %(<img id="target" src="image.jpg">)
end
end