I’d like to have a generic, abstract (I apologise for the OO terminology component, which takes care of all the styling and can be rendered by calling its function from specific “subclass instances” providing some additional bits like title, size and the actual content. This seem to work well until I want to change the default @inner_block. It seems to have some structure:
which makes it hardly override-able… or? Is there a way to keep the “default slot” being default but overriding its content when needed rather than using some conditionals?
I understand I can rewrite things and work this around but would like to know that I have to
I’m playing around with a similar idea on a attempt to make it easier to use HotwireTurbo on phoenix.
For now I have the following hack to reuse a generic component. Not really sure yet what parameters the inner_block function expects so I’m just ignoring them for now.
Ane example - let’s say I pass default content inside a component. Something like
<.button>
This renders <strong>inside</strong> the button!
</.button>
The above taken from the docs mentioned above. And in the majority of cases it is OK. F. e.
<.queue_widget>
The queue is empty - nothing to see here
</.queue_widget>
But on some rare occasions there is something in the queue and needs to be put out. One of the ways to do it would be to override the default content provided in @inner_block (there is no pubsub involved and the widget does not update itself on change). I understand it is not the only way to do it but would like to know if this is at all feasible.
The template is currently common for all widgets and is located in the “abstract” one. Specific widgets provide their name, size and default content, which I’d like to override on occasions but want to keep the abstract widget generic so that it doesn’t have to “know” what it is rendering. I can do generic “assign” and use that. That’s what I am now doing but I feel like running in circles around the original idea of just assigning stuff to @inner_block
I think I still don’t know exactly what you’re attempting here. If your abstract components is meant to be abstract, then it shouldn’t be involved in markup at all. It feels a bit strange to try to abstract on one hand and on the other still try to break out of the abstraction.
Hmm… The abstraction means that all things common go there - widget has a bounding box, header, title, some styling, etc. Specific widgets provide values for the variable parameters. At least that’s how I used to do those things. Here I have a function called widget/1 and specific ones like queue_widget/1 which assigns its title, size etc. calling the “abstract” widget/1 in the end for rendering the actual output
My directly delegating to widget as a function I feel like you’re leaving options lying on the ground. <.queue_widget> and <.widget> can have different views on @inner_block.
def queue_widget(assigns) do
assigns =
assigns
|> assign(:widget_title, gettext("Queue"))
|> assign(:widget_id, "queue")
|> assign_new(:inner_block, fn -> [] end)
~H"""
<.widget>
<%= if Enum.empty?(@inner_block) do %>
…queue widget default
<% else %>
<%= render_slot(@inner_block) %>
<% end %>
</.widget>
"""
end
That would be almost perfect but how does the “abstract” widget get the other assigns if I don’t call it as a function but rather the way you wrote above?
Thank you, Benjamin. While this is noticeably more verbose than what I admittedly was hoping for. It still looks to me more “correct” than what I do now. And… it can probably be shortened to:
<.widget {assigns_to_attributes(assigns, [])}>
without introducing the pass_through part, can’t it? Seems to work and is more pleasing to the eye
Might be Captain Obvious but noticed that doing simply
<.widget {assigns}>
seems to work too. Tried to check this against change tracking docs but am still unsure about the change tracking reliability in such case/usage. Do you have any pointers?
Generally speaking, avoid accessing variables inside LiveViews, as code that access variables is always executed on every render. This also applies to the assigns variable.
I’d find it useful if LiveView added something like @assigns for cases where you need it, like when using assigns_to_attributes.
I read that paragraph in the docs too. But I couldn’t be sure whether doing things like <.widget {assigns}> constitutes “accessing variables inside LiveViews” - IOW whether it is equivalent to e. g. <%= assigns.my_assign %> or more to calling widget(assigns) and how do the two differ from change tracking perspective. The second source you linked to, lists the two constructs next to each other though. OTOH I am still not sure how does the approach, which @LostKobrakai suggested (with assigns_to_attributes/2 and @pass_thorugh) sidestep the issue.