Giter Site home page Giter Site logo

Comments (8)

allmarkedup avatar allmarkedup commented on May 25, 2024 1

@willcosgrove okay so after a bit of trial and error this change to the preview template seems to do the trick:

<% if @render_args[:component] %>
  <% if @render_args[:component].is_a?(Phlex::HTML) %>
    <%== @render_args[:component].call(view_context: self, &@render_args[:block]) %>
  <% else %>
    <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
  <% end %>
<% else %>
  <%= render(@render_args[:template], **@render_args[:locals], &@render_args[:block]) %>
<% end %>

Does calling the instantiated Phlex component like that look correct to you? Anything I'm missing there that you can spot?

from lookbook.

allmarkedup avatar allmarkedup commented on May 25, 2024 1

@willcosgrove okay great, many thanks for the explanation. And makes sense about checking for Phlex::SGML, good shout.

Appreciate you taking the time to walk me through this and I'll get a fix up for this now 👍

from lookbook.

allmarkedup avatar allmarkedup commented on May 25, 2024

Hey @willcosgrove, thanks for this. I have a few questions if you don't mind - I'm not very familiar with the Phlex codebase so I'm just trying to get my head around the issue. I'm also far from an expert when it comes to things like output buffers so apologies if I'm a little slow on the pick up!

So, as a bit of background info, the render method in the Lookbook::Preview class is actually a bit of misdirection. The components do not actually get rendered there - the render method generates a hash of data, which includes the instantiated component that was passed to the render method initially.

The actual component rendering is done via a controller action, which renders this template:

<% if @render_args[:component] %>
<%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
<% else %>
<%= render(@render_args[:template], **@render_args[:locals], &@render_args[:block]) %>
<% end %>

The @render_args variable here contains the hash of data generated by the Lookbook::Preview#render method.

So, the instantiated component here is actually being rendered in an ERB template... but I guess the issue is that the block itself is not rendered as ERB so the list.item calls are not outputting to it's internal buffer. Have I got that correct?

I assume therefore (and I realise that it is unlikely that anyone would do this, but just for my own understanding) that if one instantiates a component in a controller action, passes that to the view and renders it there, that will also not work? For example:

class FooController < ApplicationController
  def show
    @list = List.new do |list|
      list.item { "Hello" }
      list.item { "World" }
    end
  end
end
<!-- foo/show.html.erb -->
<%= render @list %>

and that to render Phlex components in a "ruby context" the call method should be used

Do you think you could provide me with a few pointers as to what this might look like? I'm going to try to dig into the Phlex codebase now to try and figure out what to best do about this but any suggestions would be much appreciated.

from lookbook.

willcosgrove avatar willcosgrove commented on May 25, 2024

So, the instantiated component here is actually being rendered in an ERB template... but I guess the issue is that the block itself is not rendered as ERB so the list.item calls are not outputting to it's internal buffer. Have I got that correct?

Yes, you're exactly right. The block being passed to render is not from an ERB context, but from the method inside the user's ComponentPreview class.

I looked into having Phlex detect this context but detecting it is error prone (it varies by templating language used) and is slow (it requires inspecting the binding of the block which is not a fast process). Because it would be hard to get this right 100% of the time, and because it would slow down the render path, @joeldrapper decided that it should be the responsibility of the code doing the rendering to indicate what type of context you want the component rendered in.

render_in (which is what Rails is calling when you pass it to render in the view) is going to assume that you're inside a view file, and use an Unbuffered component.

call is what Phlex uses internally to render other Phlex views, and is what should be used to render within a "ruby context".

Here's an example of how you might be able to modify the view code you posted above to make it use call instead of render_in for Phlex views:

<% if @render_args[:component] %>
  <% case @render_args[:component] %>
  <% when Phlex::SGML %>
    <%= raw(@render_args[:component].call(view_context: self, &@render_args[:block])) %>
  <% else %>
    <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
  <% end %>
<% else %> 
  <%= render(@render_args[:template], **@render_args[:locals], &@render_args[:block]) %> 
<% end %> 

I don't think @render_args[:args] is needed in the Phlex case, but I'm not 100% sure what those would be, so I could be wrong.

from lookbook.

willcosgrove avatar willcosgrove commented on May 25, 2024

@allmarkedup your code looks perfect. The only thing I might suggest is checking for inheritance of Phlex::SGML instead of Phlex::HTML. SGML is higher up the chain, and also includes SVG views.

We may have an even higher abstract class later if Phlex branches out into other types of views, like JSON or XML, but for now Phlex::SGML is at the top of the hierarchy.

from lookbook.

joeldrapper avatar joeldrapper commented on May 25, 2024

Thanks for writing this up @willcosgrove. @allmarkedup your fix looks good. Let me know if I can be of any help.

from lookbook.

allmarkedup avatar allmarkedup commented on May 25, 2024

Thanks again for the help, I've just merged the PR so will close this down now 👍

from lookbook.

stephannv avatar stephannv commented on May 25, 2024

I'm facing a similar issue with Lookbook + Phlex:

Phlex allows you to write something like this:

render Views::Phlex::ListExample.new do |list|
  list.item { "Hello" }  
  render(Views::Phlex::Item.new) { "Help!"}
  list.item { "World" }
end

But when we write this example on a Lookbook::Preview class, it doesn't work.

The render(Views::Phlex::Item.new) { "Help!" } returns a hash because it is using render from Lookbook::Preview class and the component isn't rendered. To solve that I could write something like this:

render Views::Phlex::ListExample.new do |list|
  list.item { "Hello" }  
  list.render(Views::Phlex::Item.new) { "Help!"}
  list.item { "World" }
end

But it has two problems:

  • On source tab, will display component usage list.render ..., but it isn't required, so other devs could understand that unnecessary code is required to build the component
  • I cannot use my component helpers. I added helpers to Lookbook::Preview, eg.:
module DesignSystemHelpers
  def my_button(*args, **kwargs, &block)
    render DesignSystem::ButtonComponent.new(*args, **kwargs, &block)
  end
  
  def my_card(...)
    ...
  end
  
  ...
end

# on initializers
config.to_prepare do
  Lookbook::Preview.include DesignSystemHelpers
end

So something like this doesn't work on Lookbook:

def default
  my_card do |card| # this will be rendered
    card.actions do # this will be rendered
      my_button(...) # this will NOT be rendered
      my_button(...) # this will NOT be rendered
    end
  end
end

This is important because I think Lookbook is a source of how developers should use our components.

And the other problem is that I cannot do that:

render Views::Phlex::ListExample.new do |list|
  h1 { "My list" }
  list.item { "Hello" }  
  list.item { "World" }
end

It will raise undefined method 'h1' for an instance of MyComponentPreview.

from lookbook.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.