How to execute multiple variables in HTML Go Template?

Issue

I’ve tried looking at the other questions, and they don’t seem to help my situation.

I essentially need to have 2 if statements on my HTML page, but whenever I trigger the second tmpl.Execute() I essentially get the same page embedded within those if statements.

Here is a function that I am trying to get working:

func RemoveVehicle(w http.ResponseWriter, r *http.Request) {

    conditionsMap := map[string]interface{}{}

    username, _ := ExtractTokenUsername(r)

    if username != "" {
        conditionsMap["Username"] = username
    }

    t, err := template.ParseFiles("remove.html")
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    if r.Method != http.MethodPost {
        t.Execute(w, conditionsMap) //Here I am trying to check to see if the user is logged in, and if not to return a false boolean that will trigger the else statement
        return
    }

    db, err := sql.Open("mysql", "root:*******@tcp(127.0.0.1:3306)/my_db")

    if err != nil {
        fmt.Println("Connection Failed.")
        panic(err.Error())
    }

    defer db.Close()

    var car Vehicle
    sqlStatement := `SELECT * FROM Vehicle`
    rows, err := db.Query(sqlStatement)
    if err != nil {
        panic(err)
    }
    defer rows.Close()

    var carSlice []Vehicle
    for rows.Next() {
        rows.Scan(&car.Id, &car.Date, &car.Brand, &car.Model, &car.Mileage, &car.Year, &car.rented, &car.Dayrate)
        carSlice = append(carSlice, car)
    }

    if r.Method != http.MethodPost {
        t.Execute(w, carSlice) // Then here I am trying to populate the form select with a few rows from a table
        return
    }

    var id_ = r.FormValue("select")
    fmt.Println(id_)

    stmt, e := db.Prepare("DELETE FROM vehicle WHERE id=?")
    ErrorCheck(e)

    stmt.Exec(id_)
}

I have commented out the 2 parts that I am trying to get working simultaneously, but they work individually.

Here is the relevant HTML:

                        {{if .Username}}
                        <div><img src="images/kogdpilnmzhz9rhzceo2.png" alt="" width="65" height="65" class="addV_label"/></div>
                        <hr style="height:5px">
                        <form action="/remove" method="POST" source="custom" name="form">
                           <input type="hidden" name="xss-token" value=""/>
                           <div class="form-group">
                              <div>
                                 <label class="addV_label">Select Vehicle&nbsp;</label>
                                 <select name="select" class="form-control loginInput2" required="required">

                                    {{range .}}
                                    <option value="{{.Id}}">{{.Brand}} {{.Model}} ({{.Year}}), {{.Mileage}} miles | £{{.Dayrate}}/pd</option>
                                    {{end}}

                                 </select>
                              </div>
                           </div>
                           <div>
                              <button class="btn-block frgt_1 btn addV_btn" type="submit" value="remove">REMOVE</button>
                           </div>
                        </form>

                        {{else}}
                        <p> Access Denied, please login.<a href="/login">Login</a></p>
                        {{end}}

I’ve tried to use a struct, but the carSlice is already a struct and conditionMap is a map.

What would be the best solution to tackling this situation?

Solution

First off, it is recommended to initialize the *template.Template types and the *sql.DB connection pool only once, during program start up. Both types are safe for concurrent use and can therefore be used by multiple handlers simultaneously.

var (
    removeTemplate *template.Template
    db             *sql.DB
)

func init() {
    var err error

    removeTemplate, err = template.ParseFiles("remove.html")
    if err != nil {
        panic(err)
    }

    db, err = sql.Open("mysql", "root:*******@tcp(127.0.0.1:3306)/my_db")
    if err != nil {
        panic(err)
    } else if err := db.Ping(); err != nil {
        panic(err)
    }
}

Now your handler can look something like the following:

func RemoveVehicle(w http.ResponseWriter, r *http.Request) {
    // check if the user is logged in
    username, _ := ExtractTokenUsername(r)
    if len(username) == 0 {
        // if not, render the template with no data, this
        // will show the "please login" part of your template
        if err := removeTemplate.Execute(w, nil); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
        return
    }

    switch r.Method {

    // if the method is GET, show the list of vehicles available
    case http.MethodGet:
        // select vehicles from db
        rows, err := db.Query(`SELECT * FROM Vehicle`)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer rows.Close()

        // scan rows
        var vehicles []Vehicle
        for rows.Next() {
            var v Vehicle
            err := rows.Scan(&v.Id, &v.Date, &v.Brand, &v.Model, &v.Mileage, &v.Year, &v.rented, &v.Dayrate)
            if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
            }
            vehicles = append(vehicles, v)
        }
        if err := rows.Err(); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        // now render the template with the data
        // that you just retrieved from the database
        data := map[string]interface{}{
            "Username": username,
            "Vehicles": vehicles,
        }
        if err := removeTemplate.Execute(w, data); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }

    // if the method is POST, delete the vehicle
    case http.MethodPost {
        var id = r.FormValue("select")
        if _, err := db.Exec("DELETE FROM vehicle WHERE id=?", id); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
}

And in your template, since a data passed in by the handler is a map with Username and Vehicles keys, you need to range over .Vehicles and not just the ..

{{if .Username}}
<div><img src="images/kogdpilnmzhz9rhzceo2.png" alt="" width="65" height="65" class="addV_label"/></div>
<hr style="height:5px">
<form action="/remove" method="POST" source="custom" name="form">
    <input type="hidden" name="xss-token" value=""/>
    <div class="form-group">
        <div>
            <label class="addV_label">Select Vehicle&nbsp;</label>
            <select name="select" class="form-control loginInput2" required="required">
                {{range .Vehicles}}
                <option value="{{.Id}}">{{.Brand}} {{.Model}} ({{.Year}}), {{.Mileage}} miles | £{{.Dayrate}}/pd</option>
                {{end}}
            </select>
        </div>
    </div>
    <div>
        <button class="btn-block frgt_1 btn addV_btn" type="submit" value="remove">REMOVE</button>
    </div>
</form>
{{else}}
<p> Access Denied, please login.<a href="/login">Login</a></p>
{{end}}

Answered By – mkopriva

Answer Checked By – Gilberto Lyons (GoLangFix Admin)

Leave a Reply

Your email address will not be published.