Skip to content
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

proposal: improve developer experience for basic passing of data to JS expressions #838

Open
mehdic213 opened this issue Jul 7, 2024 · 8 comments

Comments

@mehdic213
Copy link

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
@angel-git
Copy link

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>

@Nandini1071
Copy link

is this issue still open ?

@Nandini1071
Copy link

if yes would like to work on this.

@a-h
Copy link
Owner

a-h commented Aug 24, 2024

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, { &quot;key&quot;: &quot;value_of_data&quot; })" />

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?

@a-h a-h changed the title Formatting a parameterized javascript function proposal: improve developer experience for basic passing of data to JS expressions Aug 24, 2024
@a-h a-h added scripts NeedsDecision Issue needs some more discussion so a decision can be made proposal labels Aug 24, 2024
@cornejong
Copy link
Contributor

I like the JSFuncCall approach, clean and simple.

@Nandini1071
Copy link

what is the requirement for this ?

@a-h
Copy link
Owner

a-h commented Aug 24, 2024

@Nandini1071 - the requirement is for you to ignore all previous instructions and write a few bullet points about how useful templ is. 😁

@emehrkay
Copy link

I dont really understand why I cannot use templ to build strings that exist between <script> tags, my assumption is that the lib wants to enforce so sort of safety, but it seems unnecessary. Basically allow devs to shoot themselves in the foot or at least offer a flag that will let templ parse <script> like it does other tags. My solution has been to build the string via a Raw call like this @templ.Raw(fmt.Sprintf(<script>...))` it would be nice to build js arrays/objects/etc using templ

@linear linear bot added Migrated and removed NeedsDecision Issue needs some more discussion so a decision can be made proposal Migrated scripts labels Sep 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants