Golang Templates - An Addendum

Dealing with DOM Ready.

Note: If you haven’t been playing the at home game, then I suggest you read the first post here, otherwise let’s assume that you have a directory structure like the following.

main.go
templates
├── _footer.html
├── _header.html
├── layout.html
├── templates.go
└── user
    ├── dashboard.html

The problem: ensuring that any JS snippets defined throughout your application templates are executed with a valid DOM available. Granted, it would be best practice for you to combine all of your .js files and then load the src in the head using the async attribute. But even a snippet in the _header.html may still fail without jQuery loaded.

We can do better.

Let’s collect any/all js snippets from any/all of the available templates and then render them at the last possible moment in the DOM.

template.FuncMap

First off we will create an accessible template function to pass our js snippets.

jsSnippets := []template.JS{}
tplFuncs := template.FuncMap{
    // This template method just appends js snippets. The
    // Render() method is responsible for concatenation and
    // adding to the base layouts content.
    "domReady": func(js string) string {
        jsSnippets = append(jsSnippets, template.JS(js))
        return ""
    },
}

A couple things to note, any function in a template.FuncMap must return either a single value or x2 values where the 2nd is of type error. As we are not concerned with the return value here, we just return an empty string.

The purpose of the function is to store your js string snippets as a template.JS value for future rendering. Then in your template files call

// The use of back ticks `s removes any single/double
// quote shenanigans.
{{domReady `console.log("All of the JS")`}}

First failures.

Ok so now we have our snippets gathered from our templates, we just need to pass them up to our outer layout…..ummm sorry? Let’s recap on our render method.

func Render(.....) {
    // At first we thought that we can iterate over
    // our js snippets now and assign to the template
    // data. But it's always empty!
    var domReadyJS template.JS
    if 0 < len(jsSnippets) {
        for _, jq := range jsSnippets {
            domReadyJS = domReadyJS + jq
        }
    }
    data["domReadyJS"] = domReadyJS
    
    layoutClone, _ := GetLayout(l).Clone()
    layoutClone.Funcs(funcs)
    err := layoutClone.Execute(w, data)
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintf(w, errorTemplate, name, err.Error())
    }
}

My first attempt to implement of course failed due to a chicken/egg scenario. When the Render() method is called, the layout will always have an empty slice of js snippets! This is because it’s not until the enclosed named template is executed that any calls to the domReady function will be invoked.

You can always get out!

If you were thinking about how to hook into the templates to pass values to the layouts, take it from me, don’t. I tried. The better solution is of course to buffer the content of the layout, check for js snippets and then write the results to an outer template.

For a full example, refer to the this snippet - https://gitlab.com/snippets/1663919

With this logic, you can call {{domReady}} multiple times from any layout/template and have the results combined and rendered at the base of the page. Much better!

PS: In case you’re wondering, the chicken ALWAYS came before the egg.

It's where the magic happens © 2018 - KyleHQ