Partial Refresh with Portal

As said before, The results of an web.EventFunc could be:

  • Go to a new page
  • Reload the whole current page
  • Refresh part of the current page

We have covered two. Now let's demonstrate refresh part of the current page:

import (
	"time"

	"github.com/qor5/web/v3"
	. "github.com/theplant/htmlgo"
)

func PartialUpdatePage(ctx *web.EventContext) (pr web.PageResponse, err error) {
	pr.Body = Div(
		H1("Partial Update"),
		A().Text("Edit").Href("javascript:;").
			Attr("@click", web.POST().EventFunc("edit1").Go()),
		web.Portal(
			If(len(fd.Title) > 0,
				H1(fd.Title),
				H5(fd.Date),
			).Else(
				Text("Default value"),
			),
		).Name("part1"),
		Div().Text(time.Now().Format(time.RFC3339Nano)),
	)
	return
}

type formData struct {
	Title string
	Date  string
}

var fd formData

func edit1(ctx *web.EventContext) (er web.EventResponse, err error) {
	er.UpdatePortals = append(er.UpdatePortals, &web.PortalUpdate{
		Name: "part1",
		Body: Div(
			web.Scope(
				Fieldset(
					Legend("Input value"),
					Div(
						Label("Title"),
						Input("").Type("text").Attr("v-model", "form.Title"),
					),

					Div(
						Label("Date"),
						Input("").Type("date").Attr("v-model", "form.Date"),
					),
				),
				Button("Update").
					Attr("@click", web.POST().EventFunc("reload2").Go()),
			).VSlot("{ locals, form }").FormInit(JSONString(fd)),
		),
	})
	return
}

func reload2(ctx *web.EventContext) (er web.EventResponse, err error) {
	ctx.MustUnmarshalForm(&fd)
	er.Reload = true
	return
}

var PartialUpdatePagePB = web.Page(PartialUpdatePage).
	EventFunc("edit1", edit1).
	EventFunc("reload2", reload2)

var PartialUpdatePagePath = URLPathByFunc(PartialUpdatePage)

web.Portal().Name("part1") Place a placeholder inside you page, and append web.PortalUpdate to er.UpdatePortals to update the portal with that name. Multiple portal can be updated at the same time.

Load Portal in separate AJAX request

With web.Portal, We can also load the portal with a separate AJAX request after page load. It is useful for the type of the content is not that important to the page, But load them are quite heavy. Like related products of a product detail page of a ECommerce site.

import (
	"fmt"
	"time"

	"github.com/qor5/web/v3"
	. "github.com/theplant/htmlgo"
)

func PartialReloadPage(ctx *web.EventContext) (pr web.PageResponse, err error) {
	reloadCount = 0

	ctx.Injector.HeadHTML(`
<style>
.rp {
	float: left;
	width: 200px;
	height: 200px;
	margin-right: 20px;
	background-color: orange;
}
</style>
`,
	)
	pr.Body = Div(
		H1("Portal Reload Automatically"),

		web.Scope(
			web.Portal().Loader(web.POST().EventFunc("autoReload")).AutoReloadInterval("locals.interval"),
			Button("stop").Attr("@click", "locals.interval = 0"),
		).Init(`{interval: 2000}`).VSlot("{ locals, form }"),

		H1("Load Data Only"),
		web.Scope(
			Ul(
				Li(
					Text("{{item}}"),
				).Attr("v-for", "item in locals.items"),
			),
			Button("Fetch Data").Attr("@click", web.GET().EventFunc("loadData").ThenScript(`locals.items = r.data`).Go()),
		).VSlot("{ locals, form }").FormInit("{ items: []}"),

		H1("Partial Load and Reload"),
		Div(
			H2("Product 1"),
		).Style("height: 200px; background-color: grey;"),
		H2("Related Products"),
		web.Portal().Name("related_products").Loader(web.POST().EventFunc("related").Query("productCode", "AH123")),
		A().Href("javascript:;").Text("Reload Related Products").
			Attr("@click", web.POST().EventFunc("reload3").Go()),
	)
	return
}

func related(ctx *web.EventContext) (er web.EventResponse, err error) {
	code := ctx.R.FormValue("productCode")
	er.Body = Div(

		Div(
			H3("Product A (related products of "+code+")"),
			Div().Text(time.Now().Format(time.RFC3339Nano)),
		).Class("rp"),
		Div(
			H3("Product B"),
			Div().Text(time.Now().Format(time.RFC3339Nano)),
		).Class("rp"),
		Div(
			H3("Product C"),
			Div().Text(time.Now().Format(time.RFC3339Nano)),
		).Class("rp"),
	)
	return
}

func reload3(ctx *web.EventContext) (er web.EventResponse, err error) {
	er.ReloadPortals = []string{"related_products"}
	return
}

var reloadCount = 1

func autoReload(ctx *web.EventContext) (er web.EventResponse, err error) {
	er.Body = Span(time.Now().String())
	reloadCount++

	if reloadCount > 5 {
		er.RunScript = `locals.interval = 0;`
	}
	return
}

func loadData(ctx *web.EventContext) (er web.EventResponse, err error) {
	var r []string
	for i := 0; i < 10; i++ {
		r = append(r, fmt.Sprintf("%d-%d", i, time.Now().Nanosecond()))
	}
	er.Data = r
	return
}

var PartialReloadPagePB = web.Page(PartialReloadPage).
	EventFunc("related", related).
	EventFunc("reload3", reload3).
	EventFunc("autoReload", autoReload).
	EventFunc("loadData", loadData)

var PartialReloadPagePath = URLPathByFunc(PartialReloadPage)

It is not only load the portal in separate AJAX request, Also you can reload it with ease er.ReloadPortals = []string{"related_products"} in an event func.

Under the hood, We use Vue's Dynamic & Async Components, to load Go generated html (vue runtime templates) from the server and mount those vue components into the page. It works the same way for reload the whole page, push state page switch, and refresh part of the current page.