1.2.0
Highlights
Initialize a view instance with a block
You can now optionally pass a content block to .new instead of passing it to #render. You won't need to handle it in your initializer as it will continue to be passed to the template method. This means you no longer need to worry about the precedence of brace blocks vs do blocks but also means you can instantiate a view with its content and save it somewhere to be rendered later. It also means you can use argument forwarding to forward a method’s arguments to the initialiser of a different view.
For example, this Nav::Link view takes the arguments text and to::
class Nav::Link < Phlex::HTML
def initialize(text, to:)
@text, @to = text, to
end
def template
li { a(href: @text) { @text } }
end
endWe can define a public method on the Nav view that forwards its arguments to the Nav::Link initializer.
class Nav < Phlex::HTML
def template(&)
nav { ul(&) }
end
def link(...)
render Link.new(...)
end
endThen we can use the Nav component like this.
render Nav do |n|
n.link "Home", to: "/"
n.link "About", to: "/about"
n.link "Contact", to: "/contact"
end[Experimental] DeferredRender pattern
The above technique won't help if you need to render slots more than once or out-of-order. In this case, we need to yield the block early and use public methods to capture all the slots before rendering the template. DeferredRender does just that. Here's how you could use it to build a tabs component where the tab labels are output before the tab contents.
class Tabs < Phlex::HTML
# Create a data structure for each of our tabs
Tab = Data.define(:name, :content)
# Include DeferredRender for an early yield
include DeferredRender
def initialize
# Create an empty list of tabs
@tabs = []
end
# The template is rendered after yielding the block, so there's no content block to render.
# Instead, the idea is to use the captured `@tabs` list, which we do in `#navigation` and `#contents`.
def template
div(class: "tabs") do
navigation
contents
end
end
# This is called while yielding the block, so the caller can add tabs to our list
def add_tab(name, &content)
@tabs << Tab.new(name:, content:)
end
private
# Both `navigation` and `contents` iterate through the list of tabs that we collected during the yield.
def navigation
ul(class: "tab-navigation") do
@tabs.each do |tab|
li { tab.name }
end
end
end
def contents
div(class: "tab-contents") do
@tabs.each do |tab|
article(&tab.content)
end
end
end
endUnderscored tag methods
Sometimes we need to override a tag method. If you’ve done this and need to get back to the original, you can now use _tagname.
Unbuffered decorator
You’ll probably never need to use this, but phlex-rails will use it for compatibility with ERB. The Unbuffered decorator can wrap a yielded view so that methods called on it return the captured result rather than immediately appending something to the output buffer. This means builder views (like the Nav example above) can behave like normal builders in ERB. We’ll share more details about this change in the next release of phlex-rails
Float now goes through object formatters
It's reasonable to want to customise the precision when outputting a float, so you can do the with format_object now.
def format_object(object)
case object
when Float
object.round(2)
else
super
end
endRemoved experimental Turbo views
@marcoroth’s new gem turbo-ruby uses Phlex to output Turbo elements, so we’ll probably switch to that in phlex-rails.
What's Changed
- Update Gemfile removing
syntax_suggestby @joeldrapper in #423 - Float should go through object formatters by @joeldrapper in #422
- Rename private methods by @joeldrapper in #424
- Define underscore prefixed tag methods by @joeldrapper in #425
- Remove experimental Turbo elements by @joeldrapper in #426
- Add experimental
DeferredRendermodule by @joeldrapper in #427 - Use new
...argument forwarding by @joeldrapper in #430 - Implement
DeferredRenderwith newaround_contenthook by @joeldrapper in #431 - Revert "Implement
DeferredRenderwith newaround_contenthook" by @joeldrapper in #432 - Add
Phlex::HTML#renderfor rendering a component into a String by @marcoroth in #429 - Improve
DeferredRenderby @joeldrapper in #436 - Don't capture blocks when yielding by @joeldrapper in #437
- Add
clearwaterproject to "Prior Art" section by @marcoroth in #439 - Detect block passed to
newby @joeldrapper in #438 - Yield self when vanishing
DeferredRenderblocks by @joeldrapper in #441 - Ensure captures return original target by @joeldrapper in #444
- Fix ensured capture return values by @joeldrapper in #445
- Fix using keyword arguments in initializer by @willcosgrove in #447
- Unbuffered decorator by @joeldrapper in #448
- Update experimental warning by @joeldrapper in #450
- Warn when redefining underscored methods by @joeldrapper in #449
New Contributors
- @willcosgrove made their first contribution in #447
Full Changelog: 1.1.0...1.2.0