I’m not sure what happened, once I wrote the below things run smoothly:
// Serve wasm_exec.js file with correct MIME type
http.HandleFunc("/scripts/wasm_exec.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/javascript")
http.ServeFile(w, r, "/scripts/wasm_exec.js")
})
// Serve main.wasm file with correct MIME type
http.HandleFunc("/wasm/main.wasm", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/wasm")
http.ServeFile(w, r, "/wasm/main.wasm")
})
Later on I committed them, and the app still running smoothly!!
Below is my full code:
// main.go
package main
import (
"html/template"
"log"
"net/http"
)
// Define user access levels
var userAccessLevels = map[string][]string{
"manager": {"edit"},
"auditor": {"readonly"},
"limitedAccessUser": {"readonly"},
"supervisor": {"edit", "no-access"},
}
// Sample data for the table
var tableData = []struct {
ID int
Name string
Role string
Email string
}{
{1, "John Doe", "manager", "john@example.com"},
{2, "Jane Doe", "auditor", "jane@example.com"},
{3, "Alice", "limitedAccessUser", "alice@example.com"},
{4, "Bob", "supervisor", "bob@example.com"},
}
func main() {
// using GET /{$} mean / only, but using GET / mean / and anything after it which may cause routes conflict
http.Handle("GET /home", loggingMiddleware(http.HandlerFunc(homePage)))
http.Handle("GET /base", loggingMiddleware(http.HandlerFunc(baseHandler)))
http.HandleFunc("GET /{$}", handler)
// // Serve wasm_exec.js file with correct MIME type
// http.HandleFunc("/scripts/wasm_exec.js", func(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("Content-Type", "application/javascript")
// http.ServeFile(w, r, "/scripts/wasm_exec.js")
// })
// // Serve main.wasm file with correct MIME type
// http.HandleFunc("/wasm/main.wasm", func(w http.ResponseWriter, r *http.Request) {
// w.Header().Set("Content-Type", "application/wasm")
// http.ServeFile(w, r, "/wasm/main.wasm")
// })
//http.Handle("/", http.FileServer(http.Dir("./static")))
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
// Start the server with CSP headers
http.ListenAndServe(":8080", nil)
}
func baseHandler(w http.ResponseWriter, r *http.Request) {
// Define templates
//t := template.Must(template.ParseFiles("template/base.html", "template/body.html"))
t, err := template.ParseFiles("template/base.html", "template/body.html") //)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Execute base template
if err := t.Execute(w, nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func handler(w http.ResponseWriter, r *http.Request) {
t, err := template.ParseFiles("template/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
t.Execute(w, nil)
}
func homePage(w http.ResponseWriter, r *http.Request) {
userType := "supervisor"
accessLevels, ok := userAccessLevels[userType]
if !ok {
http.Error(w, "Invalid user type", http.StatusBadRequest)
return
}
log.Println(accessLevels)
t, err := template.ParseFiles("template/homePage.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := struct {
Title string
Message string
Roles []string
Access map[string]string
Table []struct {
ID int
Name string
Role string
Email string
}
}{
Title: "Welcome",
Message: "This is the Home Page",
Roles: accessLevels,
Access: makeAccessMap(accessLevels),
Table: tableData,
}
t.Execute(w, data)
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("Request received: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Println("Request handled successfully")
})
}
func makeAccessMap(accessLevels []string) map[string]string {
accessMap := make(map[string]string)
for _, level := range accessLevels {
accessMap[level] = level
}
return accessMap
}
And the Web Assembly files are built from:
// base.go
//go:build js && wasm
package main
import (
"encoding/json"
"fmt"
"syscall/js"
)
var (
global js.Value
document js.Value
)
func main() {
global = js.Global()
document = global.Get("document") // Cache document for efficiency
global.Set("handleData", js.FuncOf(handleData))
global.Set("notifyme", js.FuncOf(notifyme))
// global.Set("clickCallback", js.FuncOf(clickCallback))
select {} // Block main thread
}
func handleData(this js.Value, args []js.Value) interface{} {
if len(args) < 2 {
return nil // Handle invalid number of arguments gracefully
}
num := args[0].Int()
text := args[1].String()
data := map[string]interface{}{
"number": num,
"text": text,
}
jsonData, err := json.Marshal(data)
if err != nil {
// Handle potential marshalling errors (optional)
log(fmt.Sprintf("Error: %v", err))
return nil
}
addElement(jsonData)
return string(jsonData)
}
func notifyme(this js.Value, args []js.Value) interface{} {
script := `
new function() {
let data = "just a reminder";
let para = document.createElement("p");
let node = document.createTextNode(data);
para.appendChild(node);
document.body.appendChild(para);
}();
`
global.Call("eval", script)
log("a Just reminder should be printed at the page")
return nil
}
func log(message string) {
js.Global().Get("console").Call("log", message)
}
func createElement(tagName string) js.Value {
return js.Global().Get("document").Call("createElement", tagName)
}
func addElement(jsonData []byte) {
var parsedData map[string]interface{}
err := json.Unmarshal(jsonData, &parsedData)
if err != nil {
// Handle potential unmarshalling errors (optional)
return
}
text := parsedData["text"].(string)
num := parsedData["number"].(float64)
node := document.Call("createTextNode", fmt.Sprintf("%v: What is the %.2f?", text, num)) // Format number with 2 decimal places
para := createElement("p")
para.Call("appendChild", node)
// Create a js.Func object for clickCallback
clickCallbackFunc := js.FuncOf(clickCallback)
addEventListener(para, "click", clickCallbackFunc)
body := document.Get("body")
body.Call("appendChild", para)
}
func addEventListener(el js.Value, event string, callback js.Func) {
el.Call("addEventListener", event, callback)
}
func clickCallback(this js.Value, args []js.Value) interface{} {
log("Para had been clicked")
return nil
}
// $env:GOOS = "js"
// $env:GOARCH = "wasm"
// $CGO_LDFLAGS="-Wl,-LTO"
// go build -ldflags="-s -w" -o base.wasm -tags js,wasm base.go
// go build -o base.wasm -tags js,wasm base.go
And
// body.go
//go:build js && wasm
package main
import (
"syscall/js"
)
var (
global js.Value
)
func init() {
global = js.Global()
}
func main() {
global.Set("handleClick", js.FuncOf(handleClick))
select {} // Block main thread
}
func handleClick(this js.Value, args []js.Value) interface{} {
alert("Hello, world!") // Use custom alert function
return nil
}
func alert(message string) {
global.Call("alert", message)
}
// $env:GOOS = "js"
// $env:GOARCH = "wasm"
// $CGO_LDFLAGS="-Wl,-LTO"
// go build -ldflags="-s -w" -o body.wasm -tags js,wasm body.go
// go build -o body.wasm -tags js,wasm body.go
The generated wasm files had been moved to the folder static/wasm
And in the static/script
folder I have:
// wasm_load.js
const modules = {};
let wasm;
async function loadWasmModule(module) {
modules[module] = new Go();
try {
const response = await fetch(`/static/wasm/${module}.wasm`);
const bytes = await response.arrayBuffer();
const result_1 = await WebAssembly.instantiate(bytes, modules[module].importObject);
wasm = result_1.instance;
modules[module].run(wasm);
} catch (error) {
console.error(`Failed to load wasm module ${module}:`, error);
}
}
The template files used are:
// template/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Base Template</title>
<script src="/static/scripts/wasm_exec.js"></script>
<script src="/static/scripts/wasm_load.js"></script>
</head>
<body>
<header>
<h1>Welcome to My Website</h1>
</header>
<main>
{{template "content" .}}
</main>
<footer>
<p>© 2024 My Website. All rights reserved.</p>
<button onclick="notifyme()">notifyme</button>
</footer>
</body>
<script>
loadWasmModule('base')
</script>
</html>
And
//template/body.js
{{define "content"}}
<h2>Body Content</h2>
<button onclick="handleClick()">Click me</button>
<p>This is the content of the body template.</p>
<button onclick="callHandleData()">handleData</button>
<script>
loadWasmModule('body')
function callHandleData() {
let data = handleData(46, "Hello, World!");
// Parse the data using JSON.parse // Parse the data using JSON.parse
let parsedData = JSON.parse(data); // Double stringify for safety
// Access the text property from the parsed object
let text = parsedData.text;
let num = parsedData.number;
alert(data)
alert(text + " What is the "+ num); // Or use text for further manipulation
}
</script>
{{end}}
And
//template.index.html
{{define "content"}}
<h2>Body Content</h2>
<button onclick="handleClick()">Click me</button>
<p>This is the content of the body template.</p>
<button onclick="callHandleData()">handleData</button>
<script>
loadWasmModule('body')
function callHandleData() {
let data = handleData(46, "Hello, World!");
// Parse the data using JSON.parse // Parse the data using JSON.parse
let parsedData = JSON.parse(data); // Double stringify for safety
// Access the text property from the parsed object
let text = parsedData.text;
let num = parsedData.number;
alert(data)
alert(text + " What is the "+ num); // Or use text for further manipulation
}
</script>
{{end}}
And
//template/homePage.html
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}}</title>
<style>
/* CSS styles here */
table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
th, td {
padding: 3px; /* Adjusted padding for narrower rows */
border: 1px solid #ddd;
text-align: left;
width: 150px; /* Set fixed width for cells */
overflow: hidden; /* Hide overflow content */
white-space: nowrap; /* Prevent wrapping */
}
tr:nth-child(even) {
background-color: #f2f2f2;
}
/* tr:hover {
background-color: #ddd;
} */
th {
background-color: #4CAF50;
color: white;
}
.editable {
background-color: #fff;
}
.editable:focus {
background-color: #f0f0f0;
}
/* Define individual widths for columns */
.col1 {
width: 100px; /* Width for ID column */
}
.col2 {
width: 200px; /* Width for Name column */
}
.col3 {
width: 150px; /* Width for Role column */
}
.col4 {
width: 250px; /* Width for Email column */
}
/* Style for the first column */
.first-column {
background-color: #ffcccb; /* Light red background color */
}
/* Hover effect for table cells */
tbody tr:hover td,
tbody tr td:hover {
background-color: #ffe5cc; /* Light orange background color */
}
@media screen and (max-width: 600px) {
table {
border: 0;
}
th, td {
border: none;
display: block;
padding: 3px; /* Adjusted padding for narrower rows */
width: 100%; /* Set width to 100% for full width in smaller screens */
}
th {
background-color: #4CAF50;
color: white;
text-align: left;
}
}
</style>
</head>
<body>
<h1>{{.Title}}</h1>
<p>{{.Message}}</p>
<p>User roles are: {{.Roles}}</p>
<div>
{{range $role, $access := .Access}}
<p>Role: {{$role}}, Access: {{$access}}</p>
{{end}}
</div>
{{ $readOnly := false }}
{{ $isVisible := true }}
{{ range .Roles }}
{{ if eq . "no-access" }}
{{ $isVisible = false }}
{{ end }}
{{ if eq . "readonly" }}
{{ $readOnly = true }}
{{ end }}
{{ end }}
{{ if $isVisible }}
<div data-access="{{if $readOnly}} readonly {{end}}">
<input type="text" {{if $readOnly}} readonly {{end}} placeholder="Enter text here">
</div>
{{ else }}
<p>This content is not accessible.</p>
{{ end }}
<table border="1">
<tr>
<th class="col1">ID</th>
<th class="col2">Name</th>
<th class="col3">Role</th>
{{ if $isVisible }}
<th class="col4" data-access="{{if $readOnly}}readonly{{end}}">Email</th>
{{ end }}
</tr>
{{range .Table}}
<tr>
<td contenteditable="true" class="editable col1 first-column">{{.ID}}</td>
<td contenteditable="true" class="editable col2">{{.Name}}</td>
<td contenteditable="true" class="editable col3">{{.Role}}</td>
{{ if $isVisible }}
<td contenteditable="true" class="editable col4" data-access="{{if $readOnly}}readonly{{end}}">{{.Email}}</td>
{{ end }}
</tr>
{{end}}
</table>
<script>
// Add event listeners to each editable cell
const editableCells = document.querySelectorAll('.editable');
editableCells.forEach(cell => {
cell.addEventListener('input', function() {
// Add your logic here to handle input changes
console.log('Cell content changed:', this.textContent);
});
});
// Add event listeners to each cell in the table body
const cells = document.querySelectorAll('tbody td');
cells.forEach(cell => {
cell.addEventListener('mouseover', function() {
// Add highlighted class to the row
this.parentNode.classList.add('highlighted');
// Add highlighted class to all cells in the same column
const index = Array.from(this.parentNode.children).indexOf(this);
const allRows = document.querySelectorAll('tbody tr');
allRows.forEach(row => {
row.children[index].classList.add('highlighted');
});
});
cell.addEventListener('mouseout', function() {
// Remove highlighted class from the row
this.parentNode.classList.remove('highlighted');
// Remove highlighted class from all cells in the same column
const index = Array.from(this.parentNode.children).indexOf(this);
const allRows = document.querySelectorAll('tbody tr');
allRows.forEach(row => {
row.children[index].classList.remove('highlighted');
});
});
});
</script>
</body>
</html>