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

Support for {{template "named"}} / {{define "named"}} #14

Closed
rtbenfield opened this issue Dec 15, 2014 · 4 comments
Closed

Support for {{template "named"}} / {{define "named"}} #14

rtbenfield opened this issue Dec 15, 2014 · 4 comments

Comments

@rtbenfield
Copy link

Is it possible to extend Render to allow the use of the Go template {{template "named"}} / {{define "named"}} syntax? It does somewhat work, but only in the single file scenario. Since all of the template files are compiled into one template.Template Go throws a redefinition error if you try to define a template more than once. This issue was brought up in martini-contrib/render here, but defers to the yield function which accomplishes the goal but only for a single block.

I would like to be able to add placeholders in my layout for things like scripts, styles, and even the title to later define in my view. I understand one possibility is to append the HTML into the context object or utilize a helper function, but I don't want to have HTML in my Go code.

I have been experimenting with potential fixes, and have found that it can be accomplished by changing the templates property from a *template.Template to a map[string]*template.Template, which then gets populated by compileTemplates by first creating a template.Template with the layout, then cloning it for each view in the directory. When accessing the template, it is retrieved from the map using its name and passed to the engine. I am not yet sure how this approach impacts performance. One flaw with this approach is layout overriding. Since the layout was not specified when compileTemplates was called, there is no combination containing the view and the override layout. This could be overcome by adding an on-the-fly compiling step for overrides, but this would result in higher potential for errors mid-runtime rather than at startup.

If there is an approach that others are taking to overcome this I would love to discuss it.

@unrolled
Copy link
Owner

@Code-Maverick Have you looked into this anymore, or did you find a solution that is working for you?

@rtbenfield
Copy link
Author

@unrolled I sort of found an approach I like. I'm using an MVC architecture with a base controller struct which I embed in my individual controllers. That base controller has a RegisterTemplate function that parses the template and puts it into a map keyed on a name that is unique to that controller. The base controller embeds Render to utilize the JSON and XML functions, but redefines HTML to use a custom variation with some similarity to the one in Render. Each of my controllers has a New function to instantiate it which registers the templates used in its actions. Then the actions call the HTML function with the template name and context object. I'm currently on parsing the templates on startup because my controllers are static with request specific dependencies injected into the actions using Gorilla Context (I'd like to change this to have my controllers instantiated on each request, but I haven't found a solution to this I like yet). The benefit to this way is I can define a layout template and a view template for each view I will need and the layout template can defined many {{ template "name" .}} placeholders which get defined like {{ defined "name" }}...{{end}} in my view template. I don't get the redefinition error since each view is parsed separately.

What do you think of this approach? I'm no Go expert, so please call out anything you would do differently.

Controller.go

// Controller is the base controller of the application
type Controller struct {
    *render.Render
    templates map[string]*template.Template
}

// Initialize defaults of Base Controller
func (c *Controller) init() {
    c.Render = render.New(render.Options{
        IsDevelopment: true,
    })
    c.templates = make(map[string]*template.Template)
}

// RegisterTemplate registers a template with the given name
func (c *Controller) RegisterTemplate(name string, filenames ...string) {
    t := template.Must(template.ParseFiles(filenames...))
    c.templates[name] = t
}

// HTML renders the specified HTML template with the specified context and status code
func (c *Controller) HTML(w http.ResponseWriter, status int, name string, context interface{}) {
    if tmpl, ok := c.templates[name]; ok {
        err := tmpl.Execute(w, 5)

        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }

        head := render.Head{
            ContentType: "text/html; charset=UTF-8",
            Status:      status,
        }

        head.Write(w)
    } else {
        http.Error(w, "Template with name "+name+" not found", http.StatusInternalServerError)
    }
}

DashboardController.go

// DashboardController contains actions related to the user dashboard
type DashboardController struct {
    Controller
}

// NewDashboardController initializes a new DashboardController with its default options
func NewDashboardController() *DashboardController {
    controller := &DashboardController{}
    controller.init()
    controller.RegisterTemplate("dashboard", "templates/_authorizedLayout.gohtml", "templates/dashboard/dashboard.gohtml")
    return controller
}

// GetDashboard is a GET Action displaying the login page in HTML
func (c *DashboardController) GetDashboard(w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    c.HTML(w, http.StatusOK, "dashboard", nil)
    return nil
}

@unrolled
Copy link
Owner

unrolled commented May 4, 2015

To be honest, I don't do much templating in Go... more apis and cli apps for me. With that said, this seems alright to me. Is this issue still valid, or shall we close it?

@rtbenfield
Copy link
Author

Sounds good. Thanks for your work on this project.

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

2 participants