[change] optimize forms

This commit is contained in:
Max 2022-10-22 17:32:37 +08:00
parent 7e0732e973
commit 6bbcfec0cd
13 changed files with 601 additions and 493 deletions

View file

@ -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()
}

244
widgets/compute/compute.go Normal file
View file

@ -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
}

28
widgets/compute/types.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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{},
}

View file

@ -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 {
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
}
}
messages := []string{}
for key := range values {
err := dsl.computeOut(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
}
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})
}
}
}
}
}
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())
}
}
if len(messages) > 0 {
return fmt.Errorf("%s", strings.Join(messages, ";"))
}
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())
}
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
}

View file

@ -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) {

125
widgets/form/handler.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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