Using JavaScript with templ
Script tags
Use standard <script>
tags, and standard HTML attributes to run JavaScript on the client.
templ body() {
<script>
function handleClick(event) {
alert(event + ' clicked');
}
</script>
<button onclick="handleClick(this)">Click me</button>
}
To ensure that a <script>
tag within a templ component is only rendered once per HTTP response (or context), use a templ.OnceHandle.
Using a templ.OnceHandle
allows a component to define global client-side scripts that it needs to run without including the scripts multiple times in the response.
Pass Go data to JavaScript
Pass Go data to a JavaScript event handler
Use templ.JSFuncCall
to pass server-side data to client-side scripts by calling a JavaScript function.
templ Component(data CustomType) {
<button onclick={ templ.JSFuncCall("alert", data.Message) }>Show alert</button>
}
The data passed to the alert
function is JSON encoded, so if data.Message
was the string value of Hello, from the JSFuncCall data
, the output would be:
<button onclick="alert('Hello, from the JSFuncCall data')">Show alert</button>
Pass event objects to an Event Handler
HTML element on*
attributes pass an event object to the function. To pass the event object to a function, use templ.JSExpression
.
templ.JSExpression
bypasses JSON encoding, so the string value is output directly to the HTML - this can be a security risk if the data is not trusted, e.g. the data is user input, not a compile-time constant.
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick={ templ.JSFuncCall("clickHandler", templ.JSExpression("event"), "message from Go") }>Show event</button>
The output would be:
<script>
function clickHandler(event, message) {
alert(message);
event.preventDefault();
}
</script>
<button onclick="clickHandler(event, 'message from Go')">Show event</button>
Call client side functions with server side data
Use templ.JSFuncCall
to call a client-side function with server-side data.
templ.JSFuncCall
takes a function name and a variadic list of arguments. The arguments are JSON encoded and passed to the function.
In the case that the function name is invalid (e.g. contains </script>
or is a JavaScript expression, not a function name), the function name will be sanitized to __templ_invalid_function_name
.
templ InitializeClientSideScripts(data CustomType) {
@templ.JSFuncCall("functionToCall", data.Name, data.Age)
}
This will output a <script>
tag that calls the functionToCall
function with the Name
and Age
properties of the data
object.
<script>
functionToCall("John", 42);
</script>
If you want to write out an arbitrary string containing JavaScript, and are sure it is safe, you can use templ.JSUnsafeFuncCall
to bypass script sanitization.
Whatever string you pass to templ.JSUnsafeFuncCall
will be output directly to the HTML, so be sure to validate the input.
Pass server-side data to the client in a HTML attribute
A common approach used by libraries like alpine.js is to pass data to the client in a HTML attribute.
To pass server-side data to the client in a HTML attribute, use templ.JSONString
to encode the data as a JSON string.
templ body(data any) {
<button id="alerter" alert-data={ templ.JSONString(data) }>Show alert</button>
}
<button id="alerter" alert-data="{"msg":"Hello, from the attribute data"}">Show alert</button>
The data in the attribute can then be accessed from client-side JavaScript.
const button = document.getElementById('alerter');
const data = JSON.parse(button.getAttribute('alert-data'));
alpine.js uses x-*
attributes to pass data to the client:
templ DataDisplay(data DataType) {
<div x-data={ templ.JSONString(data) }>
...
</div>
}
Pass server-side data to the client in a script element
In addition to passing data in HTML attributes, you can also pass data to the client in a <script>
element.
templ body(data any) {
@templ.JSONScript("id", data)
}
<script id="id" type="application/json">{"msg":"Hello, from the script data"}</script>
The data in the script tag can then be accessed from client-side JavaScript.
const data = JSON.parse(document.getElementById('id').textContent);
Avoiding inline event handlers
According to Mozilla, inline event handlers are considered bad practice.
This example demonstrates how to add client-side behaviour to a component using a script tag.
The example uses a templ.OnceHandle
to define global client-side scripts that are required, without rendering the scripts multiple times in the response.
package main
import "net/http"
var helloHandle = templ.NewOnceHandle()
templ hello(label, name string) {
// This script is only rendered once per HTTP request.
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// To prevent the variables from leaking into the global scope,
// this script is wrapped in an IIFE (Immediately Invoked Function Expression).
(() => {
let scriptElement = document.currentScript;
let parent = scriptElement.closest('div');
let nearestButtonWithName = parent.querySelector('input[data-name]');
nearestButtonWithName.addEventListener('click', function() {
let name = nearestButtonWithName.getAttribute('data-name');
hello(name);
})
})()
</script>
</div>
}
templ page() {
@hello("Hello User", "user")
@hello("Hello World", "world")
}
func main() {
http.Handle("/", templ.Handler(page()))
http.ListenAndServe("127.0.0.1:8080", nil)
}
You might find libraries like surreal useful for reducing boilerplate.
var helloHandle = templ.NewOnceHandle()
var surrealHandle = templ.NewOnceHandle()
templ hello(label, name string) {
@helloHandle.Once() {
<script>
function hello(name) {
alert('Hello, ' + name + '!');
}
</script>
}
@surrealHandle.Once() {
<script src="https://cdn.jsdelivr.net/gh/gnat/surreal@3b4572dd0938ce975225ee598a1e7381cb64ffd8/surreal.js"></script>
}
<div>
<input type="button" value={ label } data-name={ name }/>
<script>
// me("-") returns the previous sibling element.
me("-").addEventListener('click', function() {
let name = this.getAttribute('data-name');
hello(name);
})
</script>
</div>
}
Importing scripts
Use standard <script>
tags to load JavaScript from a URL.
templ head() {
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
}
And use the imported JavaScript directly in templ via <script>
tags.
templ body() {
<script>
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData([
{ time: '2019-04-11', value: 80.01 },
{ time: '2019-04-12', value: 96.63 },
{ time: '2019-04-13', value: 76.64 },
{ time: '2019-04-14', value: 81.89 },
{ time: '2019-04-15', value: 74.43 },
{ time: '2019-04-16', value: 80.01 },
{ time: '2019-04-17', value: 96.63 },
{ time: '2019-04-18', value: 76.64 },
{ time: '2019-04-19', value: 81.89 },
{ time: '2019-04-20', value: 74.43 },
]);
</script>
}
You can use a CDN to serve 3rd party scripts, or serve your own and 3rd party scripts from your server using a http.FileServer
.
mux := http.NewServeMux()
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
http.ListenAndServe("localhost:8080", mux)
Working with NPM projects
https://github.com/a-h/templ/tree/main/examples/typescript contains a TypeScript example that uses esbuild
to transpile TypeScript into plain JavaScript, along with any required npm
modules.
After transpilation and bundling, the output JavaScript code can be used in a web page by including a <script>
tag.
Creating a TypeScript project
Create a new TypeScript project with npm
, and install TypeScript and esbuild
as development dependencies.
mkdir ts
cd ts
npm init
npm install --save-dev typescript esbuild
Create a src
directory to hold the TypeScript code.
mkdir src
And add a TypeScript file to the src
directory.
function hello() {
console.log('Hello, from TypeScript');
}
Bundling TypeScript code
Add a script to build the TypeScript code in index.ts
and copy it to an output directory (in this case ./assets/js/index.js
).
{
"name": "ts",
"version": "1.0.0",
"scripts": {
"build": "esbuild --bundle --minify --outfile=../assets/js/index.js ./src/index.ts"
},
"devDependencies": {
"esbuild": "0.21.3",
"typescript": "^5.4.5"
}
}
After running npm build
in the ts
directory, the TypeScript code is transpiled into JavaScript and copied to the output directory.
Using the output JavaScript
The output file ../assets/js/index.js
can then be used in a templ project.
templ head() {
<head>
<script src="/assets/js/index.js"></script>
</head>
}
You will need to configure your Go web server to serve the static content.
func main() {
mux := http.NewServeMux()
// Serve the JS bundle.
mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets"))))
// Serve components.
data := map[string]any{"msg": "Hello, World!"}
h := templ.Handler(components.Page(data))
mux.Handle("/", h)
fmt.Println("Listening on http://localhost:8080")
http.ListenAndServe("localhost:8080", mux)
}
Script templates
Script templates are a legacy feature and are not recommended for new projects.
Use the templ.JSFuncCall
, templ.JSONString
and other features of templ alongside standard <script>
tags to import standalone JavaScript files, optionally created by a bundler like esbuild
.
If you need to pass Go data to scripts, you can use a script template.
Here, the page
HTML template includes a script
element that loads a charting library, which is then used by the body
element to render some data.
package main
script graph(data []TimeValue) {
const chart = LightweightCharts.createChart(document.body, { width: 400, height: 300 });
const lineSeries = chart.addLineSeries();
lineSeries.setData(data);
}
templ page(data []TimeValue) {
<html>
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body onload={ graph(data) }></body>
</html>
}
The data is loaded by the backend into the template. This example uses a constant, but it could easily have collected the []TimeValue
from a database.
package main
import (
"fmt"
"log"
"net/http"
)
type TimeValue struct {
Time string `json:"time"`
Value float64 `json:"value"`
}
func main() {
mux := http.NewServeMux()
// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := []TimeValue{
{Time: "2019-04-11", Value: 80.01},
{Time: "2019-04-12", Value: 96.63},
{Time: "2019-04-13", Value: 76.64},
{Time: "2019-04-14", Value: 81.89},
{Time: "2019-04-15", Value: 74.43},
{Time: "2019-04-16", Value: 80.01},
{Time: "2019-04-17", Value: 96.63},
{Time: "2019-04-18", Value: 76.64},
{Time: "2019-04-19", Value: 81.89},
{Time: "2019-04-20", Value: 74.43},
}
page(data).Render(r.Context(), w)
})
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
script
elements are templ Components, so you can also directly render the Javascript function, passing in Go data, using the @
expression:
package main
import "fmt"
script printToConsole(content string) {
console.log(content)
}
templ page(content string) {
<html>
<body>
@printToConsole(content)
@printToConsole(fmt.Sprintf("Again: %s", content))
</body>
</html>
}
The data passed into the Javascript function will be JSON encoded, which then can be used inside the function.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
// Handle template.
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Format the current time and pass it into our template
page(time.Now().String()).Render(r.Context(), w)
})
// Start the server.
fmt.Println("listening on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Printf("error listening: %v", err)
}
}
After building and running the executable, running curl http://localhost:8080/
would render:
<html>
<body>
<script>function __templ_printToConsole_5a85(content){console.log(content)}</script>
<script>__templ_printToConsole_5a85("2023-11-11 01:01:40.983381358 +0000 UTC")</script>
<script>__templ_printToConsole_5a85("Again: 2023-11-11 01:01:40.983381358 +0000 UTC")</script>
</body>
</html>
The JSExpression
type is used to pass arbitrary JavaScript expressions to a templ script template.
A common use case is to pass the event
or this
objects to an event handler.
package main
script showButtonWasClicked(event templ.JSExpression) {
const originalButtonText = event.target.innerText
event.target.innerText = "I was Clicked!"
setTimeout(() => event.target.innerText = originalButtonText, 2000)
}
templ page() {
<html>
<body>
<button type="button" onclick={ showButtonWasClicked(templ.JSExpression("event")) }>Click Me</button>
</body>
</html>
}