-
-
Notifications
You must be signed in to change notification settings - Fork 298
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
feature: add templ.HasChildren function #975
Comments
Good idea. I propose a design like this: func Children(ctx context.Context) (ok bool, c templ.Component) {
_, v := getContext(ctx)
if v.children == nil {
return false, NopComponent
}
return true, *v.children
} So you could use it as: if children, ok := templ.Children(ctx); ok {
@children
} else {
<div>No children</div>
} This wouldn't replace the To implement this, we'd need some generator tests to ensure that Happy to take a PR for this if someone else wants to implement, also happy to hear comments on the proposal, including naming the function something else. |
Acceptable, it looks like due to the early calculation of child components it would be logical to use a flag whether the child components were initially passed or not, because the order of the call would then matter. |
I'm not sure I completely follow. Are you suggesting a different API? We have an experimental package at https://github.com/templ-go/x - that might be a good place for a proof-of-concept design. |
How would you implement that in a templ List(){} like annotation? I heavily using this kind of structure as the docs are telling, but I didn't find anything on how to get from a code component to a templ component. |
@pulsone21 it would be used as follows: templ List() {
if children, ok := templ.Children(ctx); ok {
<ul>
@children
</ul>
} else {
<div>No children</div>
}
}
templ Page() {
@List() {
<li>A</li>
<li>B</li>
<li>C</li>
}
} |
I have implemented the suggested change into my fork, see here: The unit Tests are working, run However i have the issue that the generator test I struggle a bit to reverse engineer how this actually works, so i come to ask for some help. PS: Prior a eventual PR to the main i would squash everything and clean it up. |
Seems like a bug to me. I wouldn't expect the context is cleared after I get a value out of it. Since I see now the clear children call I wonder if this is why the initial proposal returned also the children's not only a bool. |
I have a proposal that I previously voiced - freezing the flag in terms of its initial state regarding the child elements. We are effectively freezing the variable storing the number of elements (someone might need to know this number), bypassing the cleanup of the child elements. I understand why cleaning is done so as not to confuse nested components, but it creates a problem with further work. Since code generation exists, why not use its capabilities? |
The information about child documents is being lost from the context before an attempt to retrieve it at the code level because code generation creates a precedent for clearing information about the presence of child elements. |
I just updated the The final template code would look like this: ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
if ok := templ.HasChildren(ctx); ok {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("No children")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `generator/test-has-children/template.templ`, Line: 9, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
ctx = templ.ClearChildren(ctx)
return templ_7745c5c3_Err In my local testing it worked so far: go test ./...
ok github.com/a-h/templ 0.427s
? github.com/a-h/templ/cfg [no test files]
ok github.com/a-h/templ/benchmarks/templ 0.256s [no tests to run]
? github.com/a-h/templ/cmd/templ/generatecmd/run/testprogram [no test files]
? github.com/a-h/templ/cmd/templ/generatecmd/sse [no test files]
ok github.com/a-h/templ/cmd/templ 0.599s
ok github.com/a-h/templ/cmd/templ/fmtcmd 0.877s
ok github.com/a-h/templ/cmd/templ/generatecmd 0.306s
ok github.com/a-h/templ/cmd/templ/generatecmd/modcheck 1.317s
ok github.com/a-h/templ/cmd/templ/generatecmd/proxy 1.119s
? github.com/a-h/templ/cmd/templ/infocmd [no test files]
? github.com/a-h/templ/cmd/templ/lspcmd/httpdebug [no test files]
? github.com/a-h/templ/cmd/templ/lspcmd/lspdiff [no test files]
? github.com/a-h/templ/cmd/templ/lspcmd/pls [no test files]
? github.com/a-h/templ/cmd/templ/sloghandler [no test files]
? github.com/a-h/templ/cmd/templ/testproject [no test files]
? github.com/a-h/templ/cmd/templ/visualize [no test files]
? github.com/a-h/templ/examples/content-security-policy [no test files]
? github.com/a-h/templ/examples/hello-world-ssr [no test files]
? github.com/a-h/templ/examples/hello-world-static [no test files]
? github.com/a-h/templ/examples/syntax-and-usage/components [no test files]
? github.com/a-h/templ/generator/htmldiff [no test files]
ok github.com/a-h/templ/cmd/templ/generatecmd/run 3.566s
ok github.com/a-h/templ/cmd/templ/generatecmd/symlink 1.809s
ok github.com/a-h/templ/cmd/templ/generatecmd/test-eventhandler 2.045s
? github.com/a-h/templ/get-version [no test files]
? github.com/a-h/templ/storybook [no test files]
ok github.com/a-h/templ/cmd/templ/generatecmd/testwatch 20.074s
ok github.com/a-h/templ/cmd/templ/generatecmd/watcher 3.375s
ok github.com/a-h/templ/cmd/templ/imports 5.410s
ok github.com/a-h/templ/cmd/templ/lspcmd 20.319s
ok github.com/a-h/templ/cmd/templ/lspcmd/proxy 1.489s
ok github.com/a-h/templ/cmd/templ/processor 1.241s
ok github.com/a-h/templ/examples/blog 1.733s
ok github.com/a-h/templ/generator 1.467s
ok github.com/a-h/templ/generator/test-a-href 1.868s
ok github.com/a-h/templ/generator/test-attribute-errors 1.857s
ok github.com/a-h/templ/generator/test-attribute-escaping 2.356s
ok github.com/a-h/templ/generator/test-call 2.550s
ok github.com/a-h/templ/generator/test-cancelled-context 2.781s
ok github.com/a-h/templ/generator/test-complex-attributes 2.462s
ok github.com/a-h/templ/generator/test-constant-attribute-escaping 2.407s
ok github.com/a-h/templ/generator/test-context 1.382s
ok github.com/a-h/templ/generator/test-css-expression 1.385s
ok github.com/a-h/templ/generator/test-css-middleware 1.438s
ok github.com/a-h/templ/generator/test-css-usage 1.120s
ok github.com/a-h/templ/generator/test-doctype 1.012s
ok github.com/a-h/templ/generator/test-element-attributes 1.340s
ok github.com/a-h/templ/generator/test-elseif 1.348s
ok github.com/a-h/templ/generator/test-for 1.849s
ok github.com/a-h/templ/generator/test-form-action 1.822s
ok github.com/a-h/templ/generator/test-go-comments 1.769s
ok github.com/a-h/templ/generator/test-go-template-in-templ 1.778s
ok github.com/a-h/templ/generator/test-has-children 1.825s
ok github.com/a-h/templ/generator/test-html 1.579s
ok github.com/a-h/templ/generator/test-html-comment 1.682s
ok github.com/a-h/templ/generator/test-if 1.280s
ok github.com/a-h/templ/generator/test-ifelse 1.272s
ok github.com/a-h/templ/generator/test-import 1.430s
ok github.com/a-h/templ/generator/test-method 1.509s
ok github.com/a-h/templ/generator/test-once 1.500s
ok github.com/a-h/templ/generator/test-only-scripts 1.492s
ok github.com/a-h/templ/generator/test-raw-elements 1.644s
ok github.com/a-h/templ/generator/test-script-inline 1.734s
ok github.com/a-h/templ/generator/test-script-usage 1.741s
ok github.com/a-h/templ/generator/test-script-usage-nonce 1.966s
ok github.com/a-h/templ/generator/test-spread-attributes 2.021s
ok github.com/a-h/templ/generator/test-string 2.025s
ok github.com/a-h/templ/generator/test-string-errors 2.245s
ok github.com/a-h/templ/generator/test-switch 2.237s
ok github.com/a-h/templ/generator/test-switchdefault 2.168s
ok github.com/a-h/templ/generator/test-templ-element 1.979s
ok github.com/a-h/templ/generator/test-templ-in-go-template 1.941s
ok github.com/a-h/templ/generator/test-text 1.780s
ok github.com/a-h/templ/generator/test-text-whitespace 1.727s
ok github.com/a-h/templ/generator/test-void 1.653s
ok github.com/a-h/templ/generator/test-whitespace-around-go-keywords 1.498s
ok github.com/a-h/templ/parser/v2 1.496s
ok github.com/a-h/templ/parser/v2/goexpression 1.501s
ok github.com/a-h/templ/runtime 1.466s
ok github.com/a-h/templ/safehtml 1.502s
ok github.com/a-h/templ/turbo 1.630s But to be honest, i have no idea what are the possible implications of that change really are. |
I agree, the children passing behaviour may need to be modified under the hood for this to work. Just to add to the context, the early clearing of the context is done to avoid accidental passing of children to nested components. Here is a modified version of your test to show the flaw. There is a new component package testhaschildren
templ shouldNotRenderChildren() {
if ok := templ.HasChildren(ctx); ok {
<p>Should not appear in result</p>
{ children... }
<p>Should not appear in result end</p>
}
}
templ list() {
@shouldNotRenderChildren()
if ok := templ.HasChildren(ctx); ok {
<ul>
{ children... }
</ul>
} else {
<p>No children</p>
}
@shouldNotRenderChildren()
}
templ render(names []string) {
<div>
@list() {
for _, n := range names {
<li>{ n }</li>
}
}
</div>
} The result: <div>
<p>Should not appear in result</p>
<li>Foo</li>
<li>Bar</li>
<li>Deez</li>
<p>Should not appear in result end</p>
<p>No children</p>
</div> I need to have a longer think about this one, but thankyou for the discussion to tease out this issue! Any other suggestions let me know! |
Thanks for the hint didn't see that coming. If you need a hand or something I can support let me know. I just want to have this feature really bad 😅 |
I found a workaround which, could also be the final solution.... templ list() {
<ul>
{ children... }
</ul>
}
templ ListWrapper(users []User){
@list(){
if len(users) > 0 {
for _, u := range users {
<p>u.Name</p>
}
} else {
<p>No user in the list</p>
}
}
} With that approach there is no need to change the children passing behaviour and it solves the same issue. I think both ways are fine... my new approach would need some documentation updates. if we go with this, im happy to write it. |
Some components should have a default display if no child is passed, but the problem is that when calling
getChildren(ctx)
.But I get
NopComponent
. This is unacceptable because there is a nil check in the same function. I can't compare and understand whether there are child components or not.Please add the
HasChildren(ctx)
function to the release, which will give an understanding.So, i want to do like this one:
The text was updated successfully, but these errors were encountered: