C0ol_Cod3r
C0ol_Cod3r

Reputation: 949

How to use rendered template in creating a pdf

Ok so I am Go Lang with the Echo framework, to try and build pdf which will load data from a database source - that bit will come later.

So this is how I am rendering my pdf html layout,

func (c *Controller) DataTest(ec echo.Context) error {
  return ec.Render(http.StatusOK, "pdf.html", map[string]interface{}{
    "name": "TEST",
    "msg":  "Hello, XXXX!",
  })
}

The above function works fine, and renders the html (I built a temp path to the function). Now I want to use that function as my html template to build my pdf's.

So I am using wkhtmltopdf and the lib "github.com/SebastiaanKlippert/go-wkhtmltopdf"

This is how I would render the html in the pdf,

html, err := ioutil.ReadFile("./assets/pdf.html")

if err != nil {
    return err
}

But I need to be able to update the template so that is why I am trying to render the page and take that into the pdf.

However, the Echo framework returns an error type and not of type bytes or strings and I am not sure how to update it so my rendered content is returned as bytes?

Thanks,

UPDATE

page := wkhtmltopdf.NewPageReader(bytes.NewReader(c.DataTest(data)))

This is how I am currently doing, the data is just a html string which is then turned into a slice of bytes for the NewReader.

This works fine, but I wanted to turn the DataTest function into a fully rendered html page by Echo. The problem with that is that when you return a rendered page, it is returned as an error type.

So I was trying to work out a why of updating it, so I could return the data as an html string, which would then be put in as a slice of bytes.

Upvotes: 2

Views: 4259

Answers (2)

sh.seo
sh.seo

Reputation: 1610

if you want rendered html, use echo' custom middleware. I hope it helps you.

main.go

package main

import (
    "bufio"
    "bytes"
    "errors"
    "fmt"
    "html/template"
    "io"
    "net"

    "net/http"

    "github.com/labstack/echo"
)

type TemplateRegistry struct {
    templates map[string]*template.Template
}

func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    tmpl, ok := t.templates[name]
    if !ok {
        err := errors.New("Template not found -> " + name)
        return err
    }
    return tmpl.ExecuteTemplate(w, "base.html", data)
}

func main() {
    e := echo.New()

    templates := make(map[string]*template.Template)
    templates["about.html"] = template.Must(template.ParseFiles("view/about.html", "view/base.html"))
    e.Renderer = &TemplateRegistry{
        templates: templates,
    }

    // add custom middleware
    // e.Use(PdfMiddleware)

    // only AboutHandler for Pdf
    e.GET("/about", PdfMiddleware(AboutHandler))

    // Start the Echo server
    e.Logger.Fatal(e.Start(":8080"))
}

// custom middleware
func PdfMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) (err error) {
        resBody := new(bytes.Buffer)
        mw := io.MultiWriter(c.Response().Writer, resBody)
        writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
        c.Response().Writer = writer

        if err = next(c); err != nil {
            c.Error(err)
        }

        // or use resBody.Bytes()
        fmt.Println(resBody.String())
        return
    }
}

type bodyDumpResponseWriter struct {
    io.Writer
    http.ResponseWriter
}

func (w *bodyDumpResponseWriter) WriteHeader(code int) {
    w.ResponseWriter.WriteHeader(code)
}

func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
    return w.Writer.Write(b)
}

func (w *bodyDumpResponseWriter) Flush() {
    w.ResponseWriter.(http.Flusher).Flush()
}

func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
    return w.ResponseWriter.(http.Hijacker).Hijack()
}

func AboutHandler(c echo.Context) error {
    return c.Render(http.StatusOK, "about.html", map[string]interface{}{
        "name": "About",
        "msg":  "All about Boatswain!",
    })
}

view/about.html

{{define "title"}}
  Boatswain Blog | {{index . "name"}}
{{end}}

{{define "body"}}
  <h1>{{index . "msg"}}</h1>
  <h2>This is the about page.</h2>
{{end}}

view/base.html

{{define "base.html"}}
<!DOCTYPE html>
  <html>
    <head>
      <title>{{template "title" .}}</title>
    </head>
    <body>
      {{template "body" .}}
    </body>
  </html>
{{end}}

Upvotes: 1

Nestor Sokil
Nestor Sokil

Reputation: 2272

So from what I understand you want to:

  1. Render HTML from template
  2. Convert HTML to PDF
  3. Send it as an HTTP response? This part is unclear from your question, but it doesn't matter really.

So, the reason Echo returns error is because it actually not only does the template rendering, but also sending a response to client. If you want to do something else in-between, you can't use that method from echo.

Luckily, echo doesn't do anything magical there. You can just use the standard template package with the same result.

func GetHtml(filename string, data interface{}) (string, error) {
    filedata, err := ioutil.ReadFile(filename)
    if err != nil {
        return "", err
    }

    asString := string(filedata)
    t, err := template.New("any-name").Parse(asString)
    if err != nil {
        return "", err
    }

    var buffer bytes.Buffer
    err = t.Execute(&buffer, data)
    if err != nil {
        return "", err
    }

    return buffer.String(), nil
}

There you have your function that returns a string. You can use buffer.Bytes() to have a byte array if that's preferrable.

After this you can do whatever you need to, like convert to PDF and write it back to clients using echoCtx.Response().Writer().

Hope that helps, but in future try asking more precise questions, then you are more likely to receive an accurate response.

Upvotes: 0

Related Questions