Search code examples
gostructgo-templates

Check if item exist and is true or false


in my code I'm sending a struct ReturnedResult to the template:

type ReturnedResult struct {
    Result  bool   `json:"result"`
    Message string `json:"message"`
}

I want the template to:

  1. Check if there is a Result or nil
  2. If there is a Result I want to check if it is true or false

So, i wrote the below:

{{with .Result}}
  {{if .Result}}
    <div id='foo'>
      <i class="far fa-thumbs-up"></i>If report not opened automatically, pls download from <a href={{.Message}}>here</a>
    </div>
    <script>
      url = `{{.Message}}`
      window.open(url, "_blank");
      setTimeout(function () {document.querySelector('#foo').style.display='none'}, 5000);
    </script>
  {{else}}
    <i class="fas fa-exclamation-triangle">{{.Message}}</i>>
    <script>
      setTimeout(function () {document.querySelector('#foo').style.display='none'}, 5000);
    </script>
  {{end}}
{{end}}

Sometime I call the template without data as tmpl.Execute(w, nil), some times with data as tmpl.Execute(w, webReturn)

The one without data is working fine, but the one with data is not working and no idea what is wrong.

Below is my full code if required.

// go build -ldflags "-H=windowsgui"
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
    "text/template"

    "github.com/zserge/lorca"
)

// ContactDetails ...
type ContactDetails struct {
    Email   string
    Subject string
    Message string
    Color1  []string
    Color2  []string
}

// ReturnedResult ...
type ReturnedResult struct {
    Result  bool   `json:"result"`
    Message string `json:"message"`
}

func index(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.ParseFiles("forms.html"))
    if r.Method != http.MethodPost {
        tmpl.Execute(w, nil)
        return
    }

    r.ParseForm()
    fmt.Println(r.Form) // print information on server side.
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println(r.Form["color1"][0])
    fmt.Println(r.Form["color1"][1])
    for k, v := range r.Form {
        fmt.Println("key:", k)
        fmt.Println("val:", strings.Join(v, ""))
    }
    details := ContactDetails{
        Email:   r.FormValue("email"),
        Subject: r.FormValue("subject"),
        Message: r.FormValue("message"),
        Color1:  r.Form["color1"],
        Color2:  r.Form["color2"],
    }
    fmt.Printf("Post from website! r.PostFrom = %v\n", r.PostForm)
    fmt.Printf("Details = %v\n", details)

    //r.FormValue("username")
    fmt.Println()
    // do something with details
    sheetID := "AKfycbxfMucXOzX15tfU4errRSAa9IzuTRbHzvUdRxzzeYnNA8Ynz8LJuBuaMA/exec"
    url := "https://script.google.com/macros/s/" + sheetID + "/exec"
    bytesRepresentation, err := json.Marshal(details)
    if err != nil {
        log.Fatalln(err)
    }
    resp, err := http.Post(url, "application/json", bytes.NewBuffer(bytesRepresentation))
    if err != nil {
        log.Fatalln(err)
    }
    // read all response body
    data, _ := ioutil.ReadAll(resp.Body)

    // close response body
    resp.Body.Close()

    webReturn := ReturnedResult{}
    if err := json.Unmarshal([]byte(data), &webReturn); err != nil {
        panic(err)
    }
    fmt.Println(webReturn.Message)

    webReturn.Message = strings.ReplaceAll(webReturn.Message, "&export=download", "")
    //tmpl.Execute(w, struct{ Success bool }{webReturn.Result})
    tmpl.Execute(w, webReturn)
}

func main() {
    // Start Host goroutine
    go func() {
        http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./public"))))
        http.HandleFunc("/", index)
        http.ListenAndServe(":8090", nil)
    }()

    // Start UI
    ui, err := lorca.New("http://localhost:8090/index", "", 1200, 800)
    if err != nil {
        log.Fatal(err)
    }
    defer ui.Close()

    <-ui.Done()
}

Template;

<title>Form Submittal</title>
<style>
  body {
      font-family: Ubuntu, "times new roman", times, roman, serif;
  }
</style>
<link href="http://localhost:8090/static/fontawesome-free-5.15.2-web/css/all.css" rel="stylesheet"> <!--load all styles -->
<!-- link rel="stylesheet" href="http://localhost:8090/styles/MaterialDesign-Webfont-master/css/materialdesignicons.min.css" -->

<script type="module" src="http://localhost:8090/static/node_modules/@mdi/js/mdi.js"></script>
<script type="module">
 // import {mdi-home} from "./node_modules/@mdi/js/mdi.js";
 // alert(a);
</script>
<script>
  //import { mdi-home } from '/MaterialDesign-JS-master/mdi.js';
  function deleteRow(row) {
    var i = row.parentNode.parentNode.rowIndex - 2; // this -> td -> tr // -2 because the first 2 rows are used for header
    var tbl = document.querySelector('tbody');
    if(tbl && tbl.rows.length > 1) {
      tbl.deleteRow(i); 
      Array.from(tbl.rows).forEach((row, index) => {
        row.cells[0].innerHTML = index + 1;
      });
    }
  } 

  function insRow(row) {
    var i = row.parentNode.parentNode.rowIndex - 2; // this -> td -> tr // -2 because the first 2 rows are used for header
    var tbl = document.querySelector('tbody');
    var row = document.createElement('tr');
    row.innerHTML=`
      <th></th>
      <td><input size=25 type="text" name="color1" /></td>
      <td><input size=25 type="text" name="color2" ></td>
      <td><i class="fas fa-edit" onclick="insRow(this)"></i></td>
      <td><i class="fas fa-eraser" onclick="deleteRow(this)"></i></td>
    `;
    var len = tbl.rows.length;
    row.cells[0].innerHTML = len + 1;
    tbl.insertBefore(row, tbl.children[i+1]);
    Array.from(tbl.rows).forEach((row, index) => {
      row.cells[0].innerHTML = index + 1;
    });
    //tbl.appendChild(row);
  }
</script>
<h1>Contact</h1>
Bob lives in a <span class="mdi mdi-home"></span>.

<form method="POST">
    <label>Email:</label><br />
    <input type="text" name="email"><br />
    <label>Subject:</label><br />
    <input type="text" name="subject"><br />
    <label>Message:</label><br />
    <textarea name="message"></textarea><br />

    <table>
      <caption>Monthly savings</caption>
      <thead>
        <tr>
          <th colspan="5">The table header</th>
        </tr>
        <tr>
          <th>SN</td>
          <th>PO number</td>
          <th>PO invoice value</td>
          <th colspan="2">Action</td>
        </tr>
      </thead>
      <tbody >
        <tr>
          <th>1</th>
          <td><input size=25 type="text" name="color1" /></td>
          <td><input size=25 type="text" name="color2" /></td>
          <td colspan="2"><i class="fas fa-edit" onclick="insRow(this)"></i></td>
        </tr>
      </tbody>
      <tfoot>
        <tr>
          <th colspan="5">The table footer</th>
        </tr>
      </tfoot>
    </table>

   <input type="submit">
</form>
{{with .Message}}
  {{if .Result}}
    <div id='foo'>
      <i class="far fa-thumbs-up"></i>If report not opened automatically, pls download from <a href={{.Message}}>here</a>
    </div>
    <script>
      url = `{{.Message}}`
      window.open(url, "_blank");
      setTimeout(function () {document.querySelector('#foo').style.display='none'}, 5000);
    </script>
  {{else}}
    <i class="fas fa-exclamation-triangle">{{.Message}}</i>>
    <script>
      setTimeout(function () {document.querySelector('#foo').style.display='none'}, 5000);
    </script>
  {{end}}
{{end}}

Solution

  • template.Execute() returns an error, do check that:

    if err := tmpl.Execute(w, webReturn); err != nil {
        // Handle error:
        log.Printf("Template error: %v", err)
        return
    }
    

    Here's a minimal reproducible example:

    type ReturnedResult struct {
        Result  bool
        Message string
    }
    
    func main() {
        t := template.Must(template.New("").Parse(src))
    
        if err := t.Execute(os.Stdout, nil); err != nil {
            panic(err)
        }
    
        p := ReturnedResult{Result: false, Message: "a"}
        if err := t.Execute(os.Stdout, p); err != nil {
            panic(err)
        }
    
        p = ReturnedResult{Result: true, Message: "a"}
        if err := t.Execute(os.Stdout, p); err != nil {
            panic(err)
        }
    }
    
    const src = `{{with .Message}}
      {{if .Result}}
        Message with true result
      {{else }}
        Message with false result
      {{end}}
    {{end}}
    `
    

    Testing it on the Go Playground, output is:

      panic: template: :2:7: executing "" at <.Result>: can't evaluate field Result in type string
    
    goroutine 1 [running]:
    main.main()
        /tmp/sandbox993188219/prog.go:22 +0x226
    

    The problem is that the {{with}} action sets the dot, so inside the {{with}} dot will mean the Message field, which obviously doesn't have .Result field (it's not a struct).

    To just test (and not change the dot), use a simple {{if}}:

    {{if .Message}}
      {{if .Result}}
        Message with true result
      {{else }}
        Message with false result
      {{end}}
    {{end}}
    

    With this template the output will be (try it on the Go Playground):

    <empty output for the nil param>
    
    Message with false result
    
    Message with true result