From 6bbcfec0cd48bf2af61010896bdb89e2755847cd Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Oct 2022 17:32:37 +0800 Subject: [PATCH] [change] optimize forms --- service/guard.go | 26 ++-- widgets/compute/compute.go | 244 +++++++++++++++++++++++++++++++ widgets/compute/types.go | 28 ++++ widgets/form/action.go | 135 +---------------- widgets/form/api.go | 62 +++++++- widgets/form/compute.go | 100 ++++++------- widgets/form/form.go | 13 +- widgets/form/handler.go | 125 ++++++++++++++++ widgets/form/types.go | 19 +-- widgets/table/compute.go | 266 +++++----------------------------- widgets/table/compute_test.go | 14 +- widgets/table/handler.go | 11 +- widgets/table/types.go | 39 ++--- 13 files changed, 595 insertions(+), 487 deletions(-) create mode 100644 widgets/compute/compute.go create mode 100644 widgets/compute/types.go create mode 100644 widgets/form/handler.go diff --git a/service/guard.go b/service/guard.go index 1c1d23df0b..8259d0cf58 100644 --- a/service/guard.go +++ b/service/guard.go @@ -5,21 +5,23 @@ import ( "github.com/gin-gonic/gin" "github.com/yaoapp/yao/helper" - "github.com/yaoapp/yao/table" + table_v0 "github.com/yaoapp/yao/table" - widget_table "github.com/yaoapp/yao/widgets/table" + "github.com/yaoapp/yao/widgets/form" + "github.com/yaoapp/yao/widgets/table" ) -// Guards 服务中间件 +// Guards middlewares var Guards = map[string]gin.HandlerFunc{ - "bearer-jwt": bearerJWT, // JWT 鉴权 - "cross-domain": crossDomain, // 跨域许可 - "table-guard": table.Guard, // Table Guard - "widget-table": widget_table.Guard, // Widget Table Guard + "bearer-jwt": guardBearerJWT, // Bearer JWT + "cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing + "table-guard": table_v0.Guard, // Table Guard ( v0.9 table) + "widget-table": table.Guard, // Widget Table Guard + "widget-form": form.Guard, // Widget Form Guard } -// JWT 鉴权 -func bearerJWT(c *gin.Context) { +// JWT Bearer JWT +func guardBearerJWT(c *gin.Context) { tokenString := c.Request.Header.Get("Authorization") tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer ")) if tokenString == "" { @@ -32,15 +34,15 @@ func bearerJWT(c *gin.Context) { c.Set("__sid", claims.SID) } -// crossDomain 跨域访问 -func crossDomain(c *gin.Context) { +// CORS Cross Origin +func guardCrossOrigin(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") - if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } + c.Next() } diff --git a/widgets/compute/compute.go b/widgets/compute/compute.go new file mode 100644 index 0000000000..c42492b1b0 --- /dev/null +++ b/widgets/compute/compute.go @@ -0,0 +1,244 @@ +package compute + +import ( + "fmt" + "reflect" + "strings" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" + "github.com/yaoapp/kun/maps" + "github.com/yaoapp/yao/widgets/field" +) + +var views = map[string]bool{"find": true, "get": true, "search": true} + +// ComputeEdit edit compute edit +func (c *Computable) ComputeEdit(name string, process *gou.Process, args []interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + namer := strings.Split(strings.ToLower(name), ".") + name = namer[len(namer)-1] + + switch name { + case "save", "create": + if len(args) == 0 { + return nil + } + switch args[0].(type) { + case maps.MapStr: + return c.editRow(process, args[0].(maps.MapStr), getField) + + case map[string]interface{}: + return c.editRow(process, args[0].(map[string]interface{}), getField) + } + return nil + + case "update", "updatewhere", "updatein": + if len(args) < 2 { + return nil + } + + switch args[1].(type) { + case maps.MapStr: + return c.editRow(process, args[1].(maps.MapStr), getField) + + case map[string]interface{}: + return c.editRow(process, args[1].(map[string]interface{}), getField) + } + return nil + + case "insert": + if len(args) < 2 { + return nil + } + + if _, ok := args[0].([]string); !ok { + return fmt.Errorf("args[0] is not a []string %s", reflect.ValueOf(args[0]).Type().Name()) + } + + if _, ok := args[1].([][]interface{}); !ok { + return fmt.Errorf("args[1] is not a [][] %s", reflect.ValueOf(args[1]).Type().Name()) + } + + return c.editRows(process, args[0].([]string), args[1].([][]interface{}), getField) + } + + return nil +} + +// EditRow edit row +func (c *Computable) editRow(process *gou.Process, res map[string]interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + + messages := []string{} + row := maps.MapOf(res).Dot() + data := maps.StrAny{"row": row}.Dot() + for key := range res { + if computes, has := c.Computes.Edit[key]; has { + unit := computes[0] + field, path, err := getField(unit.Name) + if err != nil { + messages = append(messages, err.Error()) + continue + } + + data.Set("value", res[key]) + data.Merge(any.MapOf(field.Edit.Map()).MapStrAny.Dot()) + new, err := field.Edit.Compute.Value(data, process.Sid, process.Global) + if err != nil { + messages = append(messages, fmt.Sprintf("%s.%s bind: %s, value: %v error: %s", path, unit.Name, key, res[key], err.Error())) + continue + } + res[key] = new + } + } + + if len(messages) > 0 { + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) + } + + return nil +} + +// EditRows edit row +func (c *Computable) editRows(process *gou.Process, columns []string, res [][]interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + + messages := []string{} + keys := map[string]int{} + for i, name := range columns { + keys[name] = i + } + + for i := range res { + if len(keys) != len(res[i]) { + continue + } + + row := map[string]interface{}{} + for i, v := range res[i] { + row[columns[i]] = v + } + + err := c.editRow(process, row, getField) + if err != nil { + messages = append(messages, err.Error()) + } + + for k, v := range row { + res[i][keys[k]] = v + } + } + + if len(messages) > 0 { + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) + } + + return nil +} + +// ComputeView view view +func (c *Computable) ComputeView(name string, process *gou.Process, res interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + + namer := strings.Split(strings.ToLower(name), ".") + name = namer[len(namer)-1] + if _, has := views[name]; !has { + return nil + } + + switch res.(type) { + case []maps.MapStrAny, []interface{}: + return c.viewRows(name, process, res, getField) + + case map[string]interface{}: + return c.viewRow(name, process, res.(map[string]interface{}), getField) + + case maps.MapStrAny: + return c.viewRow(name, process, res.(maps.MapStrAny), getField) + } + + return fmt.Errorf("res should be a map or array, but got a %s", reflect.ValueOf(res).Kind().String()) +} + +// ViewRows viewrows +func (c *Computable) viewRows(name string, process *gou.Process, res interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + switch res.(type) { + + case []interface{}: + messages := []string{} + for i := range res.([]interface{}) { + err := c.ComputeView(name, process, res.([]interface{})[i], getField) + if err != nil { + messages = append(messages, err.Error()) + } + } + if len(messages) > 0 { + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) + } + return nil + + case []maps.MapStrAny: + messages := []string{} + for i := range res.([]maps.MapStrAny) { + err := c.ComputeView(name, process, res.([]maps.MapStrAny)[i], getField) + if err != nil { + messages = append(messages, fmt.Sprintf("%d %s", i, err.Error())) + } + } + if len(messages) > 0 { + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) + } + return nil + } + + return nil +} + +// ViewRow row +func (c *Computable) viewRow(name string, process *gou.Process, res map[string]interface{}, getField func(string) (*field.ColumnDSL, string, error)) error { + + messages := []string{} + row := maps.MapOf(res).Dot() + data := maps.StrAny{"row": row}.Dot() + + // page + if row.Has("data") && row.Has("total") && + row.Has("pagesize") && row.Has("pagecnt") && + row.Has("prev") && row.Has("next") { + switch res["data"].(type) { + + case []maps.MapStrAny: + return c.viewRows(name, process, res["data"].([]maps.MapStrAny), getField) + + case []interface{}: + return c.viewRows(name, process, res["data"].([]interface{}), getField) + } + } + + for key, computes := range c.Computes.View { + unit := computes[0] + field, path, err := getField(unit.Name) + if err != nil { + messages = append(messages, err.Error()) + continue + } + + data.Set("value", res[key]) + data.Merge(any.MapOf(field.View.Map()).MapStrAny.Dot()) + new, err := field.View.Compute.Value(data, process.Sid, process.Global) + if err != nil { + res[key] = nil + messages = append(messages, fmt.Sprintf("%s.%s bind: %s, value: %v error: %s", path, unit.Name, key, res[key], err.Error())) + continue + } + res[key] = new + } + + if len(messages) > 0 { + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) + } + + return nil +} + +// ComputeFilter filter +func (c *Computable) ComputeFilter(name string, process *gou.Process, args []interface{}, getFilter func(string) (*field.FilterDSL, string, error)) error { + return nil +} diff --git a/widgets/compute/types.go b/widgets/compute/types.go new file mode 100644 index 0000000000..7f1c3b204f --- /dev/null +++ b/widgets/compute/types.go @@ -0,0 +1,28 @@ +package compute + +const ( + // View View component + View uint8 = iota + // Edit Edit component + Edit + // Filter Filter component + Filter +) + +// Computable with computes +type Computable struct { + Computes *Maps +} + +// Maps compute mapping +type Maps struct { + Edit map[string][]Unit + View map[string][]Unit + Filter map[string][]Unit +} + +// Unit the compute unit +type Unit struct { + Name string // index + Kind uint8 // Type +} diff --git a/widgets/form/action.go b/widgets/form/action.go index a4e57b8ce3..61768fe608 100644 --- a/widgets/form/action.go +++ b/widgets/form/action.go @@ -4,11 +4,6 @@ import ( "fmt" "github.com/yaoapp/gou" - "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/log" - "github.com/yaoapp/kun/maps" - "github.com/yaoapp/yao/config" - "github.com/yaoapp/yao/i18n" "github.com/yaoapp/yao/widgets/action" ) @@ -16,31 +11,38 @@ var processActionDefaults = map[string]*action.Process{ "Setting": { Name: "yao.form.Setting", + Guard: "bearer-jwt", Process: "yao.form.Xgen", Default: []interface{}{nil}, }, "Component": { Name: "yao.form.Component", + Guard: "bearer-jwt", Default: []interface{}{nil, nil, nil}, }, "Find": { Name: "yao.form.Find", + Guard: "bearer-jwt", Default: []interface{}{nil, nil}, }, "Save": { Name: "yao.form.Save", + Guard: "bearer-jwt", Default: []interface{}{nil}, }, "Create": { Name: "yao.form.Create", + Guard: "bearer-jwt", Default: []interface{}{nil}, }, "Update": { Name: "yao.form.Update", + Guard: "bearer-jwt", Default: []interface{}{nil, nil}, }, "Delete": { Name: "yao.table.Delete", + Guard: "bearer-jwt", Default: []interface{}{nil}, }, } @@ -98,126 +100,3 @@ func (act *ActionDSL) BindModel(m *gou.Model) { act.Find.Default[1] = act.Bind.Option } } - -func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { - - form, err := Get(process) - if err != nil { - return nil, err - } - args := p.Args(process) - - // Process - name := p.Process - if name == "" { - name = p.ProcessBind - } - - if name == "" { - log.Error("[form] %s %s process is required", form.ID, p.Name) - return nil, fmt.Errorf("[form] %s %s process is required", form.ID, p.Name) - } - - // Before Hook - if p.Before != nil { - log.Trace("[form] %s %s before: exec(%v)", form.ID, p.Name, args) - newArgs, err := p.Before.Exec(args, process.Sid, process.Global) - if err != nil { - log.Error("[form] %s %s before: %s", form.ID, p.Name, err.Error()) - } else { - log.Trace("[form] %s %s before: args:%v", form.ID, p.Name, args) - args = newArgs - } - } - - // Compute In - switch p.Name { - case "yao.form.Save", "yao.form.Create": - switch args[0].(type) { - case map[string]interface{}, maps.MapStr: - data := any.Of(args[0]).Map().MapStrAny - err := form.computeSave(process, data) - if err != nil { - log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - args[0] = data - } - break - - case "yao.form.Update": - switch args[1].(type) { - case map[string]interface{}, maps.MapStr: - data := any.Of(args[1]).Map().MapStrAny - err := form.computeSave(process, data) - if err != nil { - log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - args[1] = data - } - break - } - - // Execute Process - act, err := gou.ProcessOf(name, args...) - if err != nil { - log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - return nil, fmt.Errorf("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - - res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - return nil, fmt.Errorf("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - - // Compute Out - switch p.Name { - - case "yao.form.Find": - switch res.(type) { - case map[string]interface{}, maps.MapStr: - data := any.MapOf(res).MapStrAny - err := form.computeFind(process, data) - if err != nil { - log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) - } - res = data - } - break - } - - // After hook - if p.After != nil { - log.Trace("[form] %s %s after: exec(%v)", form.ID, p.Name, res) - newRes, err := p.After.Exec(res, process.Sid, process.Global) - if err != nil { - log.Error("[form] %s %s after: %s", form.ID, p.Name, err.Error()) - } else { - log.Trace("[form] %s %s after: %v", form.ID, p.Name, newRes) - res = newRes - } - } - - // Tranlate - if p.Name == "yao.form.Setting" { - - widgets := []string{} - if form.Action.Bind.Model != "" { - m := gou.Select(form.Action.Bind.Model) - widgets = append(widgets, fmt.Sprintf("model.%s", m.ID)) - } - - if form.Action.Bind.Table != "" { - widgets = append(widgets, fmt.Sprintf("table.%s", form.Action.Bind.Table)) - } - - widgets = append(widgets, fmt.Sprintf("form.%s", form.ID)) - res, err = i18n.Trans(process.Lang(config.Conf.Lang), widgets, res) - if err != nil { - return nil, fmt.Errorf("[form] Trans.table.%s %s", form.ID, err.Error()) - } - - } - - return res, nil -} diff --git a/widgets/form/api.go b/widgets/form/api.go index 40a9c53758..953450baa8 100644 --- a/widgets/form/api.go +++ b/widgets/form/api.go @@ -1,11 +1,71 @@ package form import ( + "fmt" + + "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou" "github.com/yaoapp/yao/share" + "github.com/yaoapp/yao/widgets/action" ) +// Guard form widget guard +func Guard(c *gin.Context) { + + id := c.Param("id") + if id == "" { + abort(c, 400, "the form widget id does not found") + return + } + + form, has := Forms[id] + if !has { + abort(c, 404, fmt.Sprintf("the form widget %s does not exist", id)) + return + } + + act, err := form.getAction(c.FullPath()) + if err != nil { + abort(c, 404, err.Error()) + return + } + + err = act.UseGuard(c, id) + if err != nil { + abort(c, 400, err.Error()) + return + } + +} + +func abort(c *gin.Context, code int, message string) { + c.JSON(code, gin.H{"code": code, "message": message}) + c.Abort() +} + +func (form *DSL) getAction(path string) (*action.Process, error) { + + switch path { + case "/api/__yao/form/:id/setting": + return form.Action.Setting, nil + case "/api/__yao/form/:id/component/:xpath/:method": + return form.Action.Component, nil + case "/api/__yao/form/:id/find/:primary": + return form.Action.Find, nil + case "/api/__yao/form/:id/save": + return form.Action.Save, nil + case "/api/__yao/form/:id/create": + return form.Action.Create, nil + case "/api/__yao/form/:id/insert": + return form.Action.Update, nil + case "/api/__yao/form/:id/delete/:primary": + return form.Action.Delete, nil + } + + return nil, fmt.Errorf("the form widget %s %s action does not exist", form.ID, path) +} + // export API func exportAPI() error { @@ -13,7 +73,7 @@ func exportAPI() error { Name: "Widget Form API", Description: "Widget Form API", Version: share.VERSION, - Guard: "-", + Guard: "widget-form", Group: "__yao/form", Paths: []gou.Path{}, } diff --git a/widgets/form/compute.go b/widgets/form/compute.go index dc3d80e2ec..fd2fa78afd 100644 --- a/widgets/form/compute.go +++ b/widgets/form/compute.go @@ -2,78 +2,62 @@ package form import ( "fmt" - "strings" - "github.com/yaoapp/gou" - "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/widgets/compute" + "github.com/yaoapp/yao/widgets/field" ) -func (dsl *DSL) computeFind(process *gou.Process, values map[string]interface{}) error { - - messages := []string{} - for key := range values { - err := dsl.computeOut(process, key, values) - if err != nil { - messages = append(messages, err.Error()) +func (dsl *DSL) getField() func(string) (*field.ColumnDSL, string, error) { + return func(name string) (*field.ColumnDSL, string, error) { + field, has := dsl.Fields.Form[name] + if !has { + return nil, "fields.table", fmt.Errorf("fields.table.%s does not exist", name) } + return &field, "fields.table", nil } - - if len(messages) > 0 { - return fmt.Errorf("%s", strings.Join(messages, ";")) - } - - return nil } -func (dsl *DSL) computeSave(process *gou.Process, values map[string]interface{}) error { - - messages := []string{} - for key := range values { - err := dsl.computeIn(process, key, values) - if err != nil { - messages = append(messages, err.Error()) +func (dsl *DSL) computeMapping() error { + if dsl.Computes == nil { + dsl.Computes = &compute.Maps{ + Filter: map[string][]compute.Unit{}, + Edit: map[string][]compute.Unit{}, + View: map[string][]compute.Unit{}, } } - if len(messages) > 0 { - return fmt.Errorf("%s", strings.Join(messages, ";")) + if dsl.Fields == nil { + return nil } - return nil -} - -func (dsl *DSL) computeIn(process *gou.Process, key string, values map[string]interface{}) error { - if name, has := dsl.ComputesIn[key]; has { - compute, err := gou.ProcessOf(name, key, values[key], values) - if err != nil { - log.Error("[form] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[form] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) + if dsl.Fields.Form != nil && dsl.Layout.Form != nil && dsl.Layout.Form.Sections != nil { + for _, section := range dsl.Layout.Form.Sections { + + for _, inst := range section.Columns { + + if field, has := dsl.Fields.Form[inst.Name]; has { + + // View + if field.View != nil && field.View.Compute != nil { + bind := field.ViewBind() + if _, has := dsl.Computes.View[bind]; !has { + dsl.Computes.View[bind] = []compute.Unit{} + } + dsl.Computes.View[bind] = append(dsl.Computes.View[bind], compute.Unit{Name: inst.Name, Kind: compute.View}) + } + + // Edit + if field.Edit != nil && field.Edit.Compute != nil { + bind := field.EditBind() + if _, has := dsl.Computes.Edit[bind]; !has { + dsl.Computes.Edit[bind] = []compute.Unit{} + } + dsl.Computes.Edit[bind] = append(dsl.Computes.Edit[bind], compute.Unit{Name: inst.Name, Kind: compute.Edit}) + } + } + } } - - res, err := compute.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[form] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[form] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) - } - values[key] = res } - return nil -} -func (dsl *DSL) computeOut(process *gou.Process, key string, values map[string]interface{}) error { - if name, has := dsl.ComputesOut[key]; has { - compute, err := gou.ProcessOf(name, key, values[key], values) - if err != nil { - log.Error("[form] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[form] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - } - - res, err := compute.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[form] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[form] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - } - values[key] = res - } return nil } diff --git a/widgets/form/form.go b/widgets/form/form.go index 0c7afe16af..2b4b1d5c44 100644 --- a/widgets/form/form.go +++ b/widgets/form/form.go @@ -53,11 +53,9 @@ var Forms map[string]*DSL = map[string]*DSL{} // New create a new DSL func New(id string) *DSL { return &DSL{ - ID: id, - Fields: &FieldsDSL{Form: field.Columns{}}, - CProps: field.CloudProps{}, - ComputesIn: field.ComputeFields{}, - ComputesOut: field.ComputeFields{}, + ID: id, + Fields: &FieldsDSL{Form: field.Columns{}}, + CProps: field.CloudProps{}, } } @@ -170,7 +168,10 @@ func MustGet(form interface{}) *DSL { func (dsl *DSL) Parse() error { // ComputeFields - // dsl.Fields.Form.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) + err := dsl.computeMapping() + if err != nil { + return err + } // Columns return dsl.Fields.Form.CPropsMerge(dsl.CProps, func(name string, kind string, column field.ColumnDSL) (xpath string) { diff --git a/widgets/form/handler.go b/widgets/form/handler.go new file mode 100644 index 0000000000..03aa37aea3 --- /dev/null +++ b/widgets/form/handler.go @@ -0,0 +1,125 @@ +package form + +import ( + "fmt" + "strings" + + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/i18n" + "github.com/yaoapp/yao/widgets/action" +) + +// ******************************** +// * Execute the process of form * +// ******************************** +// Life-Circle: Before Hook → Compute Edit → Run Process → Compute View → After Hook +// Execute Compute Edit On: Save, Create, Update +// Execute Compute View On: Find +func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { + + form, err := Get(process) + if err != nil { + return nil, err + } + args := p.Args(process) + + // Process + name := p.Process + if name == "" { + name = p.ProcessBind + } + + if name == "" { + log.Error("[form] %s %s process is required", form.ID, p.Name) + return nil, fmt.Errorf("[form] %s %s process is required", form.ID, p.Name) + } + + // Before Hook + if p.Before != nil { + log.Trace("[form] %s %s before: exec(%v)", form.ID, p.Name, args) + newArgs, err := p.Before.Exec(args, process.Sid, process.Global) + if err != nil { + log.Error("[form] %s %s before: %s", form.ID, p.Name, err.Error()) + } else { + log.Trace("[form] %s %s before: args:%v", form.ID, p.Name, args) + args = newArgs + } + } + + // Compute Edit + err = form.ComputeEdit(p.Name, process, args, form.getField()) + if err != nil { + log.Error("[form] %s %s Compute Edit Error: %s", form.ID, p.Name, err.Error()) + } + + // Execute Process + act, err := gou.ProcessOf(name, args...) + if err != nil { + log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) + return nil, fmt.Errorf("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) + } + + res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() + if err != nil { + log.Error("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) + return nil, fmt.Errorf("[form] %s %s -> %s %s", form.ID, p.Name, name, err.Error()) + } + + // Compute View + err = form.ComputeView(p.Name, process, res, form.getField()) + if err != nil { + log.Error("[form] %s %s Compute View Error: %s", form.ID, p.Name, err.Error()) + } + + // After hook + if p.After != nil { + log.Trace("[form] %s %s after: exec(%v)", form.ID, p.Name, res) + newRes, err := p.After.Exec(res, process.Sid, process.Global) + if err != nil { + log.Error("[form] %s %s after: %s", form.ID, p.Name, err.Error()) + } else { + log.Trace("[form] %s %s after: %v", form.ID, p.Name, newRes) + res = newRes + } + } + + // Tranlate the result + newRes, err := form.translate(p.Name, process, res) + if err != nil { + return nil, fmt.Errorf("[form] %s %s Translate Error: %s", form.ID, p.Name, err.Error()) + } + + return newRes, nil +} + +// translateSetting +func (dsl *DSL) translate(name string, process *gou.Process, data interface{}) (interface{}, error) { + + if strings.ToLower(name) != "yao.form.setting" { + return data, nil + } + + widgets := []string{} + if dsl.Action.Bind.Model != "" { + m := gou.Select(dsl.Action.Bind.Model) + widgets = append(widgets, fmt.Sprintf("model.%s", m.ID)) + } + + if dsl.Action.Bind.Table != "" { + widgets = append(widgets, fmt.Sprintf("table.%s", dsl.Action.Bind.Table)) + } + + if dsl.Action.Bind.Form != "" { + widgets = append(widgets, fmt.Sprintf("form.%s", dsl.Action.Bind.Form)) + } + + widgets = append(widgets, fmt.Sprintf("form.%s", dsl.ID)) + res, err := i18n.Trans(process.Lang(config.Conf.Lang), widgets, data) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/widgets/form/types.go b/widgets/form/types.go index 4b4d92479e..0c52b1d296 100644 --- a/widgets/form/types.go +++ b/widgets/form/types.go @@ -3,21 +3,21 @@ package form import ( "github.com/yaoapp/yao/widgets/action" "github.com/yaoapp/yao/widgets/component" + "github.com/yaoapp/yao/widgets/compute" "github.com/yaoapp/yao/widgets/field" "github.com/yaoapp/yao/widgets/hook" ) // DSL the form DSL type DSL struct { - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Action *ActionDSL `json:"action"` - Layout *LayoutDSL `json:"layout"` - Fields *FieldsDSL `json:"fields"` - Config map[string]interface{} `json:"config,omitempty"` - ComputesIn field.ComputeFields `json:"-"` - ComputesOut field.ComputeFields `json:"-"` - CProps field.CloudProps `json:"-"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Action *ActionDSL `json:"action"` + Layout *LayoutDSL `json:"layout"` + Fields *FieldsDSL `json:"fields"` + Config map[string]interface{} `json:"config,omitempty"` + CProps field.CloudProps `json:"-"` + compute.Computable } // ActionDSL the form action DSL @@ -47,6 +47,7 @@ type BindActionDSL struct { Model string `json:"model,omitempty"` // bind model Store string `json:"store,omitempty"` // bind store Table string `json:"table,omitempty"` // bind table + Form string `json:"form,omitempty"` // bind form Option map[string]interface{} `json:"option,omitempty"` // bind option } diff --git a/widgets/table/compute.go b/widgets/table/compute.go index f64c47385c..b26f6f692b 100644 --- a/widgets/table/compute.go +++ b/widgets/table/compute.go @@ -2,26 +2,37 @@ package table import ( "fmt" - "reflect" - "strings" - "github.com/yaoapp/gou" - "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/maps" + "github.com/yaoapp/yao/widgets/compute" + "github.com/yaoapp/yao/widgets/field" ) -var computeViews = map[string]bool{ - "yao.table.find": true, - "yao.table.get": true, - "yao.table.search": true, +func (dsl *DSL) getField() func(string) (*field.ColumnDSL, string, error) { + return func(name string) (*field.ColumnDSL, string, error) { + field, has := dsl.Fields.Table[name] + if !has { + return nil, "fields.table", fmt.Errorf("fields.table.%s does not exist", name) + } + return &field, "fields.table", nil + } +} + +func (dsl *DSL) getFilter() func(string) (*field.FilterDSL, string, error) { + return func(name string) (*field.FilterDSL, string, error) { + field, has := dsl.Fields.Filter[name] + if !has { + return nil, "fields.filter", fmt.Errorf("fields.filter.%s does not exist", name) + } + return &field, "fields.filter", nil + } } func (dsl *DSL) computeMapping() error { - if dsl.computes == nil { - dsl.computes = &computeMaps{ - filter: map[string][]compute{}, - edit: map[string][]compute{}, - view: map[string][]compute{}, + if dsl.Computes == nil { + dsl.Computes = &compute.Maps{ + Filter: map[string][]compute.Unit{}, + Edit: map[string][]compute.Unit{}, + View: map[string][]compute.Unit{}, } } @@ -35,10 +46,10 @@ func (dsl *DSL) computeMapping() error { if filter, has := dsl.Fields.Filter[inst.Name]; has { if filter.Edit != nil && filter.Edit.Compute != nil { bind := filter.FilterBind() - if _, has := dsl.computes.filter[bind]; !has { - dsl.computes.filter[bind] = []compute{} + if _, has := dsl.Computes.Filter[bind]; !has { + dsl.Computes.Filter[bind] = []compute.Unit{} } - dsl.computes.filter[bind] = append(dsl.computes.filter[bind], compute{name: inst.Name, kind: TypeFilter}) + dsl.Computes.Filter[bind] = append(dsl.Computes.Filter[bind], compute.Unit{Name: inst.Name, Kind: compute.Filter}) } } } @@ -50,19 +61,19 @@ func (dsl *DSL) computeMapping() error { // View if field.View != nil && field.View.Compute != nil { bind := field.ViewBind() - if _, has := dsl.computes.view[bind]; !has { - dsl.computes.view[bind] = []compute{} + if _, has := dsl.Computes.View[bind]; !has { + dsl.Computes.View[bind] = []compute.Unit{} } - dsl.computes.view[bind] = append(dsl.computes.view[bind], compute{name: inst.Name, kind: TypeView}) + dsl.Computes.View[bind] = append(dsl.Computes.View[bind], compute.Unit{Name: inst.Name, Kind: compute.View}) } // Edit if field.Edit != nil && field.Edit.Compute != nil { bind := field.EditBind() - if _, has := dsl.computes.edit[bind]; !has { - dsl.computes.edit[bind] = []compute{} + if _, has := dsl.Computes.Edit[bind]; !has { + dsl.Computes.Edit[bind] = []compute.Unit{} } - dsl.computes.edit[bind] = append(dsl.computes.edit[bind], compute{name: inst.Name, kind: TypeEdit}) + dsl.Computes.Edit[bind] = append(dsl.Computes.Edit[bind], compute.Unit{Name: inst.Name, Kind: compute.Edit}) } } } @@ -70,212 +81,3 @@ func (dsl *DSL) computeMapping() error { return nil } - -func (dsl *DSL) computeView(name string, process *gou.Process, res interface{}) error { - - if _, has := computeViews[strings.ToLower(name)]; !has { - return nil - } - - switch res.(type) { - case []maps.MapStrAny, []interface{}: - return dsl.computeViewRows(name, process, res) - - case map[string]interface{}: - return dsl.computeViewRow(name, process, res.(map[string]interface{})) - - case maps.MapStrAny: - return dsl.computeViewRow(name, process, res.(maps.MapStrAny)) - } - - return fmt.Errorf("res should be a map or array, but got a %s", reflect.ValueOf(res).Kind().String()) -} - -func (dsl *DSL) computeViewRows(name string, process *gou.Process, res interface{}) error { - switch res.(type) { - - case []interface{}: - messages := []string{} - for i := range res.([]interface{}) { - err := dsl.computeView(name, process, res.([]interface{})[i]) - if err != nil { - messages = append(messages, err.Error()) - } - } - if len(messages) > 0 { - return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) - } - return nil - - case []maps.MapStrAny: - messages := []string{} - for i := range res.([]maps.MapStrAny) { - err := dsl.computeView(name, process, res.([]maps.MapStrAny)[i]) - if err != nil { - messages = append(messages, fmt.Sprintf("%d %s", i, err.Error())) - } - } - if len(messages) > 0 { - return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) - } - return nil - } - - return nil -} - -func (dsl *DSL) computeViewRow(name string, process *gou.Process, res map[string]interface{}) error { - - messages := []string{} - row := maps.MapOf(res).Dot() - data := maps.StrAny{"row": row}.Dot() - - // page - if row.Has("data") && row.Has("total") && - row.Has("pagesize") && row.Has("pagecnt") && - row.Has("prev") && row.Has("next") { - switch res["data"].(type) { - - case []maps.MapStrAny: - return dsl.computeViewRows(name, process, res["data"].([]maps.MapStrAny)) - - case []interface{}: - return dsl.computeViewRows(name, process, res["data"].([]interface{})) - } - } - - for key, computes := range dsl.computes.view { - c := computes[0] - field := dsl.Fields.Table[c.name] - data.Set("value", res[key]) - data.Merge(any.MapOf(field.View.Map()).MapStrAny.Dot()) - new, err := field.View.Compute.Value(data, process.Sid, process.Global) - if err != nil { - res[key] = nil - messages = append(messages, fmt.Sprintf("fields.table.%s bind: %s, value: %v error: %s", c.name, key, res[key], err.Error())) - continue - } - res[key] = new - } - - if len(messages) > 0 { - return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) - } - - return nil -} - -func (dsl *DSL) computeEdit(name string, process *gou.Process, args []interface{}) error { - name = strings.ToLower(name) - switch name { - case "yao.table.save", "yao.table.create": - if len(args) == 0 { - return nil - } - switch args[0].(type) { - case maps.MapStr: - return dsl.computeEditRow(name, process, args[0].(maps.MapStr)) - - case map[string]interface{}: - return dsl.computeEditRow(name, process, args[0].(map[string]interface{})) - } - return nil - - case "yao.table.update", "yao.table.updatewhere", "yao.table.updatein": - if len(args) < 2 { - return nil - } - - switch args[1].(type) { - case maps.MapStr: - return dsl.computeEditRow(name, process, args[1].(maps.MapStr)) - - case map[string]interface{}: - return dsl.computeEditRow(name, process, args[1].(map[string]interface{})) - } - return nil - - case "yao.table.insert": - if len(args) < 2 { - return nil - } - - if _, ok := args[0].([]string); !ok { - return fmt.Errorf("args[0] is not a []string %s", reflect.ValueOf(args[0]).Type().Name()) - } - - if _, ok := args[1].([][]interface{}); !ok { - return fmt.Errorf("args[1] is not a [][] %s", reflect.ValueOf(args[1]).Type().Name()) - } - - return dsl.computeEditRows(name, process, args[0].([]string), args[1].([][]interface{})) - } - - return nil -} - -func (dsl *DSL) computeEditRow(name string, process *gou.Process, res map[string]interface{}) error { - - messages := []string{} - row := maps.MapOf(res).Dot() - data := maps.StrAny{"row": row}.Dot() - for key := range res { - if computes, has := dsl.computes.edit[key]; has { - c := computes[0] - field := dsl.Fields.Table[c.name] - data.Set("value", res[key]) - data.Merge(any.MapOf(field.Edit.Map()).MapStrAny.Dot()) - new, err := field.Edit.Compute.Value(data, process.Sid, process.Global) - if err != nil { - messages = append(messages, fmt.Sprintf("fields.table.%s bind: %s, value: %v error: %s", c.name, key, res[key], err.Error())) - continue - } - res[key] = new - } - } - - if len(messages) > 0 { - return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) - } - - return nil -} - -func (dsl *DSL) computeEditRows(name string, process *gou.Process, columns []string, res [][]interface{}) error { - - messages := []string{} - keys := map[string]int{} - for i, name := range columns { - keys[name] = i - } - - for i := range res { - if len(keys) != len(res[i]) { - continue - } - - row := map[string]interface{}{} - for i, v := range res[i] { - row[columns[i]] = v - } - - err := dsl.computeEditRow(name, process, row) - if err != nil { - messages = append(messages, err.Error()) - } - - for k, v := range row { - res[i][keys[k]] = v - } - } - - if len(messages) > 0 { - return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) - } - - return nil -} - -func (dsl *DSL) computeFilter(name string, process *gou.Process, args []interface{}) error { - return nil -} diff --git a/widgets/table/compute_test.go b/widgets/table/compute_test.go index 83de6f612d..0c692c676d 100644 --- a/widgets/table/compute_test.go +++ b/widgets/table/compute_test.go @@ -18,13 +18,13 @@ func TestComputeMapping(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, 11, len(tab.computes.view)) - assert.Equal(t, 1, len(tab.computes.view["created_at"])) - assert.Equal(t, 1, len(tab.computes.view["stay"])) - assert.Equal(t, 4, len(tab.computes.edit)) - assert.Equal(t, 1, len(tab.computes.edit["stay"])) - assert.Equal(t, 2, len(tab.computes.filter)) - assert.Equal(t, 2, len(tab.computes.filter["where.name.like"])) + assert.Equal(t, 11, len(tab.Computes.View)) + assert.Equal(t, 1, len(tab.Computes.View["created_at"])) + assert.Equal(t, 1, len(tab.Computes.View["stay"])) + assert.Equal(t, 4, len(tab.Computes.Edit)) + assert.Equal(t, 1, len(tab.Computes.Edit["stay"])) + assert.Equal(t, 2, len(tab.Computes.Filter)) + assert.Equal(t, 2, len(tab.Computes.Filter["where.name.like"])) } func TestComputeFind(t *testing.T) { diff --git a/widgets/table/handler.go b/widgets/table/handler.go index 1b92b2ce12..9c0c3ddba7 100644 --- a/widgets/table/handler.go +++ b/widgets/table/handler.go @@ -2,6 +2,7 @@ package table import ( "fmt" + "strings" "github.com/yaoapp/gou" "github.com/yaoapp/kun/log" @@ -15,7 +16,7 @@ import ( // ******************************** // Life-Circle: Compute Filter → Before Hook → Compute Edit → Run Process → Compute View → After Hook // Execute Compute Filter On: Search, Get, Find -// Execute Compute Edit On: Save, Create, Update, UpdateWhere, UpdateIn, Insert(not support yet) +// Execute Compute Edit On: Save, Create, Update, UpdateWhere, UpdateIn, Insert // Execute Compute View On: Search, Get, Find func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { @@ -37,7 +38,7 @@ func processHandler(p *action.Process, process *gou.Process) (interface{}, error } // Compute Filter - err = tab.computeFilter(p.Name, process, args) + err = tab.ComputeFilter(p.Name, process, args, tab.getFilter()) if err != nil { log.Error("[table] %s %s Compute Filter Error: %s", tab.ID, p.Name, err.Error()) } @@ -55,7 +56,7 @@ func processHandler(p *action.Process, process *gou.Process) (interface{}, error } // Compute Edit - err = tab.computeEdit(p.Name, process, args) + err = tab.ComputeEdit(p.Name, process, args, tab.getField()) if err != nil { log.Error("[table] %s %s Compute Edit Error: %s", tab.ID, p.Name, err.Error()) } @@ -74,7 +75,7 @@ func processHandler(p *action.Process, process *gou.Process) (interface{}, error } // Compute View - err = tab.computeView(p.Name, process, res) + err = tab.ComputeView(p.Name, process, res, tab.getField()) if err != nil { log.Error("[table] %s %s Compute View Error: %s", tab.ID, p.Name, err.Error()) } @@ -103,7 +104,7 @@ func processHandler(p *action.Process, process *gou.Process) (interface{}, error // translateSetting func (dsl *DSL) translate(name string, process *gou.Process, data interface{}) (interface{}, error) { - if name != "yao.table.Setting" { + if strings.ToLower(name) != "yao.table.setting" { return data, nil } diff --git a/widgets/table/types.go b/widgets/table/types.go index f0b6deae2a..68a3d5f620 100644 --- a/widgets/table/types.go +++ b/widgets/table/types.go @@ -3,41 +3,22 @@ package table import ( "github.com/yaoapp/yao/widgets/action" "github.com/yaoapp/yao/widgets/component" + "github.com/yaoapp/yao/widgets/compute" "github.com/yaoapp/yao/widgets/field" "github.com/yaoapp/yao/widgets/hook" ) -const ( - // TypeView View component - TypeView uint8 = iota - // TypeEdit Edit component - TypeEdit - // TypeFilter Filter component - TypeFilter -) - // DSL the table DSL type DSL struct { - Root string `json:"-"` - ID string `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Action *ActionDSL `json:"action"` - Layout *LayoutDSL `json:"layout"` - Fields *FieldsDSL `json:"fields"` - Config map[string]interface{} `json:"config,omitempty"` - CProps field.CloudProps `json:"-"` - computes *computeMaps -} - -type computeMaps struct { - edit map[string][]compute - view map[string][]compute - filter map[string][]compute -} - -type compute struct { - name string // index - kind uint8 // Type + Root string `json:"-"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Action *ActionDSL `json:"action"` + Layout *LayoutDSL `json:"layout"` + Fields *FieldsDSL `json:"fields"` + Config map[string]interface{} `json:"config,omitempty"` + CProps field.CloudProps `json:"-"` + compute.Computable } // ActionDSL the table action DSL