Search code examples
gogo-html-template

Cancelling function by user action (html/template)


My goal is:

  • have an html page with a <a href> to enable/disable a function
  • when enabled: calling function: a sum in my example
  • when disabled: stop / kill function called before

Two first points are OK (or feel free to tell me what I may improve)

But I have no idea how to do the third point (because way I used for first points is not good?)

    package main
    
    import (
        "html/template"
        "github.com/labstack/echo"
        "log"
        "io"
        "net/http"
        "strconv"
        "fmt"
        "time"
    )
    
    //init LOG
    func init(){
        log.SetPrefix("TRACE: ")
        log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
        log.Println("init started")
    }
    
    // main function
    func main() {
    
    // defining struct instance
    settings.Enabled = false
    
    // Parsing the required html
    // file in same directory
    // Parsing Templates
    t := &Template{
        templates: template.Must(template.ParseGlob("test.html")),
    }
    
    // standard output to print merged data
    
    e := echo.New()
    e.Renderer = t
    e.HideBanner = false
    e.HidePort = false
    e.Debug = true
    
    e.GET("/", index)
    e.GET("/toggleEnable", toggleEnable)
    
    e.Start("127.0.0.1" + ":" + strconv.Itoa(8080))
    
    }
    
    func index(c echo.Context) error {
        return c.Render(http.StatusOK, "index", settings)
    } 
    
    // Settings Type ...
    type Settings struct {
        Enabled bool
    }
    var settings Settings
    
    type Template struct {
        templates *template.Template
    }
    
    func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
        return t.templates.ExecuteTemplate(w, name, data)
    }
    
    func toggleEnable(c echo.Context) error {
        if settings.Enabled {
            settings.Enabled = false
        } else {
            settings.Enabled = true
            Sum()
        }
        return c.Redirect(http.StatusTemporaryRedirect, c.Request().Header.Get("Referer"))
    }
    
    func Sum() {
    
        sum := 0
        for i := 1; i < 50; i++ {
            sum += i
            fmt.Println(sum)
            time.Sleep(3 * time.Second)
        }
    
    }

and test.html:

``` 
    <!DOCTYPE html> 
    <html> 
       <head> 
          <title>Test</title> 
       </head> 
       <body> 
    {{define "index"}}
          <h1>Settings</h1>
          
             <p>Test enabled : <a href="/toggleEnable">{{.Enabled }}</a></p>
             
    {{end}}
       </body> 
    </html> 

```

For the moment, if I false -> true: sum works and if I true-> false: nothing happens and if I false -> true one more time: an other count begin.

Next step will be to display count in html page.


Solution

  • As currently written when the browser requests /toggleEnable nothing will be returned until Sum() completes (which will take around 150 seconds). After the Sum() is complete the page with Test enabled : false will be returned/displayed. If you want to provide the ability for the user to interrupt the process then it's probably best to run it in a Goroutine; something like this:

    // Settings Type ...
    type Settings struct {
        Enabled bool
        Stop chan struct{}
    }
    var settings Settings
    
    type Template struct {
        templates *template.Template
    }
    
    func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
        return t.templates.ExecuteTemplate(w, name, data)
    }
    
    func toggleEnable(c echo.Context) error {
        if settings.Enabled {
            settings.Enabled = false
            close(settings.Stop)
        } else {
            settings.Enabled = true
            settings.Stop = make(chan struct{})
            go Sum(settings.Stop)
        }
        return c.Redirect(http.StatusTemporaryRedirect, c.Request().Header.Get("Referer"))
    }
    
    func Sum(stop chan struct{}) {
        sum := 0
        for i := 1; i < 50; i++ {
            sum += i
            fmt.Println(sum)
            select {
            case <-stop:
                fmt.Println("stopped")
                return
            case <-time.After(3 * time.Second):
            }
        }
    }
    

    Note that there are some issues with this. It is written on the assumption that there will be a single client and the page will not be updated when the timer completes (you would need to add a refresh or similar).

    Next step will be to display count in html page

    To do this you will need a way for the browser to request updates. This could be through a refresh (fairly simple but not a great user experience) or using Javascript (either polling or using websockets).