mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-18 01:10:38 +08:00
update
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/oarkflow/jsonschema"
|
||||||
"github.com/oarkflow/mq/renderer"
|
"github.com/oarkflow/mq/renderer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ func main() {
|
|||||||
fmt.Printf("Error reading schema file: %v\n", err)
|
fmt.Printf("Error reading schema file: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var schema map[string]interface{}
|
compiler := jsonschema.NewCompiler()
|
||||||
if err := json.Unmarshal(schemaContent, &schema); err != nil {
|
schema, err := compiler.Compile(schemaContent)
|
||||||
fmt.Printf("Error parsing schema: %v\n", err)
|
if err != nil {
|
||||||
|
fmt.Printf("Error compiling schema: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Handle("/form.css", http.FileServer(http.Dir("templates")))
|
http.Handle("/form.css", http.FileServer(http.Dir("templates")))
|
||||||
http.HandleFunc("/render", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/render", func(w http.ResponseWriter, r *http.Request) {
|
||||||
templateName := r.URL.Query().Get("template")
|
templateName := r.URL.Query().Get("template")
|
||||||
|
12
go.mod
12
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/oarkflow/mq
|
module github.com/oarkflow/mq
|
||||||
|
|
||||||
go 1.24.0
|
go 1.24.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
github.com/gofiber/fiber/v2 v2.52.6
|
||||||
@@ -20,6 +20,15 @@ require (
|
|||||||
golang.org/x/time v0.11.0
|
golang.org/x/time v0.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect
|
||||||
|
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 // indirect
|
||||||
|
github.com/kaptinlin/go-i18n v0.1.4 // indirect
|
||||||
|
golang.org/x/text v0.25.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
@@ -31,6 +40,7 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/oarkflow/jsonschema v0.0.4
|
||||||
github.com/prometheus/client_model v0.6.1 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.63.0 // indirect
|
github.com/prometheus/common v0.63.0 // indirect
|
||||||
github.com/prometheus/procfs v0.16.0 // indirect
|
github.com/prometheus/procfs v0.16.0 // indirect
|
||||||
|
16
go.sum
16
go.sum
@@ -6,8 +6,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
|
github.com/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms=
|
||||||
github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE=
|
github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
@@ -16,6 +20,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 h1:b70jEaX2iaJSPZULSUxKtm73LBfsCrMsIlYCUgNGSIs=
|
||||||
|
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976/go.mod h1:ZGQeOwybjD8lkCjIyJfqR5LD2wMVHJ31d6GdPxoTsWY=
|
||||||
|
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 h1:c7gcNWTSr1gtLp6PyYi3wzvFCEcHJ4YRobDgqmIgf7Q=
|
||||||
|
github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092/go.mod h1:ZZAN4fkkful3l1lpJwF8JbW41ZiG9TwJ2ZlqzQovBNU=
|
||||||
|
github.com/kaptinlin/go-i18n v0.1.4 h1:wCiwAn1LOcvymvWIVAM4m5dUAMiHunTdEubLDk4hTGs=
|
||||||
|
github.com/kaptinlin/go-i18n v0.1.4/go.mod h1:g1fn1GvTgT4CiLE8/fFE1hboHWJ6erivrDpiDtCcFKg=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
@@ -42,10 +52,14 @@ github.com/oarkflow/jet v0.0.4 h1:rs0nTzodye/9zhrSX7FlR80Gjaty6ei2Ln0pmaUrdwg=
|
|||||||
github.com/oarkflow/jet v0.0.4/go.mod h1:YXIc47aYyx1xKpnmuz1Z9o88cxxa47r7X3lfUAxZ0Qg=
|
github.com/oarkflow/jet v0.0.4/go.mod h1:YXIc47aYyx1xKpnmuz1Z9o88cxxa47r7X3lfUAxZ0Qg=
|
||||||
github.com/oarkflow/json v0.0.21 h1:tBx4ufwC48UAd3fUCqLVH/dERpnZ85Dgw5/h7H2HMoM=
|
github.com/oarkflow/json v0.0.21 h1:tBx4ufwC48UAd3fUCqLVH/dERpnZ85Dgw5/h7H2HMoM=
|
||||||
github.com/oarkflow/json v0.0.21/go.mod h1:maoLmQZJ/8pF1MugtpVqzHJ59dH1Z7xFSNkhl9BQjYo=
|
github.com/oarkflow/json v0.0.21/go.mod h1:maoLmQZJ/8pF1MugtpVqzHJ59dH1Z7xFSNkhl9BQjYo=
|
||||||
|
github.com/oarkflow/jsonschema v0.0.4 h1:n5Sb7WVb7NNQzn/ei9++4VPqKXCPJhhsHeTGJkIuwmM=
|
||||||
|
github.com/oarkflow/jsonschema v0.0.4/go.mod h1:AxNG3Nk7KZxnnjRJlHLmS1wE9brtARu5caTFuicCtnA=
|
||||||
github.com/oarkflow/log v1.0.79 h1:DxhtkBGG+pUu6cudSVw5g75FbKEQJkij5w7n5AEN00M=
|
github.com/oarkflow/log v1.0.79 h1:DxhtkBGG+pUu6cudSVw5g75FbKEQJkij5w7n5AEN00M=
|
||||||
github.com/oarkflow/log v1.0.79/go.mod h1:U/4chr1DyOiQvS6JiQpjYTCJhK7RGR8xrXPsGlouLzM=
|
github.com/oarkflow/log v1.0.79/go.mod h1:U/4chr1DyOiQvS6JiQpjYTCJhK7RGR8xrXPsGlouLzM=
|
||||||
github.com/oarkflow/xid v1.2.8 h1:uCIX61Binq2RPMsqImZM6pPGzoZTmRyD6jguxF9aAA0=
|
github.com/oarkflow/xid v1.2.8 h1:uCIX61Binq2RPMsqImZM6pPGzoZTmRyD6jguxF9aAA0=
|
||||||
github.com/oarkflow/xid v1.2.8/go.mod h1:jG4YBh+swbjlWApGWDBYnsJEa7hi3CCpmuqhB3RAxVo=
|
github.com/oarkflow/xid v1.2.8/go.mod h1:jG4YBh+swbjlWApGWDBYnsJEa7hi3CCpmuqhB3RAxVo=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
@@ -74,6 +88,8 @@ golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
@@ -6,6 +6,8 @@ import (
|
|||||||
"html/template"
|
"html/template"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/oarkflow/jsonschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A single template for the entire group structure
|
// A single template for the entire group structure
|
||||||
@@ -152,8 +154,10 @@ var standardAttrs = []string{
|
|||||||
// FieldInfo represents metadata for a field extracted from JSONSchema
|
// FieldInfo represents metadata for a field extracted from JSONSchema
|
||||||
type FieldInfo struct {
|
type FieldInfo struct {
|
||||||
Name string
|
Name string
|
||||||
|
FieldPath string // Full path for nested fields (e.g., "user.address.street")
|
||||||
Order int
|
Order int
|
||||||
Definition map[string]any
|
Schema *jsonschema.Schema
|
||||||
|
IsRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupInfo represents metadata for a group extracted from JSONSchema
|
// GroupInfo represents metadata for a group extracted from JSONSchema
|
||||||
@@ -171,14 +175,14 @@ type GroupTitle struct {
|
|||||||
|
|
||||||
// JSONSchemaRenderer is responsible for rendering HTML fields based on JSONSchema
|
// JSONSchemaRenderer is responsible for rendering HTML fields based on JSONSchema
|
||||||
type JSONSchemaRenderer struct {
|
type JSONSchemaRenderer struct {
|
||||||
Schema map[string]any
|
Schema *jsonschema.Schema
|
||||||
HTMLLayout string
|
HTMLLayout string
|
||||||
TemplateData map[string]any // Data for template interpolation
|
TemplateData map[string]any // Data for template interpolation
|
||||||
cachedHTML string // Cached rendered HTML
|
cachedHTML string // Cached rendered HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJSONSchemaRenderer creates a new instance of JSONSchemaRenderer
|
// NewJSONSchemaRenderer creates a new instance of JSONSchemaRenderer
|
||||||
func NewJSONSchemaRenderer(schema map[string]any, htmlLayout string) *JSONSchemaRenderer {
|
func NewJSONSchemaRenderer(schema *jsonschema.Schema, htmlLayout string) *JSONSchemaRenderer {
|
||||||
return &JSONSchemaRenderer{
|
return &JSONSchemaRenderer{
|
||||||
Schema: schema,
|
Schema: schema,
|
||||||
HTMLLayout: htmlLayout,
|
HTMLLayout: htmlLayout,
|
||||||
@@ -204,7 +208,7 @@ func (r *JSONSchemaRenderer) interpolateTemplate(templateStr string) string {
|
|||||||
for key, value := range r.TemplateData {
|
for key, value := range r.TemplateData {
|
||||||
placeholder := fmt.Sprintf("{{%s}}", key)
|
placeholder := fmt.Sprintf("{{%s}}", key)
|
||||||
if valueStr, ok := value.(string); ok {
|
if valueStr, ok := value.(string); ok {
|
||||||
result = fmt.Sprintf("%s", bytes.ReplaceAll([]byte(result), []byte(placeholder), []byte(valueStr)))
|
result = strings.ReplaceAll(result, placeholder, valueStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -226,21 +230,30 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) {
|
|||||||
return r.cachedHTML, nil
|
return r.cachedHTML, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
groups := parseGroupsFromSchema(r.Schema)
|
groups := r.parseGroupsFromSchema()
|
||||||
var groupHTML bytes.Buffer
|
var groupHTML bytes.Buffer
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
groupHTML.WriteString(renderGroup(group))
|
groupHTML.WriteString(renderGroup(group))
|
||||||
}
|
}
|
||||||
|
|
||||||
formConfig := r.Schema["form"].(map[string]any)
|
// Extract form configuration
|
||||||
formClass, _ := formConfig["class"].(string)
|
var formClass, formAction, formMethod, formEnctype string
|
||||||
formAction, _ := formConfig["action"].(string)
|
if r.Schema.Form != nil {
|
||||||
formMethod, _ := formConfig["method"].(string)
|
if class, ok := r.Schema.Form["class"].(string); ok {
|
||||||
formEnctype, _ := formConfig["enctype"].(string)
|
formClass = class
|
||||||
|
}
|
||||||
|
if action, ok := r.Schema.Form["action"].(string); ok {
|
||||||
|
formAction = r.interpolateTemplate(action)
|
||||||
|
}
|
||||||
|
if method, ok := r.Schema.Form["method"].(string); ok {
|
||||||
|
formMethod = method
|
||||||
|
}
|
||||||
|
if enctype, ok := r.Schema.Form["enctype"].(string); ok {
|
||||||
|
formEnctype = enctype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Interpolate template data into form action
|
buttonsHTML := r.renderButtons()
|
||||||
formAction = r.interpolateTemplate(formAction)
|
|
||||||
buttonsHTML := renderButtons(formConfig)
|
|
||||||
|
|
||||||
// Create a new template with the layout and functions
|
// Create a new template with the layout and functions
|
||||||
tmpl, err := template.New("layout").Funcs(template.FuncMap{
|
tmpl, err := template.New("layout").Funcs(template.FuncMap{
|
||||||
@@ -269,32 +282,30 @@ func (r *JSONSchemaRenderer) RenderFields() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseGroupsFromSchema extracts and sorts groups and fields from schema
|
// parseGroupsFromSchema extracts and sorts groups and fields from schema
|
||||||
func parseGroupsFromSchema(schema map[string]any) []GroupInfo {
|
func (r *JSONSchemaRenderer) parseGroupsFromSchema() []GroupInfo {
|
||||||
formConfig, ok := schema["form"].(map[string]any)
|
if r.Schema.Form == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsData, ok := r.Schema.Form["groups"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
properties, ok := schema["properties"].(map[string]any)
|
groups, ok := groupsData.([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredFields map[string]bool = make(map[string]bool)
|
var result []GroupInfo
|
||||||
if reqFields, ok := schema["required"].([]any); ok {
|
for _, group := range groups {
|
||||||
for _, field := range reqFields {
|
groupMap, ok := group.(map[string]interface{})
|
||||||
if fieldName, ok := field.(string); ok {
|
if !ok {
|
||||||
requiredFields[fieldName] = true
|
continue
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var groups []GroupInfo
|
|
||||||
for _, group := range formConfig["groups"].([]any) {
|
|
||||||
groupMap := group.(map[string]any)
|
|
||||||
|
|
||||||
var groupTitle GroupTitle
|
var groupTitle GroupTitle
|
||||||
if titleMap, ok := groupMap["title"].(map[string]any); ok {
|
if titleMap, ok := groupMap["title"].(map[string]interface{}); ok {
|
||||||
if text, ok := titleMap["text"].(string); ok {
|
if text, ok := titleMap["text"].(string); ok {
|
||||||
groupTitle.Text = text
|
groupTitle.Text = text
|
||||||
}
|
}
|
||||||
@@ -304,37 +315,152 @@ func parseGroupsFromSchema(schema map[string]any) []GroupInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
groupClass, _ := groupMap["class"].(string)
|
groupClass, _ := groupMap["class"].(string)
|
||||||
|
if groupClass == "" {
|
||||||
|
groupClass = "form-group-fields"
|
||||||
|
}
|
||||||
|
|
||||||
var fields []FieldInfo
|
var fields []FieldInfo
|
||||||
for _, fieldName := range groupMap["fields"].([]any) {
|
if fieldsData, ok := groupMap["fields"].([]interface{}); ok {
|
||||||
fieldDef := properties[fieldName.(string)].(map[string]any)
|
for _, fieldName := range fieldsData {
|
||||||
order := 0
|
if fieldNameStr, ok := fieldName.(string); ok {
|
||||||
if ord, exists := fieldDef["order"].(int); exists {
|
// Handle nested field paths
|
||||||
order = ord
|
fieldInfos := r.extractFieldsFromPath(fieldNameStr, "")
|
||||||
|
fields = append(fields, fieldInfos...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldDefCopy := make(map[string]any)
|
|
||||||
for k, v := range fieldDef {
|
|
||||||
fieldDefCopy[k] = v
|
|
||||||
}
|
|
||||||
fieldDefCopy["isRequired"] = requiredFields[fieldName.(string)]
|
|
||||||
|
|
||||||
fields = append(fields, FieldInfo{
|
|
||||||
Name: fieldName.(string),
|
|
||||||
Order: order,
|
|
||||||
Definition: fieldDefCopy,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort fields by order
|
||||||
sort.Slice(fields, func(i, j int) bool {
|
sort.Slice(fields, func(i, j int) bool {
|
||||||
return fields[i].Order < fields[j].Order
|
orderI := 0
|
||||||
|
orderJ := 0
|
||||||
|
if fields[i].Schema.Order != nil {
|
||||||
|
orderI = *fields[i].Schema.Order
|
||||||
|
}
|
||||||
|
if fields[j].Schema.Order != nil {
|
||||||
|
orderJ = *fields[j].Schema.Order
|
||||||
|
}
|
||||||
|
return orderI < orderJ
|
||||||
})
|
})
|
||||||
groups = append(groups, GroupInfo{
|
|
||||||
|
result = append(result, GroupInfo{
|
||||||
Title: groupTitle,
|
Title: groupTitle,
|
||||||
Fields: fields,
|
Fields: fields,
|
||||||
GroupClass: groupClass,
|
GroupClass: groupClass,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return groups
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFieldsFromPath recursively extracts fields from a path, handling nested properties
|
||||||
|
func (r *JSONSchemaRenderer) extractFieldsFromPath(fieldPath, parentPath string) []FieldInfo {
|
||||||
|
var fields []FieldInfo
|
||||||
|
|
||||||
|
// Build the full path
|
||||||
|
fullPath := fieldPath
|
||||||
|
if parentPath != "" {
|
||||||
|
fullPath = parentPath + "." + fieldPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to the schema at this path
|
||||||
|
schema := r.getSchemaAtPath(fieldPath)
|
||||||
|
if schema == nil {
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this field is required
|
||||||
|
isRequired := r.isFieldRequired(fieldPath)
|
||||||
|
|
||||||
|
// If this schema has properties, it's a nested object
|
||||||
|
if schema.Properties != nil && len(*schema.Properties) > 0 {
|
||||||
|
// Recursively process nested properties
|
||||||
|
for propName, propSchema := range *schema.Properties {
|
||||||
|
nestedFields := r.extractFieldsFromNestedSchema(propName, fullPath, propSchema, isRequired)
|
||||||
|
fields = append(fields, nestedFields...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a leaf field
|
||||||
|
order := 0
|
||||||
|
if schema.Order != nil {
|
||||||
|
order = *schema.Order
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = append(fields, FieldInfo{
|
||||||
|
Name: fieldPath,
|
||||||
|
FieldPath: fullPath,
|
||||||
|
Order: order,
|
||||||
|
Schema: schema,
|
||||||
|
IsRequired: isRequired,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFieldsFromNestedSchema processes nested schema properties
|
||||||
|
func (r *JSONSchemaRenderer) extractFieldsFromNestedSchema(propName, parentPath string, propSchema *jsonschema.Schema, parentRequired bool) []FieldInfo {
|
||||||
|
var fields []FieldInfo
|
||||||
|
|
||||||
|
fullPath := propName
|
||||||
|
if parentPath != "" {
|
||||||
|
fullPath = parentPath + "." + propName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this nested field is required
|
||||||
|
isRequired := parentRequired || contains(r.Schema.Required, propName)
|
||||||
|
|
||||||
|
// If this property has nested properties, recurse
|
||||||
|
if propSchema.Properties != nil && len(*propSchema.Properties) > 0 {
|
||||||
|
for nestedPropName, nestedPropSchema := range *propSchema.Properties {
|
||||||
|
nestedFields := r.extractFieldsFromNestedSchema(nestedPropName, fullPath, nestedPropSchema, isRequired)
|
||||||
|
fields = append(fields, nestedFields...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a leaf field
|
||||||
|
order := 0
|
||||||
|
if propSchema.Order != nil {
|
||||||
|
order = *propSchema.Order
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = append(fields, FieldInfo{
|
||||||
|
Name: propName,
|
||||||
|
FieldPath: fullPath,
|
||||||
|
Order: order,
|
||||||
|
Schema: propSchema,
|
||||||
|
IsRequired: isRequired,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSchemaAtPath navigates to a schema at a given path
|
||||||
|
func (r *JSONSchemaRenderer) getSchemaAtPath(path string) *jsonschema.Schema {
|
||||||
|
if r.Schema.Properties == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(path, ".")
|
||||||
|
currentSchema := r.Schema
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
if currentSchema.Properties == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if propSchema, exists := (*currentSchema.Properties)[part]; exists {
|
||||||
|
currentSchema = propSchema
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSchema
|
||||||
|
}
|
||||||
|
|
||||||
|
// isFieldRequired checks if a field is required at the current schema level
|
||||||
|
func (r *JSONSchemaRenderer) isFieldRequired(fieldName string) bool {
|
||||||
|
return contains(r.Schema.Required, fieldName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderGroup generates HTML for a single group
|
// renderGroup generates HTML for a single group
|
||||||
@@ -352,9 +478,6 @@ func renderGroup(group GroupInfo) string {
|
|||||||
"GroupClass": group.GroupClass,
|
"GroupClass": group.GroupClass,
|
||||||
"FieldsHTML": template.HTML(fieldsHTML.String()),
|
"FieldsHTML": template.HTML(fieldsHTML.String()),
|
||||||
}
|
}
|
||||||
if group.GroupClass == "" {
|
|
||||||
data["GroupClass"] = "form-group-fields"
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tmpl.Execute(&groupHTML, data); err != nil {
|
if err := tmpl.Execute(&groupHTML, data); err != nil {
|
||||||
return "" // Return empty string on error
|
return "" // Return empty string on error
|
||||||
@@ -364,25 +487,24 @@ func renderGroup(group GroupInfo) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderField(field FieldInfo) string {
|
func renderField(field FieldInfo) string {
|
||||||
ui, ok := field.Definition["ui"].(map[string]any)
|
if field.Schema.UI == nil {
|
||||||
if !ok {
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
element, _ := ui["element"].(string)
|
element, ok := field.Schema.UI["element"].(string)
|
||||||
if element == "" {
|
if !ok || element == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build all attributes
|
// Build all attributes
|
||||||
allAttributes := buildAllAttributes(field, ui)
|
allAttributes := buildAllAttributes(field)
|
||||||
|
|
||||||
// Get content
|
// Get content
|
||||||
content := getFieldContent(field.Definition, ui)
|
content := getFieldContent(field)
|
||||||
contentHTML := getFieldContentHTML(field.Definition, ui)
|
contentHTML := getFieldContentHTML(field)
|
||||||
|
|
||||||
// Generate label if needed
|
// Generate label if needed
|
||||||
labelHTML := generateLabel(field, ui)
|
labelHTML := generateLabel(field)
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"Element": element,
|
"Element": element,
|
||||||
@@ -390,8 +512,8 @@ func renderField(field FieldInfo) string {
|
|||||||
"Content": content,
|
"Content": content,
|
||||||
"ContentHTML": template.HTML(contentHTML),
|
"ContentHTML": template.HTML(contentHTML),
|
||||||
"LabelHTML": template.HTML(labelHTML),
|
"LabelHTML": template.HTML(labelHTML),
|
||||||
"Class": getUIValue(ui, "class"),
|
"Class": getUIValue(field.Schema.UI, "class"),
|
||||||
"OptionsHTML": template.HTML(generateOptions(ui)),
|
"OptionsHTML": template.HTML(generateOptions(field.Schema.UI)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use specific template if available, otherwise use generic
|
// Use specific template if available, otherwise use generic
|
||||||
@@ -412,142 +534,133 @@ func renderField(field FieldInfo) string {
|
|||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAllAttributes(field FieldInfo, ui map[string]any) string {
|
func buildAllAttributes(field FieldInfo) string {
|
||||||
var attributes []string
|
var attributes []string
|
||||||
|
|
||||||
// Add standard attributes from ui
|
// Use the field path as the name attribute for nested fields
|
||||||
for _, attr := range standardAttrs {
|
fieldName := field.FieldPath
|
||||||
if value, exists := ui[attr]; exists {
|
if fieldName == "" {
|
||||||
if attr == "class" && value == "" {
|
fieldName = field.Name
|
||||||
continue // Skip empty class
|
}
|
||||||
|
|
||||||
|
// Add name attribute
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`name="%s"`, fieldName))
|
||||||
|
|
||||||
|
// Add standard attributes from UI
|
||||||
|
if field.Schema.UI != nil {
|
||||||
|
for _, attr := range standardAttrs {
|
||||||
|
if attr == "name" {
|
||||||
|
continue // Already handled above
|
||||||
|
}
|
||||||
|
if value, exists := field.Schema.UI[attr]; exists {
|
||||||
|
if attr == "class" && value == "" {
|
||||||
|
continue // Skip empty class
|
||||||
|
}
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, attr, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data-* and aria-* attributes
|
||||||
|
for key, value := range field.Schema.UI {
|
||||||
|
if strings.HasPrefix(key, "data-") || strings.HasPrefix(key, "aria-") {
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
||||||
}
|
}
|
||||||
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, attr, value))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle required field
|
// Handle required field
|
||||||
if isRequired, ok := field.Definition["isRequired"].(bool); ok && isRequired {
|
if field.IsRequired {
|
||||||
if !contains(attributes, "required=") {
|
if !containsAttribute(attributes, "required=") {
|
||||||
attributes = append(attributes, `required="required"`)
|
attributes = append(attributes, `required="required"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle input type based on field type
|
// Handle input type based on field type
|
||||||
element, _ := ui["element"].(string)
|
element, _ := field.Schema.UI["element"].(string)
|
||||||
if element == "input" {
|
if element == "input" {
|
||||||
if inputType := getInputType(field.Definition, ui); inputType != "" {
|
if inputType := getInputType(field.Schema); inputType != "" {
|
||||||
if !contains(attributes, "type=") {
|
if !containsAttribute(attributes, "type=") {
|
||||||
attributes = append(attributes, fmt.Sprintf(`type="%s"`, inputType))
|
attributes = append(attributes, fmt.Sprintf(`type="%s"`, inputType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data-* and aria-* attributes
|
|
||||||
for key, value := range ui {
|
|
||||||
if strings.HasPrefix(key, "data-") || strings.HasPrefix(key, "aria-") {
|
|
||||||
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom attributes from field definition (excluding known schema properties)
|
|
||||||
excludeFields := map[string]bool{
|
|
||||||
"type": true, "title": true, "ui": true, "placeholder": true,
|
|
||||||
"order": true, "isRequired": true, "content": true, "children": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range field.Definition {
|
|
||||||
if !excludeFields[key] && !strings.HasPrefix(key, "ui") {
|
|
||||||
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(attributes, " ")
|
return strings.Join(attributes, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getInputType(fieldDef map[string]any, ui map[string]any) string {
|
func getInputType(schema *jsonschema.Schema) string {
|
||||||
// Check ui type first
|
// Check UI type first
|
||||||
if uiType, ok := ui["type"].(string); ok {
|
if schema.UI != nil {
|
||||||
return uiType
|
if uiType, ok := schema.UI["type"].(string); ok {
|
||||||
|
return uiType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var typeStr string
|
||||||
|
if len(schema.Type) > 0 {
|
||||||
|
typeStr = schema.Type[0]
|
||||||
|
} else {
|
||||||
|
typeStr = "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map schema types to input types
|
// Map schema types to input types
|
||||||
if fieldType, ok := fieldDef["type"].(string); ok {
|
switch typeStr {
|
||||||
switch fieldType {
|
case "string", "text":
|
||||||
case "email":
|
return "text"
|
||||||
return "email"
|
case "number", "integer":
|
||||||
case "password":
|
return "number"
|
||||||
return "password"
|
case "boolean":
|
||||||
case "number", "integer":
|
return "checkbox"
|
||||||
return "number"
|
default:
|
||||||
case "boolean":
|
return "text"
|
||||||
return "checkbox"
|
}
|
||||||
case "date":
|
}
|
||||||
return "date"
|
|
||||||
case "time":
|
func getFieldContent(field FieldInfo) string {
|
||||||
return "time"
|
// Check for content in UI first
|
||||||
case "datetime":
|
if field.Schema.UI != nil {
|
||||||
return "datetime-local"
|
if content, ok := field.Schema.UI["content"].(string); ok {
|
||||||
case "url":
|
return content
|
||||||
return "url"
|
|
||||||
case "tel":
|
|
||||||
return "tel"
|
|
||||||
case "color":
|
|
||||||
return "color"
|
|
||||||
case "range":
|
|
||||||
return "range"
|
|
||||||
case "file":
|
|
||||||
return "file"
|
|
||||||
case "hidden":
|
|
||||||
return "hidden"
|
|
||||||
default:
|
|
||||||
return "text"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "text"
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFieldContent(fieldDef map[string]any, ui map[string]any) string {
|
|
||||||
// Check for content in ui first
|
|
||||||
if content, ok := ui["content"].(string); ok {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for content in field definition
|
|
||||||
if content, ok := fieldDef["content"].(string); ok {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use title as fallback for some elements
|
// Use title as fallback for some elements
|
||||||
if title, ok := fieldDef["title"].(string); ok {
|
if field.Schema.Title != nil {
|
||||||
return title
|
return *field.Schema.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFieldContentHTML(fieldDef map[string]any, ui map[string]any) string {
|
func getFieldContentHTML(field FieldInfo) string {
|
||||||
// Check for HTML content in ui
|
// Check for HTML content in UI
|
||||||
if contentHTML, ok := ui["contentHTML"].(string); ok {
|
if field.Schema.UI != nil {
|
||||||
return contentHTML
|
if contentHTML, ok := field.Schema.UI["contentHTML"].(string); ok {
|
||||||
}
|
return contentHTML
|
||||||
|
}
|
||||||
|
|
||||||
// Check for children elements
|
// Check for children elements
|
||||||
if children, ok := ui["children"].([]any); ok {
|
if children, ok := field.Schema.UI["children"].([]interface{}); ok {
|
||||||
return renderChildren(children)
|
return renderChildren(children)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderChildren(children []any) string {
|
func renderChildren(children []interface{}) string {
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
for _, child := range children {
|
for _, child := range children {
|
||||||
if childMap, ok := child.(map[string]any); ok {
|
if childMap, ok := child.(map[string]interface{}); ok {
|
||||||
// Create a temporary field info for the child
|
// Create a temporary field info for the child
|
||||||
|
childSchema := &jsonschema.Schema{
|
||||||
|
UI: childMap,
|
||||||
|
}
|
||||||
|
if title, ok := childMap["title"].(string); ok {
|
||||||
|
childSchema.Title = &title
|
||||||
|
}
|
||||||
|
|
||||||
childField := FieldInfo{
|
childField := FieldInfo{
|
||||||
Name: getMapValue(childMap, "name", ""),
|
Name: getMapValue(childMap, "name", ""),
|
||||||
Definition: childMap,
|
Schema: childSchema,
|
||||||
}
|
}
|
||||||
result.WriteString(renderField(childField))
|
result.WriteString(renderField(childField))
|
||||||
}
|
}
|
||||||
@@ -555,41 +668,49 @@ func renderChildren(children []any) string {
|
|||||||
return result.String()
|
return result.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateLabel(field FieldInfo, ui map[string]any) string {
|
func generateLabel(field FieldInfo) string {
|
||||||
// Check if label should be generated
|
// Check if label should be generated
|
||||||
if showLabel, ok := ui["showLabel"].(bool); !showLabel && ok {
|
if field.Schema.UI != nil {
|
||||||
return ""
|
if showLabel, ok := field.Schema.UI["showLabel"].(bool); !showLabel && ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title, _ := field.Definition["title"].(string)
|
var title string
|
||||||
|
if field.Schema.Title != nil {
|
||||||
|
title = *field.Schema.Title
|
||||||
|
}
|
||||||
if title == "" {
|
if title == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
name := getUIValue(ui, "name")
|
fieldName := field.FieldPath
|
||||||
if name == "" {
|
if fieldName == "" {
|
||||||
name = field.Name
|
fieldName = field.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if field is required
|
// Check if field is required
|
||||||
isRequired, _ := field.Definition["isRequired"].(bool)
|
|
||||||
requiredSpan := ""
|
requiredSpan := ""
|
||||||
if isRequired {
|
if field.IsRequired {
|
||||||
requiredSpan = ` <span class="required">*</span>`
|
requiredSpan = ` <span class="required">*</span>`
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(`<label for="%s">%s%s</label>`, name, title, requiredSpan)
|
return fmt.Sprintf(`<label for="%s">%s%s</label>`, fieldName, title, requiredSpan)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateOptions(ui map[string]any) string {
|
func generateOptions(ui map[string]interface{}) string {
|
||||||
options, ok := ui["options"].([]any)
|
if ui == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
options, ok := ui["options"].([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var optionsHTML strings.Builder
|
var optionsHTML strings.Builder
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
if optionMap, ok := option.(map[string]any); ok {
|
if optionMap, ok := option.(map[string]interface{}); ok {
|
||||||
// Complex option with attributes
|
// Complex option with attributes
|
||||||
value := getMapValue(optionMap, "value", "")
|
value := getMapValue(optionMap, "value", "")
|
||||||
text := getMapValue(optionMap, "text", value)
|
text := getMapValue(optionMap, "text", value)
|
||||||
@@ -611,23 +732,35 @@ func generateOptions(ui map[string]any) string {
|
|||||||
return optionsHTML.String()
|
return optionsHTML.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUIValue(ui map[string]any, key string) string {
|
func getUIValue(ui map[string]interface{}, key string) string {
|
||||||
|
if ui == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
if value, ok := ui[key].(string); ok {
|
if value, ok := ui[key].(string); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMapValue(m map[string]any, key, defaultValue string) string {
|
func getMapValue(m map[string]interface{}, key, defaultValue string) string {
|
||||||
if value, ok := m[key].(string); ok {
|
if value, ok := m[key].(string); ok {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(slice []string, substr string) bool {
|
func contains(slice []string, item string) bool {
|
||||||
for _, item := range slice {
|
for _, s := range slice {
|
||||||
if strings.Contains(item, substr) {
|
if s == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsAttribute(attributes []string, prefix string) bool {
|
||||||
|
for _, attr := range attributes {
|
||||||
|
if strings.HasPrefix(attr, prefix) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -635,23 +768,27 @@ func contains(slice []string, substr string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderButtons generates HTML for form buttons
|
// renderButtons generates HTML for form buttons
|
||||||
func renderButtons(formConfig map[string]any) string {
|
func (r *JSONSchemaRenderer) renderButtons() string {
|
||||||
|
if r.Schema.Form == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
var buttonsHTML bytes.Buffer
|
var buttonsHTML bytes.Buffer
|
||||||
|
|
||||||
if submitConfig, ok := formConfig["submit"].(map[string]any); ok {
|
if submitConfig, ok := r.Schema.Form["submit"].(map[string]interface{}); ok {
|
||||||
buttonHTML := renderButtonFromConfig(submitConfig, "submit")
|
buttonHTML := renderButtonFromConfig(submitConfig, "submit")
|
||||||
buttonsHTML.WriteString(buttonHTML)
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resetConfig, ok := formConfig["reset"].(map[string]any); ok {
|
if resetConfig, ok := r.Schema.Form["reset"].(map[string]interface{}); ok {
|
||||||
buttonHTML := renderButtonFromConfig(resetConfig, "reset")
|
buttonHTML := renderButtonFromConfig(resetConfig, "reset")
|
||||||
buttonsHTML.WriteString(buttonHTML)
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support for additional custom buttons
|
// Support for additional custom buttons
|
||||||
if buttons, ok := formConfig["buttons"].([]any); ok {
|
if buttons, ok := r.Schema.Form["buttons"].([]interface{}); ok {
|
||||||
for _, button := range buttons {
|
for _, button := range buttons {
|
||||||
if buttonMap, ok := button.(map[string]any); ok {
|
if buttonMap, ok := button.(map[string]interface{}); ok {
|
||||||
buttonType := getMapValue(buttonMap, "type", "button")
|
buttonType := getMapValue(buttonMap, "type", "button")
|
||||||
buttonHTML := renderButtonFromConfig(buttonMap, buttonType)
|
buttonHTML := renderButtonFromConfig(buttonMap, buttonType)
|
||||||
buttonsHTML.WriteString(buttonHTML)
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
@@ -662,7 +799,7 @@ func renderButtons(formConfig map[string]any) string {
|
|||||||
return buttonsHTML.String()
|
return buttonsHTML.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderButtonFromConfig(config map[string]any, defaultType string) string {
|
func renderButtonFromConfig(config map[string]interface{}, defaultType string) string {
|
||||||
var attributes []string
|
var attributes []string
|
||||||
|
|
||||||
buttonType := getMapValue(config, "type", defaultType)
|
buttonType := getMapValue(config, "type", defaultType)
|
||||||
|
Reference in New Issue
Block a user