Search code examples
javascripthtmlgowebsocketgo-html-template

Golang Template Range (for loop) using JSON from WebSocket


I'm using Gorilla Websocket to update some HTML (img src, text, etc); I do this the following way:

mt, message, err := c.ReadMessage()
if err != nil {
    log.Println("read:", err)
    break
}
[...]
app, err := models.DB.SearchAppStore(ctx, stars, updatedWithin, 0)
myJson, err := json.Marshal(app)
err = c.WriteMessage(mt, myJson)
if err != nil {
    log.Println("write:", err)
    break
}

Then I use javascript to update the HTML data this way:

ws.onmessage = function(evt) {
    var d = JSON.parse(evt.data);
    var app;
    for (app = 0; app < 3; app++) {
      document.getElementById("app-icon-" + app).src = d[app].ThumbnailURL;
      document.getElementById("app-title-" + app).innerHTML = d[app].Title;
      document.getElementById("app-compatibility-" + app).innerHTML = d[app].Compatibility;
    }
  };

And then I have manually typed the HTML this way:

<div class="app-section">
  <div class="icon">
    <img src="" id="app-icon-0">
  </div>
  <div class="details">
    <h2 id="app-title-0"></h2>
    <h5 id="app-compatibility-0"></h5>
  </div>
</div>

You can see the 0 in the HTML 'id's, and I should note that it's much longer but I tried to only take the relevant parts..

I would of course like not to type HTML manually since it will make it difficult(/impossible) to deal with different lenghts (like maybe sometimes I want to display a hundred apps, other times maybe there only are 3 available, etc..)

I was thinking this might be able to be done using golang's HTML {{range}} function, but I can't figure out how to integrate it with json data from websockets..

Another solution that should be managable is to just write out all the HTML inside the JS for loop at ws.onmessage, but I think it would be better if I learned how to do it using the golang template package.. Especially because it's really long and there are many classes/id's..

The way I see it, I need to get the length of the JSON (Object.keys(d).length;), then somehow I need to pass this length inside {{range}} and then can use {{index}} to interate through the JSON object..

..but I haven't been able to figure out how to do it, maybe it's not even possible.. I would greatly appreciate any help with how this can be done..


Solution

  • A simple approach is to execute a template on the server and send the resulting HTML to the client where the HTML is inserted into the page.

    Declare package level variable with compiled template. This template assumes that the argument to Execute is a slice of structs or maps with fields ThumbnailURL, Title and Compatibility.

    var t = template.Must(template.New("").Parse(`{{range .}}
      <div class="icon">
       <img src="{{.ThumbnailURL}}">
      </div>
      <div class="details">
       <h2>{{.Title}}</h2>
       <h5>{{.Compatibility}}</h5>
      </div>{{end}}`))
    

    Execute the template in your read loop. Send the HTML to the client:

    mt, message, err := c.ReadMessage()
    if err != nil {
       log.Println("read:", err)
       break
    ]
    [...]
    app, err := models.DB.SearchAppStore(ctx, stars, updatedWithin, 0)
    var buf bytes.Buffer
    if err := t.Execute(&buf, app); err != nil {
        // handle error
    }
    err = c.WriteMessage(mt, buf.Bytes())
    if err != nil {
      log.Println("write:", err)
      break
    }
    

    Include a div for the results on the page:

    <div class="app-section"></div>
    

    Set the div's inner HTML when a message is received:

    ws.onmessage = function(evt) {
        document.getElementById("app-section").innerHTML = evt.Data;
    }
    

    This solution does not use JSON.