Add s:render feature for data update rendering (dev)

This commit is contained in:
Max 2024-08-02 21:18:02 +08:00
parent 743339d3d4
commit a07c1ece51
6 changed files with 158 additions and 19 deletions

View file

@ -10,6 +10,26 @@ var dsl = []byte(`
"guard": "bearer-jwt",
"group": "__yao/sui/v1",
"paths": [
{
"label": "Render",
"description": "Render the frontend page",
"path": "/render/*route",
"method": "POST",
"guard": "-",
"process": "sui.Render",
"in": [":context", "$param.route", ":payload"],
"out": { "status": 200, "type": "text/html; charset=utf-8" }
},
{
"label": "Run",
"description": "Run the backend script, with Api prefix method",
"path": "/run/*route",
"guard": "-",
"method": "POST",
"process": "sui.Run",
"in": [":context", "$param.route", ":payload"],
"out": { "status": 200, "type": "application/json" }
},
{
"path": "/:id/setting",
"method": "GET",

View file

@ -20,6 +20,9 @@ func init() {
process.RegisterGroup("sui", map[string]process.Handler{
"setting": Setting,
"render": Render,
"run": Run,
"template.get": TemplateGet,
"template.find": TemplateFind,
"template.asset": TemplateAsset,

104
sui/api/render.go Normal file
View file

@ -0,0 +1,104 @@
package api
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/yao/sui/core"
)
// Render the frontend page
func Render(process *process.Process) interface{} {
process.ValidateArgNums(3)
ctx, ok := process.Args[0].(*gin.Context)
if !ok {
return "The context is required"
}
ctx.Header("Content-Type", "text/html; charset=utf-8")
route := process.ArgsString(1)
payload := process.ArgsMap(2)
if route == "" {
return "The route is required"
}
if payload["name"] == nil {
return "The render name is required"
}
ctx.Request.URL.Path = route
r, _, err := NewRequestContext(ctx)
if err != nil {
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
}
c, _, err := r.MakeCache()
if err != nil {
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
}
if c == nil {
return fmt.Sprintf("<div class='text-danger'> Cache not found </div>")
}
data, ok := payload["data"].(map[string]interface{})
if !ok {
return fmt.Sprintf("<div class='text-danger'> Data not found </div>")
}
name, ok := payload["name"].(string)
if !ok {
return fmt.Sprintf("<div class='text-danger'> Name not found </div>")
}
html, err := r.renderHTML(c, name, c.HTML, core.Data(data))
if err != nil {
return fmt.Sprintf("<div class='text-danger'> %s </div>", err.Error())
}
return html
}
func (r *Request) renderHTML(c *core.Cache, name string, html string, data core.Data) (string, error) {
doc, err := core.NewDocument([]byte(html))
if err != nil {
return "", fmt.Errorf("Document error: %w", err)
}
sel := doc.Find(fmt.Sprintf("[s\\:render='%s']", name))
if sel.Length() == 0 {
return "", fmt.Errorf("Render %s not found", name)
}
// Set the page request data
option := core.ParserOption{
Theme: r.Request.Theme,
Locale: r.Request.Locale,
Debug: r.Request.DebugMode(),
DisableCache: r.Request.DisableCache(),
Route: r.Request.URL.Path,
Root: c.Root,
Script: c.Script,
Imports: c.Imports,
Request: r.Request,
}
// Parse the template
parser := core.NewTemplateParser(data, &option)
err = parser.RenderSelection(sel)
if err != nil {
return "", fmt.Errorf("Parser error: %w", err)
}
sel.Find("[sui-hide]").Remove()
parser.Tidy(sel)
html, err = sel.Html()
if err != nil {
return "", fmt.Errorf("Html error: %w", err)
}
return html, nil
}

8
sui/api/run.go Normal file
View file

@ -0,0 +1,8 @@
package api
import "github.com/yaoapp/gou/process"
// Run the backend script, with Api prefix method
func Run(process *process.Process) interface{} {
return nil
}

View file

@ -284,13 +284,15 @@ const pageEventScriptTmpl = `
const compEventScriptTmpl = `
if (document.querySelector("[s\\:event=%s]")) {
document.querySelector("[s\\:event=%s]").addEventListener("%s", function (event) {
const dataKeys = %s;
const jsonKeys = %s;
const root = __sui_component_root(this, "%s");
handler = new %s(root).%s;
const target = event.target || null;
__sui_event_handler(event, dataKeys, jsonKeys, target, root, handler);
let elms = document.querySelectorAll("[s\\:event=%s]");
elms.forEach(function (element) {
element.addEventListener("%s", function (event) {
const dataKeys = %s;
const jsonKeys = %s;
const root = __sui_component_root(element, "%s");
handler = new %s(root).%s;
__sui_event_handler(event, dataKeys, jsonKeys, element, root, handler);
});
});
}
`

View file

@ -68,17 +68,19 @@ var keepWords = map[string]bool{
}
var allowUsePropAttrs = map[string]bool{
"s:if": true,
"s:elif": true,
"s:for": true,
"s:event": true,
"s:if": true,
"s:elif": true,
"s:for": true,
"s:event": true,
"s:render": true,
}
var keepAttrs = map[string]bool{
"s:ns": true,
"s:cn": true,
"s:ready": true,
"s:event": true,
"s:ns": true,
"s:cn": true,
"s:ready": true,
"s:event": true,
"s:render": true,
}
// NewTemplateParser create a new template parser
@ -157,7 +159,7 @@ func (parser *TemplateParser) Render(html string) (string, error) {
if parser.option != nil && (parser.option.Request != nil || parser.option.Preview) {
// Remove the sui-hide attribute
doc.Find("[sui-hide]").Remove()
parser.tidy(doc.Selection)
parser.Tidy(doc.Selection)
}
// fmt.Println(doc.Html())
@ -837,13 +839,13 @@ func (parser *TemplateParser) show(sel *goquery.Selection) {
// sel.SetAttr("style", style)
}
func (parser *TemplateParser) tidy(s *goquery.Selection) {
func (parser *TemplateParser) Tidy(s *goquery.Selection) {
s.Contents().Each(func(i int, child *goquery.Selection) {
node := child.Get(0)
if _, exist := child.Attr("s:jit"); node.Data == "slot" || exist {
parser.tidy(child)
parser.Tidy(child)
parser.removeWrapper(child)
return
}
@ -872,7 +874,7 @@ func (parser *TemplateParser) tidy(s *goquery.Selection) {
}
node.Attr = attrs
parser.tidy(child)
parser.Tidy(child)
})
}