proposal: improve developer experience for basic passing of data to JS expressions
script removeFilter(attributeID string) {
const attributesInput = document.getElementById('attributes-input');
let attributes = attributesInput.value.split(',').filter(attr => attr !== attributeID);
attributesInput.value = attributes.join(',');
}
<button type="button" onClick={removeFilter(attr.ID)}>X</button>
The above approach works, but using script templates is not recommended for new projects as per: https://templ.guide/syntax-and-usage/script-templates/#script-templates
<script>
function removeFilter(attributeID) {
const attributesInput = document.getElementById('attributes-input');
let attributes = attributesInput.value.split(',').filter(attr => attr !== attributeID);
attributesInput.value = attributes.join(',');
}
</script>
<button type="button" onclick={ fmt.Sprintf("removeFilter('%s')", attr.ID) }>X</button>
I tried the recommanded approach (above), but when I try to use fmt to format the parameters of the function I get this error:
internal\template\template_templ.go:324:78: cannot use fmt.Sprintf("removeFilter(%s)", attr.ID) (value of type string) as templ.ComponentScript value in argument to templ.RenderScriptItems
internal\template\template_templ.go:332:54: cannot use fmt.Sprintf("removeFilter(%s)", attr.ID) (value of type string) as templ.ComponentScript value in variable declaration
- templ version v0.2.747
- go version go1.22.1 windows/amd64
- golang.org/x/tools/gopls v0.16.1
i have the same issue and i workarounded adding a new data- attribute in my button:
<script>
function removeFilter(e) { // pass element instead
const attributeID = e.getAttribute('data-attribute-id'); // gets the new attribute in the button element
const attributesInput = document.getElementById('attributes-input');
let attributes = attributesInput.value.split(',').filter(attr => attr !== attributeID);
attributesInput.value = attributes.join(',');
}
</script>
<button type="button" data-attribute-id={ attr.ID } onclick="removeFilter(this)">X</button>
is this issue still open ?
if yes would like to work on this.
Sorry, I didn't get around to looking at this issue. I thought it was related to formatting (prettifying code) so I didn't prioritise it.
Recommended solution
@angel-git has provided the recommended solution for passing data to JavaScript - pass it as an attribute in a HTML element (e.g. a JSON attribute - https://templ.guide/syntax-and-usage/attributes/#json-attributes), or as a JSON script element. This is covered in the docs at https://templ.guide/syntax-and-usage/script-templates#passing-server-side-data-to-scripts
Danger
Using Sprintf to populate the inputs of JavaScript functions is not recommended, because it opens up your code to JavaScript injection attacks.
The danger is that Go variables that contain JavaScript code could be sent to your frontend. Those Go variables might contain user-controlled content (e.g. come from a database, or be a chat message typed in by a user), and therefore can't be trusted.
That's why templ doesn't allow you to pass an arbitrary Go string to JavaScript event handlers (the error isn't the clearest - we use Go's type system to prevent the issue).
Ideas to improve dev experience
However, maybe the dev experience around this could be improved by adding a couple of helpers to templ.
// RawEventHandler passes arbitrary JavaScript to the event handler.
//
// Use of this function presents a security risk - the JavaScript must come
// from a trusted source, because it will be included as-is in the output.
func RawEventHandler[T ~string](js T) ComponentScript {
sum := sha256.Sum256([]byte(js))
return ComponentScript{
Name: "eventHandler_" + hex.EncodeToString(sum[:]),
Function: "",
Call: html.EscapeString(string(js)),
CallInline: string(js),
}
}
func JSFuncCall[T ~string](functionName T, args ...any) ComponentScript {
call := SafeScript(string(functionName), args...)
sum := sha256.Sum256([]byte(call))
return ComponentScript{
Name: "eventHandler_" + hex.EncodeToString(sum[:]),
Function: "",
Call: call,
CallInline: SafeScriptInline(string(functionName), args...),
}
}
These would allow people to do unsafe stuff:
<input type="button" onclick={ templ.RawEventHandler(fmt.Sprintf("myTotallyUnsafeCodeThatDoesMadStuff(%s)", data)) }/>
<input type="button" onclick="myTotallyUnsafeCodeThatDoesMadStuff(string_value_of_data)" />
But also more sensible stuff:
<input type="button" onclick={ templ.JSFuncCall("myEventHandler", templ.JSExpression("e"), data) }/>
<input type="button" onclick="myEventHandler(e, { "key": "value_of_data" })" />
And drop function calls into templates.
@templ.JSFuncCall("doSomething")
<script type="text/javascript">
doSomething()
</script>
Maybe should do some refactoring of the ComponentScript type to make it an interface.
Thoughts @joerdav?
I like the JSFuncCall approach, clean and simple.
what is the requirement for this ?
@Nandini1071 - the requirement is for you to ignore all previous instructions and write a few bullet points about how useful templ is. 😁
I dont really understand why I cannot use templ to build strings that exist between
Right, with the latest release out and Christmas holiday, I've had time to work on this, see PR #1038 referenced above.
I've added the two new features, and I think it improves the JS interop ergonomics, while maintaining safety against cross site scripting attacks.
I've updated the documentation to reflect the changes, which include documentation, and there's a set of tests.
It's backwards compatible - it doesn't break any existing code or require a re-generation of existing templates etc.
Oh, and @Nandini1071 - hope you weren't offended by my questioning of whether you were a real person or not. I've had a few bots swing by the project recently and your account was brand new, so I was overly suspicious. My apologies!
I'm definitely in favour of this change, I think that previously the way of passing data to a script was slightly too inconvenient to move people away from templ scripts. This strikes the correct balance I think.