diff --git a/cmd/start.go b/cmd/start.go index 4e9c6a29..8c5449af 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -141,8 +141,8 @@ var startCmd = &cobra.Command{ func adminRoot() (string, int) { adminRoot := "/yao/" - if root, ok := share.App.Optional["adminRoot"].(string); ok && root != "" { - root = strings.TrimPrefix(root, "/") + if share.App.AdminRoot != "" { + root := strings.TrimPrefix(share.App.AdminRoot, "/") root = strings.TrimSuffix(root, "/") adminRoot = fmt.Sprintf("/%s/", root) } diff --git a/widgets/chart/chart.go b/widgets/chart/chart.go index 99169403..1eeb9d1d 100644 --- a/widgets/chart/chart.go +++ b/widgets/chart/chart.go @@ -143,7 +143,7 @@ func MustGet(chart interface{}) *DSL { func (dsl *DSL) Parse() error { // ComputeFields - dsl.Fields.Chart.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) + // dsl.Fields.Chart.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) // Filters err := dsl.Fields.Filter.CPropsMerge(dsl.CProps, func(name string, filter field.FilterDSL) (xpath string) { diff --git a/widgets/component/component.go b/widgets/component/component.go index 00204409..482ac211 100644 --- a/widgets/component/component.go +++ b/widgets/component/component.go @@ -38,8 +38,6 @@ func (dsl *DSL) Clone() *DSL { new := DSL{ Bind: dsl.Bind, Type: dsl.Type, - In: dsl.In, - Out: dsl.Out, Props: PropsDSL{}, } if dsl.Props != nil { diff --git a/widgets/component/handlers.go b/widgets/component/handlers.go index 02f0083d..8be94a33 100644 --- a/widgets/component/handlers.go +++ b/widgets/component/handlers.go @@ -6,6 +6,7 @@ import ( ) var hanlders = map[string]ComputeHanlder{ + "Get": Get, "Trim": Trim, "Concat": Concat, "QueryString": Trim, @@ -46,3 +47,11 @@ func Concat(args ...interface{}) (interface{}, error) { } return res, nil } + +// Get value +func Get(args ...interface{}) (interface{}, error) { + if len(args) == 0 { + return nil, nil + } + return args[0], nil +} diff --git a/widgets/component/types.go b/widgets/component/types.go index b21a6598..5b5b5386 100644 --- a/widgets/component/types.go +++ b/widgets/component/types.go @@ -4,8 +4,6 @@ package component type DSL struct { Bind string `json:"bind,omitempty"` Type string `json:"type,omitempty"` - In string `json:"in,omitempty"` - Out string `json:"out,omitempty"` Compute *Compute `json:"compute,omitempty"` Props PropsDSL `json:"props,omitempty"` } diff --git a/widgets/field/column.go b/widgets/field/column.go index fe19ed11..382ef076 100644 --- a/widgets/field/column.go +++ b/widgets/field/column.go @@ -1,9 +1,6 @@ package field import ( - "fmt" - "strings" - "github.com/yaoapp/yao/widgets/component" "github.com/yaoapp/yao/widgets/expression" ) @@ -38,14 +35,28 @@ func (column ColumnDSL) Replace(data map[string]interface{}) (*ColumnDSL, error) return &new, nil } +// ViewBind get the bind field name of view +func (column ColumnDSL) ViewBind() string { + if column.View != nil && column.View.Bind != "" { + return column.View.Bind + } + return column.Bind +} + +// EditBind get the bind field name of edit +func (column ColumnDSL) EditBind() string { + if column.Edit != nil && column.Edit.Bind != "" { + return column.Edit.Bind + } + return column.Bind +} + // Clone column func (column *ColumnDSL) Clone() *ColumnDSL { new := ColumnDSL{ Key: column.Key, Bind: column.Bind, Link: column.Link, - In: column.In, - Out: column.Out, } if column.View != nil { @@ -105,30 +116,6 @@ func (columns Columns) CPropsMerge(cloudProps map[string]component.CloudPropsDSL return nil } -// ComputeFieldsMerge merge the compute fields -func (columns Columns) ComputeFieldsMerge(computeInFields map[string]string, computeOutFields map[string]string) { - for name, column := range columns { - - // Compute In - if column.In != "" { - if !strings.Contains(string(column.In), ".") { - column.In = Compute(fmt.Sprintf("yao.component.%s", column.In)) - } - computeInFields[column.Bind] = string(column.In) - computeInFields[name] = string(column.In) - } - - // Compute Out - if column.Out != "" { - if !strings.Contains(string(column.Out), ".") { - column.In = Compute(fmt.Sprintf("yao.component.%s", column.Out)) - } - computeOutFields[column.Bind] = string(column.Out) - computeOutFields[name] = string(column.Out) - } - } -} - func mergeCProps(cloudProps map[string]component.CloudPropsDSL, cProps map[string]component.CloudPropsDSL) { for k, v := range cProps { cloudProps[k] = v diff --git a/widgets/field/column_test.go b/widgets/field/column_test.go index c57e2643..521ee3b1 100644 --- a/widgets/field/column_test.go +++ b/widgets/field/column_test.go @@ -30,8 +30,6 @@ func testColumn() ColumnDSL { return ColumnDSL{ Key: "${label || comment}", Bind: "${name}", - In: "TagEdit", - Out: "TagView", View: &component.DSL{ Type: "Tag", Props: component.PropsDSL{"pure": true}, diff --git a/widgets/field/filter.go b/widgets/field/filter.go index a797f70d..f4c62883 100644 --- a/widgets/field/filter.go +++ b/widgets/field/filter.go @@ -53,6 +53,14 @@ func (filter FilterDSL) Map() map[string]interface{} { return res } +// FilterBind get the bind field name of filter +func (filter FilterDSL) FilterBind() string { + if filter.Edit != nil && filter.Edit.Bind != "" { + return filter.Edit.Bind + } + return filter.Bind +} + // CPropsMerge merge the Filters cloud props func (filters Filters) CPropsMerge(cloudProps map[string]component.CloudPropsDSL, getXpath func(name string, filter FilterDSL) (xpath string)) error { diff --git a/widgets/field/types.go b/widgets/field/types.go index b667d7e5..7bde21ca 100644 --- a/widgets/field/types.go +++ b/widgets/field/types.go @@ -21,8 +21,6 @@ type ColumnDSL struct { Key string `json:"key,omitempty"` Bind string `json:"bind,omitempty"` Link string `json:"link,omitempty"` - In Compute `json:"in,omitempty"` - Out Compute `json:"out,omitempty"` View *component.DSL `json:"view,omitempty"` Edit *component.DSL `json:"edit,omitempty"` } diff --git a/widgets/form/form.go b/widgets/form/form.go index 5286d45c..0c7afe16 100644 --- a/widgets/form/form.go +++ b/widgets/form/form.go @@ -170,7 +170,7 @@ func MustGet(form interface{}) *DSL { func (dsl *DSL) Parse() error { // ComputeFields - dsl.Fields.Form.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) + // dsl.Fields.Form.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) // Columns return dsl.Fields.Form.CPropsMerge(dsl.CProps, func(name string, kind string, column field.ColumnDSL) (xpath string) { diff --git a/widgets/form/process_test.go b/widgets/form/process_test.go index bccbfde6..f0769e2c 100644 --- a/widgets/form/process_test.go +++ b/widgets/form/process_test.go @@ -25,10 +25,7 @@ func TestProcessFind(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "#FF0000", data.Get("status.color")) - assert.Equal(t, "status", data.Get("status.field")) - assert.Equal(t, "checked", data.Get("status.label")) - assert.Equal(t, "Cookie", data.Get("status.name")) + assert.Equal(t, "checked", data.Get("status")) } func TestProcessSave(t *testing.T) { @@ -57,7 +54,7 @@ func TestProcessSave(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessCreate(t *testing.T) { @@ -87,7 +84,7 @@ func TestProcessCreate(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessUpdate(t *testing.T) { @@ -115,7 +112,7 @@ func TestProcessUpdate(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessDelete(t *testing.T) { diff --git a/widgets/table/action.go b/widgets/table/action.go index d83a341e..3063abfe 100644 --- a/widgets/table/action.go +++ b/widgets/table/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" "github.com/yaoapp/yao/widgets/hook" ) @@ -233,158 +228,3 @@ func (act *ActionDSL) BindTable(tab *DSL) error { return nil } - -func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { - - tab, 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("[table] %s %s process is required", tab.ID, p.Name) - return nil, fmt.Errorf("[table] %s %s process is required", tab.ID, p.Name) - } - - // Before Hook - if p.Before != nil { - log.Trace("[table] %s %s before: exec(%v)", tab.ID, p.Name, args) - newArgs, err := p.Before.Exec(args, process.Sid, process.Global) - if err != nil { - log.Error("[table] %s %s before: %s", tab.ID, p.Name, err.Error()) - } else { - log.Trace("[table] %s %s before: args:%v", tab.ID, p.Name, args) - args = newArgs - } - } - - // Compute In - switch p.Name { - case "yao.table.Save", "yao.table.Create": - switch args[0].(type) { - case map[string]interface{}, maps.MapStr: - data := any.Of(args[0]).Map().MapStrAny - err := tab.computeSave(process, data) - if err != nil { - log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - args[0] = data - } - break - - case "yao.table.Update", "yao.table.UpdateWhere", "yao.table.UpdateIn": - switch args[1].(type) { - case map[string]interface{}, maps.MapStr: - data := any.Of(args[1]).Map().MapStrAny - err := tab.computeSave(process, data) - if err != nil { - log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - args[1] = data - } - break - - case "yao.table.Insert": - break - } - - // Execute Process - act, err := gou.ProcessOf(name, args...) - if err != nil { - log.Error("[table] %s %s -> %s %s %v", tab.ID, p.Name, name, err.Error(), args) - return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - - res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() - if err != nil { - log.Error("[table] %s %s -> %s %s %v", tab.ID, p.Name, name, err.Error(), args) - return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - - // Compute Out - switch p.Name { - - case "yao.table.Search": - if newMap, ok := res.(map[string]interface{}); ok { - err := tab.computeSearch(process, newMap, "data") - if err != nil { - log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - res = newMap - } - break - - case "yao.table.Get": - if _, ok := res.([]maps.MapStr); ok { - data := []interface{}{} - for _, v := range res.([]maps.MapStr) { - data = append(data, map[string]interface{}(v)) - } - res = data - } - - if data, ok := res.([]interface{}); ok { - - err := tab.computeGet(process, data) - if err != nil { - log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - res = data - } - break - - case "yao.table.Find": - switch res.(type) { - case map[string]interface{}, maps.MapStr: - data := any.MapOf(res).MapStrAny - err := tab.computeFind(process, data) - if err != nil { - log.Error("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) - } - res = data - } - break - } - - // After hook - if p.After != nil { - log.Trace("[table] %s %s after: exec(%v)", tab.ID, p.Name, res) - newRes, err := p.After.Exec(res, process.Sid, process.Global) - if err != nil { - log.Error("[table] %s %s after: %s", tab.ID, p.Name, err.Error()) - } else { - log.Trace("[table] %s %s after: %v", tab.ID, p.Name, newRes) - res = newRes - } - } - - // Tranlate - if p.Name == "yao.table.Setting" { - - widgets := []string{} - if tab.Action.Bind.Model != "" { - m := gou.Select(tab.Action.Bind.Model) - widgets = append(widgets, fmt.Sprintf("model.%s", m.ID)) - } - - if tab.Action.Bind.Table != "" { - widgets = append(widgets, fmt.Sprintf("table.%s", tab.Action.Bind.Table)) - } - - widgets = append(widgets, fmt.Sprintf("table.%s", tab.ID)) - res, err = i18n.Trans(process.Lang(config.Conf.Lang), widgets, res) - if err != nil { - return nil, fmt.Errorf("[table] Trans.table.%s %s", tab.ID, err.Error()) - } - - } - - return res, nil -} diff --git a/widgets/table/api_test.go b/widgets/table/api_test.go index e3ab12ce..d14dda6b 100644 --- a/widgets/table/api_test.go +++ b/widgets/table/api_test.go @@ -71,10 +71,7 @@ func TestAPISearch(t *testing.T) { data := any.Of(resp).MapStr().Dot() assert.Equal(t, "20", fmt.Sprintf("%v", data.Get("pagesize"))) assert.Equal(t, "3", fmt.Sprintf("%v", data.Get("total"))) - assert.Equal(t, "#FF0000", data.Get("data.0.status.color")) - assert.Equal(t, "status", data.Get("data.0.status.field")) - assert.Equal(t, "checked", data.Get("data.0.status.label")) - assert.Equal(t, "Cookie", data.Get("data.0.status.name")) + assert.Equal(t, "checked", data.Get("data.0.status")) assert.Equal(t, "enabled", data.Get("data.0.mode")) assert.Equal(t, "1", fmt.Sprintf("%v", data.Get("data.0.doctor_id"))) diff --git a/widgets/table/compute.go b/widgets/table/compute.go index 96a2aa36..5537ef65 100644 --- a/widgets/table/compute.go +++ b/widgets/table/compute.go @@ -2,114 +2,281 @@ package table import ( "fmt" + "reflect" "strings" "github.com/yaoapp/gou" - "github.com/yaoapp/kun/any" - "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" ) -func (dsl *DSL) computeSearch(process *gou.Process, res map[string]interface{}, itemKey string) error { - if data, has := res[itemKey]; has { - if arr, ok := data.([]interface{}); ok { - err := dsl.computeGet(process, arr) - if err != nil { - return err - } - res[itemKey] = arr +var computeViews = map[string]bool{ + "yao.table.find": true, + "yao.table.get": true, + "yao.table.search": true, +} + +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.Fields == nil { + return nil + } + + // Filter + if dsl.Fields.Filter != nil && dsl.Layout.Filter != nil && dsl.Layout.Filter.Columns != nil { + for _, inst := range dsl.Layout.Filter.Columns { + 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{} + } + dsl.computes.filter[bind] = append(dsl.computes.filter[bind], compute{name: inst.Name, kind: TypeFilter}) + } + } + } + } + + if dsl.Fields.Table != nil && dsl.Layout.Table != nil && dsl.Layout.Table.Columns != nil { + for _, inst := range dsl.Layout.Table.Columns { + if field, has := dsl.Fields.Table[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{} + } + dsl.computes.view[bind] = append(dsl.computes.view[bind], compute{name: inst.Name, kind: TypeView}) + } + + // Edit + if field.Edit != nil && field.Edit.Compute != nil { + bind := field.EditBind() + if _, has := dsl.computes.edit[bind]; !has { + dsl.computes.edit[bind] = []compute{} + } + dsl.computes.edit[bind] = append(dsl.computes.edit[bind], compute{name: inst.Name, kind: TypeEdit}) + } + } + } + } + return nil } -func (dsl *DSL) computeGet(process *gou.Process, data []interface{}) error { +func (dsl *DSL) computeView(name string, process *gou.Process, res interface{}) error { - messages := []string{} - for idx, row := range data { - switch row.(type) { - case map[string]interface{}, maps.MapStr: - rowMap := any.MapOf(row).MapStrAny - err := dsl.computeFind(process, rowMap) + 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()) } - data[idx] = rowMap + } + 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.Set("type", field.View.Type) + data.Set("props", field.View.Props) + 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.Set("type", field.Edit.Type) + data.Set("props", field.Edit.Props) + 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("%s", strings.Join(messages, ";")) + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) } + return nil } -func (dsl *DSL) computeFind(process *gou.Process, values map[string]interface{}) error { +func (dsl *DSL) computeEditRows(name string, process *gou.Process, columns []string, res [][]interface{}) error { messages := []string{} - for key := range values { - err := dsl.computeOut(process, key, values) + 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()) } - } - 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()) + for k, v := range row { + res[i][keys[k]] = v } } if len(messages) > 0 { - return fmt.Errorf("%s", strings.Join(messages, ";")) + return fmt.Errorf("\n%s", strings.Join(messages, ";\n")) } 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("[table] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[table] %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("[table] %s compute-in -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[table] %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("[table] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[table] %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("[table] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - return fmt.Errorf("[table] %s compute-out -> %s %s %s", dsl.ID, name, key, err.Error()) - } - values[key] = res - } +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 new file mode 100644 index 00000000..83de6f61 --- /dev/null +++ b/widgets/table/compute_test.go @@ -0,0 +1,161 @@ +package table + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/gou" + "github.com/yaoapp/kun/any" +) + +func TestComputeMapping(t *testing.T) { + load(t) + clear(t) + testData(t) + + tab, err := Get("compute") + 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"])) +} + +func TestComputeFind(t *testing.T) { + + load(t) + clear(t) + testData(t) + + args := []interface{}{"compute", 1} + res, err := gou.NewProcess("yao.table.find", args...).Exec() + if err != nil { + t.Fatal(err) + } + data := any.Of(res).MapStr().Dot() + assert.Equal(t, "cat::Cookie-checked", data.Get("name_view")) +} + +func TestComputeGet(t *testing.T) { + + load(t) + clear(t) + testData(t) + + params := map[string]interface{}{"limit": 2} + + args := []interface{}{"compute", params} + res, err := gou.NewProcess("yao.table.get", args...).Exec() + if err != nil { + t.Fatal(err) + } + arr := any.Of(res).CArray() + data := any.Of(arr[0]).MapStr().Dot() + assert.Equal(t, "cat::Cookie-checked", data.Get("name_view")) + +} + +func TestComputeSearch(t *testing.T) { + + load(t) + clear(t) + testData(t) + + params := map[string]interface{}{} + args := []interface{}{"compute", params, 1, 5} + res, err := gou.NewProcess("yao.table.search", args...).Exec() + if err != nil { + t.Fatal(err) + } + data := any.Of(res).MapStr().Dot() + assert.Equal(t, "cat::Cookie-checked", data.Get("data.0.name_view")) +} + +func TestComputeSave(t *testing.T) { + load(t) + clear(t) + testData(t) + args := []interface{}{"compute", map[string]interface{}{ + "name": " New Pet ", + "type": "cat", + "status": "checked", + "mode": "enabled", + "stay": 66, + "cost": 24, + "doctor_id": 1, + }} + + res, err := gou.NewProcess("yao.table.Save", args...).Exec() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "4", fmt.Sprintf("%v", res)) + + res, err = gou.NewProcess("yao.table.find", "compute", res).Exec() + if err != nil { + t.Fatal(err) + } + + data := any.Of(res).MapStr().Dot() + assert.Equal(t, "New Pet", data.Get("name")) +} + +func TestComputeUpdate(t *testing.T) { + load(t) + clear(t) + testData(t) + args := []interface{}{"compute", 1, map[string]interface{}{ + "name": " New Pet ", + "type": "cat", + "status": "checked", + "mode": "enabled", + "stay": 66, + "cost": 24, + "doctor_id": 1, + }} + + _, err := gou.NewProcess("yao.table.Update", args...).Exec() + if err != nil { + t.Fatal(err) + } + + res, err := gou.NewProcess("yao.table.find", "compute", 1).Exec() + if err != nil { + t.Fatal(err) + } + + data := any.Of(res).MapStr().Dot() + assert.Equal(t, "New Pet", data.Get("name")) +} + +func TestComputeInsert(t *testing.T) { + load(t) + clear(t) + args := []interface{}{"compute", + []string{"name", "type", "status", "mode", "stay", "cost", "doctor_id"}, + [][]interface{}{ + {" Cookie ", "cat", "checked", "enabled", 200, 105, 1}, + {"Baby", "dog", "checked", "enabled", 186, 24, 1}, + {"Poo", "others", "checked", "enabled", 199, 66, 1}, + }, + } + + _, err := gou.NewProcess("yao.table.Insert", args...).Exec() + if err != nil { + t.Fatal(err) + } + + res, err := gou.NewProcess("yao.table.find", "compute", 1).Exec() + if err != nil { + t.Fatal(err) + } + + data := any.Of(res).MapStr().Dot() + assert.Equal(t, "Cookie", data.Get("name")) +} diff --git a/widgets/table/handler.go b/widgets/table/handler.go new file mode 100644 index 00000000..1b92b2ce --- /dev/null +++ b/widgets/table/handler.go @@ -0,0 +1,127 @@ +package table + +import ( + "fmt" + + "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 table * +// ******************************** +// 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 View On: Search, Get, Find +func processHandler(p *action.Process, process *gou.Process) (interface{}, error) { + + tab, err := Get(process) + if err != nil { + return nil, fmt.Errorf("[table] %s %s %s", tab.ID, p.Name, err.Error()) + } + args := p.Args(process) + + // Process + name := p.Process + if name == "" { + name = p.ProcessBind + } + + if name == "" { + log.Error("[table] %s %s process is required", tab.ID, p.Name) + return nil, fmt.Errorf("[table] %s %s process is required", tab.ID, p.Name) + } + + // Compute Filter + err = tab.computeFilter(p.Name, process, args) + if err != nil { + log.Error("[table] %s %s Compute Filter Error: %s", tab.ID, p.Name, err.Error()) + } + + // Before Hook + if p.Before != nil { + log.Trace("[table] %s %s before: exec(%v)", tab.ID, p.Name, args) + newArgs, err := p.Before.Exec(args, process.Sid, process.Global) + if err != nil { + log.Error("[table] %s %s before: %s", tab.ID, p.Name, err.Error()) + } else { + log.Trace("[table] %s %s before: args:%v", tab.ID, p.Name, args) + args = newArgs + } + } + + // Compute Edit + err = tab.computeEdit(p.Name, process, args) + if err != nil { + log.Error("[table] %s %s Compute Edit Error: %s", tab.ID, p.Name, err.Error()) + } + + // Execute Process + act, err := gou.ProcessOf(name, args...) + if err != nil { + log.Error("[table] %s %s -> %s %s %v", tab.ID, p.Name, name, err.Error(), args) + return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) + } + + res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec() + if err != nil { + log.Error("[table] %s %s -> %s %s %v", tab.ID, p.Name, name, err.Error(), args) + return nil, fmt.Errorf("[table] %s %s -> %s %s", tab.ID, p.Name, name, err.Error()) + } + + // Compute View + err = tab.computeView(p.Name, process, res) + if err != nil { + log.Error("[table] %s %s Compute View Error: %s", tab.ID, p.Name, err.Error()) + } + + // After hook + if p.After != nil { + log.Trace("[table] %s %s after: exec(%v)", tab.ID, p.Name, res) + newRes, err := p.After.Exec(res, process.Sid, process.Global) + if err != nil { + log.Error("[table] %s %s after: %s", tab.ID, p.Name, err.Error()) + } else { + log.Trace("[table] %s %s after: %v", tab.ID, p.Name, newRes) + res = newRes + } + } + + // Tranlate the result + newRes, err := tab.translate(p.Name, process, res) + if err != nil { + return nil, fmt.Errorf("[table] %s %s Translate Error: %s", tab.ID, p.Name, err.Error()) + } + + return newRes, nil +} + +// translateSetting +func (dsl *DSL) translate(name string, process *gou.Process, data interface{}) (interface{}, error) { + + if name != "yao.table.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)) + } + + widgets = append(widgets, fmt.Sprintf("table.%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/table/process_test.go b/widgets/table/process_test.go index 26ab5e12..dc8dab2f 100644 --- a/widgets/table/process_test.go +++ b/widgets/table/process_test.go @@ -34,10 +34,7 @@ func TestProcessSearch(t *testing.T) { assert.Equal(t, "AfterSearch", data.Get("after:hook")) assert.Equal(t, "1", fmt.Sprintf("%v", data.Get("pagesize"))) assert.Equal(t, "3", fmt.Sprintf("%v", data.Get("total"))) - assert.Equal(t, "#FF0000", data.Get("data.0.status.color")) - assert.Equal(t, "status", data.Get("data.0.status.field")) - assert.Equal(t, "checked", data.Get("data.0.status.label")) - assert.Equal(t, "Cookie", data.Get("data.0.status.name")) + assert.Equal(t, "checked", data.Get("data.0.status")) } func TestProcessGet(t *testing.T) { @@ -63,10 +60,7 @@ func TestProcessGet(t *testing.T) { assert.Equal(t, 2, len(arr)) data := any.Of(arr[0]).MapStr().Dot() - assert.Equal(t, "#FF0000", data.Get("status.color")) - assert.Equal(t, "status", data.Get("status.field")) - assert.Equal(t, "checked", data.Get("status.label")) - assert.Equal(t, "Cookie", data.Get("status.name")) + assert.Equal(t, "checked", data.Get("status")) } @@ -83,10 +77,7 @@ func TestProcessFind(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "#FF0000", data.Get("status.color")) - assert.Equal(t, "status", data.Get("status.field")) - assert.Equal(t, "checked", data.Get("status.label")) - assert.Equal(t, "Cookie", data.Get("status.name")) + assert.Equal(t, "checked", data.Get("status")) } func TestProcessSave(t *testing.T) { @@ -115,7 +106,7 @@ func TestProcessSave(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessCreate(t *testing.T) { @@ -145,7 +136,7 @@ func TestProcessCreate(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessUpdate(t *testing.T) { @@ -173,7 +164,7 @@ func TestProcessUpdate(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessUpdateWhere(t *testing.T) { @@ -203,7 +194,7 @@ func TestProcessUpdateWhere(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessUpdateIn(t *testing.T) { @@ -232,7 +223,7 @@ func TestProcessUpdateIn(t *testing.T) { } data := any.Of(res).MapStr().Dot() - assert.Equal(t, "New Pet|New Pet", data.Get("name")) + assert.Equal(t, "New Pet", data.Get("name")) } func TestProcessInsert(t *testing.T) { diff --git a/widgets/table/table.go b/widgets/table/table.go index 556cd0fd..fe817c88 100644 --- a/widgets/table/table.go +++ b/widgets/table/table.go @@ -230,13 +230,15 @@ func MustGet(table interface{}) *DSL { func (dsl *DSL) Parse() error { // ComputeFields - dsl.Fields.Table.ComputeFieldsMerge(dsl.ComputesIn, dsl.ComputesOut) + err := dsl.computeMapping() + if err != nil { + return err + } // Filters - err := dsl.Fields.Filter.CPropsMerge(dsl.CProps, func(name string, filter field.FilterDSL) (xpath string) { + err = dsl.Fields.Filter.CPropsMerge(dsl.CProps, func(name string, filter field.FilterDSL) (xpath string) { return fmt.Sprintf("fields.filter.%s.edit.props", name) }) - if err != nil { return err } diff --git a/widgets/table/types.go b/widgets/table/types.go index e3e3a82b..3bcee857 100644 --- a/widgets/table/types.go +++ b/widgets/table/types.go @@ -7,6 +7,15 @@ import ( "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:"-"` @@ -19,6 +28,18 @@ type DSL struct { ComputesIn field.ComputeFields `json:"-"` ComputesOut field.ComputeFields `json:"-"` 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 } // ActionDSL the table action DSL