mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-05 16:06:55 +08:00
update
This commit is contained in:
@@ -5,8 +5,150 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A single template for the entire group structure
|
||||
const groupTemplateStr = `
|
||||
<div class="form-group-container">
|
||||
{{if .Title.Text}}
|
||||
{{if .Title.Class}}
|
||||
<div class="{{.Title.Class}}">{{.Title.Text}}</div>
|
||||
{{else}}
|
||||
<h3 class="group-title">{{.Title.Text}}</h3>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="{{.GroupClass}}">{{.FieldsHTML}}</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Templates for field rendering - now supports all HTML DOM elements
|
||||
var fieldTemplates = map[string]string{
|
||||
// Form elements
|
||||
"input": `<div class="{{.Class}}">{{.LabelHTML}}<input {{.AllAttributes}} />{{.ContentHTML}}</div>`,
|
||||
"textarea": `<div class="{{.Class}}">{{.LabelHTML}}<textarea {{.AllAttributes}}>{{.Content}}</textarea>{{.ContentHTML}}</div>`,
|
||||
"select": `<div class="{{.Class}}">{{.LabelHTML}}<select {{.AllAttributes}}>{{.OptionsHTML}}</select>{{.ContentHTML}}</div>`,
|
||||
"button": `<button {{.AllAttributes}}>{{.Content}}</button>`,
|
||||
"option": `<option {{.AllAttributes}}>{{.Content}}</option>`,
|
||||
"optgroup": `<optgroup {{.AllAttributes}}>{{.OptionsHTML}}</optgroup>`,
|
||||
"label": `<label {{.AllAttributes}}>{{.Content}}</label>`,
|
||||
"fieldset": `<fieldset {{.AllAttributes}}>{{.ContentHTML}}</fieldset>`,
|
||||
"legend": `<legend {{.AllAttributes}}>{{.Content}}</legend>`,
|
||||
"datalist": `<datalist {{.AllAttributes}}>{{.OptionsHTML}}</datalist>`,
|
||||
"output": `<output {{.AllAttributes}}>{{.Content}}</output>`,
|
||||
"progress": `<progress {{.AllAttributes}}>{{.Content}}</progress>`,
|
||||
"meter": `<meter {{.AllAttributes}}>{{.Content}}</meter>`,
|
||||
|
||||
// Text content elements
|
||||
"h1": `<h1 {{.AllAttributes}}>{{.Content}}</h1>`,
|
||||
"h2": `<h2 {{.AllAttributes}}>{{.Content}}</h2>`,
|
||||
"h3": `<h3 {{.AllAttributes}}>{{.Content}}</h3>`,
|
||||
"h4": `<h4 {{.AllAttributes}}>{{.Content}}</h4>`,
|
||||
"h5": `<h5 {{.AllAttributes}}>{{.Content}}</h5>`,
|
||||
"h6": `<h6 {{.AllAttributes}}>{{.Content}}</h6>`,
|
||||
"p": `<p {{.AllAttributes}}>{{.Content}}</p>`,
|
||||
"div": `<div {{.AllAttributes}}>{{.ContentHTML}}</div>`,
|
||||
"span": `<span {{.AllAttributes}}>{{.Content}}</span>`,
|
||||
"pre": `<pre {{.AllAttributes}}>{{.Content}}</pre>`,
|
||||
"code": `<code {{.AllAttributes}}>{{.Content}}</code>`,
|
||||
"blockquote": `<blockquote {{.AllAttributes}}>{{.ContentHTML}}</blockquote>`,
|
||||
"cite": `<cite {{.AllAttributes}}>{{.Content}}</cite>`,
|
||||
"strong": `<strong {{.AllAttributes}}>{{.Content}}</strong>`,
|
||||
"em": `<em {{.AllAttributes}}>{{.Content}}</em>`,
|
||||
"small": `<small {{.AllAttributes}}>{{.Content}}</small>`,
|
||||
"mark": `<mark {{.AllAttributes}}>{{.Content}}</mark>`,
|
||||
"del": `<del {{.AllAttributes}}>{{.Content}}</del>`,
|
||||
"ins": `<ins {{.AllAttributes}}>{{.Content}}</ins>`,
|
||||
"sub": `<sub {{.AllAttributes}}>{{.Content}}</sub>`,
|
||||
"sup": `<sup {{.AllAttributes}}>{{.Content}}</sup>`,
|
||||
"abbr": `<abbr {{.AllAttributes}}>{{.Content}}</abbr>`,
|
||||
"address": `<address {{.AllAttributes}}>{{.ContentHTML}}</address>`,
|
||||
"time": `<time {{.AllAttributes}}>{{.Content}}</time>`,
|
||||
|
||||
// List elements
|
||||
"ul": `<ul {{.AllAttributes}}>{{.ContentHTML}}</ul>`,
|
||||
"ol": `<ol {{.AllAttributes}}>{{.ContentHTML}}</ol>`,
|
||||
"li": `<li {{.AllAttributes}}>{{.Content}}</li>`,
|
||||
"dl": `<dl {{.AllAttributes}}>{{.ContentHTML}}</dl>`,
|
||||
"dt": `<dt {{.AllAttributes}}>{{.Content}}</dt>`,
|
||||
"dd": `<dd {{.AllAttributes}}>{{.Content}}</dd>`,
|
||||
|
||||
// Links and media
|
||||
"a": `<a {{.AllAttributes}}>{{.Content}}</a>`,
|
||||
"img": `<img {{.AllAttributes}} />`,
|
||||
"figure": `<figure {{.AllAttributes}}>{{.ContentHTML}}</figure>`,
|
||||
"figcaption": `<figcaption {{.AllAttributes}}>{{.Content}}</figcaption>`,
|
||||
"audio": `<audio {{.AllAttributes}}>{{.ContentHTML}}</audio>`,
|
||||
"video": `<video {{.AllAttributes}}>{{.ContentHTML}}</video>`,
|
||||
"source": `<source {{.AllAttributes}} />`,
|
||||
"track": `<track {{.AllAttributes}} />`,
|
||||
|
||||
// Table elements
|
||||
"table": `<table {{.AllAttributes}}>{{.ContentHTML}}</table>`,
|
||||
"caption": `<caption {{.AllAttributes}}>{{.Content}}</caption>`,
|
||||
"thead": `<thead {{.AllAttributes}}>{{.ContentHTML}}</thead>`,
|
||||
"tbody": `<tbody {{.AllAttributes}}>{{.ContentHTML}}</tbody>`,
|
||||
"tfoot": `<tfoot {{.AllAttributes}}>{{.ContentHTML}}</tfoot>`,
|
||||
"tr": `<tr {{.AllAttributes}}>{{.ContentHTML}}</tr>`,
|
||||
"th": `<th {{.AllAttributes}}>{{.Content}}</th>`,
|
||||
"td": `<td {{.AllAttributes}}>{{.Content}}</td>`,
|
||||
"colgroup": `<colgroup {{.AllAttributes}}>{{.ContentHTML}}</colgroup>`,
|
||||
"col": `<col {{.AllAttributes}} />`,
|
||||
|
||||
// Sectioning elements
|
||||
"article": `<article {{.AllAttributes}}>{{.ContentHTML}}</article>`,
|
||||
"section": `<section {{.AllAttributes}}>{{.ContentHTML}}</section>`,
|
||||
"nav": `<nav {{.AllAttributes}}>{{.ContentHTML}}</nav>`,
|
||||
"aside": `<aside {{.AllAttributes}}>{{.ContentHTML}}</aside>`,
|
||||
"header": `<header {{.AllAttributes}}>{{.ContentHTML}}</header>`,
|
||||
"footer": `<footer {{.AllAttributes}}>{{.ContentHTML}}</footer>`,
|
||||
"main": `<main {{.AllAttributes}}>{{.ContentHTML}}</main>`,
|
||||
|
||||
// Interactive elements
|
||||
"details": `<details {{.AllAttributes}}>{{.ContentHTML}}</details>`,
|
||||
"summary": `<summary {{.AllAttributes}}>{{.Content}}</summary>`,
|
||||
"dialog": `<dialog {{.AllAttributes}}>{{.ContentHTML}}</dialog>`,
|
||||
|
||||
// Embedded content
|
||||
"iframe": `<iframe {{.AllAttributes}}>{{.Content}}</iframe>`,
|
||||
"embed": `<embed {{.AllAttributes}} />`,
|
||||
"object": `<object {{.AllAttributes}}>{{.ContentHTML}}</object>`,
|
||||
"param": `<param {{.AllAttributes}} />`,
|
||||
"picture": `<picture {{.AllAttributes}}>{{.ContentHTML}}</picture>`,
|
||||
"canvas": `<canvas {{.AllAttributes}}>{{.Content}}</canvas>`,
|
||||
"svg": `<svg {{.AllAttributes}}>{{.ContentHTML}}</svg>`,
|
||||
|
||||
// Meta elements
|
||||
"br": `<br {{.AllAttributes}} />`,
|
||||
"hr": `<hr {{.AllAttributes}} />`,
|
||||
"wbr": `<wbr {{.AllAttributes}} />`,
|
||||
|
||||
// Generic template for any unlisted element
|
||||
"generic": `<{{.Element}} {{.AllAttributes}}>{{.ContentHTML}}</{{.Element}}>`,
|
||||
"void": `<{{.Element}} {{.AllAttributes}} />`,
|
||||
}
|
||||
|
||||
// Void elements that don't have closing tags
|
||||
var voidElements = map[string]bool{
|
||||
"area": true, "base": true, "br": true, "col": true, "embed": true,
|
||||
"hr": true, "img": true, "input": true, "link": true, "meta": true,
|
||||
"param": true, "source": true, "track": true, "wbr": true,
|
||||
}
|
||||
|
||||
var standardAttrs = []string{
|
||||
"id", "class", "name", "type", "value", "placeholder", "href", "src",
|
||||
"alt", "title", "target", "rel", "role", "tabindex", "accesskey",
|
||||
"contenteditable", "draggable", "hidden", "spellcheck", "translate",
|
||||
"autocomplete", "autofocus", "disabled", "readonly", "required",
|
||||
"multiple", "checked", "selected", "defer", "async", "loop", "muted",
|
||||
"controls", "autoplay", "preload", "poster", "width", "height",
|
||||
"rows", "cols", "size", "maxlength", "minlength", "min", "max",
|
||||
"step", "pattern", "accept", "capture", "form", "formaction",
|
||||
"formenctype", "formmethod", "formnovalidate", "formtarget",
|
||||
"colspan", "rowspan", "headers", "scope", "start", "reversed",
|
||||
"datetime", "open", "label", "high", "low", "optimum", "span",
|
||||
}
|
||||
|
||||
// FieldInfo represents metadata for a field extracted from JSONSchema
|
||||
type FieldInfo struct {
|
||||
Name string
|
||||
@@ -198,21 +340,6 @@ func parseGroupsFromSchema(schema map[string]any) []GroupInfo {
|
||||
// renderGroup generates HTML for a single group
|
||||
func renderGroup(group GroupInfo) string {
|
||||
var groupHTML bytes.Buffer
|
||||
|
||||
// A single template for the entire group structure
|
||||
const groupTemplateStr = `
|
||||
<div class="form-group-container">
|
||||
{{if .Title.Text}}
|
||||
{{if .Title.Class}}
|
||||
<div class="{{.Title.Class}}">{{.Title.Text}}</div>
|
||||
{{else}}
|
||||
<h3 class="group-title">{{.Title.Text}}</h3>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="{{.GroupClass}}">{{.FieldsHTML}}</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
// Render fields
|
||||
var fieldsHTML bytes.Buffer
|
||||
for _, field := range group.Fields {
|
||||
@@ -236,123 +363,327 @@ func renderGroup(group GroupInfo) string {
|
||||
return groupHTML.String()
|
||||
}
|
||||
|
||||
// Templates for field rendering
|
||||
var fieldTemplates = map[string]string{
|
||||
"input": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><input type="{{.InputType}}" id="{{.Name}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{.Required}} {{.AdditionalAttributes}} /></div>`,
|
||||
"textarea": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><textarea id="{{.Name}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{.Required}} {{.AdditionalAttributes}}></textarea></div>`,
|
||||
"select": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><select id="{{.Name}}" name="{{.Name}}" {{.Required}} {{.AdditionalAttributes}}>{{.OptionsHTML}}</select></div>`,
|
||||
"h": `<{{.Control}} class="{{.Class}}" id="{{.Name}}" {{.AdditionalAttributes}}>{{.Title}}</{{.Control}}>`,
|
||||
"p": `<p class="{{.Class}}" id="{{.Name}}" {{.AdditionalAttributes}}>{{.Title}}</p>`,
|
||||
"a": `<a class="{{.Class}}" id="{{.Name}}" href="{{.Href}}" {{.AdditionalAttributes}}>{{.Title}}</a>`,
|
||||
"button": `<button type="{{.Type}}" class="{{.Class}}">{{.Label}}</button>`,
|
||||
}
|
||||
|
||||
func renderField(field FieldInfo) string {
|
||||
ui, ok := field.Definition["ui"].(map[string]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
control, _ := ui["element"].(string)
|
||||
class, _ := ui["class"].(string)
|
||||
name, _ := ui["name"].(string)
|
||||
title, _ := field.Definition["title"].(string)
|
||||
placeholder, _ := field.Definition["placeholder"].(string)
|
||||
|
||||
isRequired, _ := field.Definition["isRequired"].(bool)
|
||||
required := ""
|
||||
titleHTML := title
|
||||
if isRequired {
|
||||
required = "required"
|
||||
titleHTML += ` <span class="required">*</span>`
|
||||
element, _ := ui["element"].(string)
|
||||
if element == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
inputType := "text"
|
||||
if uiType, ok := ui["type"].(string); ok {
|
||||
inputType = uiType
|
||||
} else if fieldType, ok := field.Definition["type"].(string); ok && fieldType == "email" {
|
||||
inputType = "email"
|
||||
}
|
||||
// Build all attributes
|
||||
allAttributes := buildAllAttributes(field, ui)
|
||||
|
||||
var additionalAttributes bytes.Buffer
|
||||
for key, value := range field.Definition {
|
||||
switch key {
|
||||
case "title", "ui", "placeholder", "type", "order", "isRequired":
|
||||
continue
|
||||
default:
|
||||
additionalAttributes.WriteString(fmt.Sprintf(` %s="%v"`, key, value))
|
||||
}
|
||||
}
|
||||
// Get content
|
||||
content := getFieldContent(field.Definition, ui)
|
||||
contentHTML := getFieldContentHTML(field.Definition, ui)
|
||||
|
||||
// Generate label if needed
|
||||
labelHTML := generateLabel(field, ui)
|
||||
|
||||
data := map[string]any{
|
||||
"Class": class,
|
||||
"Name": name,
|
||||
"Title": template.HTML(titleHTML),
|
||||
"Placeholder": placeholder,
|
||||
"Required": required,
|
||||
"AdditionalAttributes": template.HTML(additionalAttributes.String()),
|
||||
"InputType": inputType,
|
||||
"Control": control,
|
||||
"Href": "",
|
||||
"Element": element,
|
||||
"AllAttributes": template.HTMLAttr(allAttributes),
|
||||
"Content": content,
|
||||
"ContentHTML": template.HTML(contentHTML),
|
||||
"LabelHTML": template.HTML(labelHTML),
|
||||
"Class": getUIValue(ui, "class"),
|
||||
"OptionsHTML": template.HTML(generateOptions(ui)),
|
||||
}
|
||||
|
||||
// Handle options for select
|
||||
if control == "select" {
|
||||
options, _ := ui["options"].([]any)
|
||||
var optionsHTML bytes.Buffer
|
||||
for _, option := range options {
|
||||
optionsHTML.WriteString(fmt.Sprintf(`<option value="%v">%v</option>`, option, option))
|
||||
}
|
||||
data["OptionsHTML"] = template.HTML(optionsHTML.String())
|
||||
// Use specific template if available, otherwise use generic
|
||||
var tmplStr string
|
||||
if template, exists := fieldTemplates[element]; exists {
|
||||
tmplStr = template
|
||||
} else if voidElements[element] {
|
||||
tmplStr = fieldTemplates["void"]
|
||||
} else {
|
||||
tmplStr = fieldTemplates["generic"]
|
||||
}
|
||||
|
||||
// Handle href for links
|
||||
if control == "a" {
|
||||
if href, ok := ui["href"].(string); ok {
|
||||
data["Href"] = href
|
||||
tmpl := template.Must(template.New(element).Parse(tmplStr))
|
||||
var buf bytes.Buffer
|
||||
if err := tmpl.Execute(&buf, data); err != nil {
|
||||
return ""
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func buildAllAttributes(field FieldInfo, ui map[string]any) string {
|
||||
var attributes []string
|
||||
|
||||
// Add standard attributes from ui
|
||||
for _, attr := range standardAttrs {
|
||||
if value, exists := ui[attr]; exists {
|
||||
if attr == "class" && value == "" {
|
||||
continue // Skip empty class
|
||||
}
|
||||
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, attr, value))
|
||||
}
|
||||
}
|
||||
|
||||
if tmplStr, ok := fieldTemplates[control]; ok {
|
||||
tmpl := template.Must(template.New(control).Parse(tmplStr))
|
||||
var buf bytes.Buffer
|
||||
tmpl.Execute(&buf, data)
|
||||
return buf.String()
|
||||
// Handle required field
|
||||
if isRequired, ok := field.Definition["isRequired"].(bool); ok && isRequired {
|
||||
if !contains(attributes, "required=") {
|
||||
attributes = append(attributes, `required="required"`)
|
||||
}
|
||||
}
|
||||
// Handle generic 'h' template for h1-h6
|
||||
if control[0] == 'h' && len(control) == 2 && control[1] >= '1' && control[1] <= '6' {
|
||||
data["Control"] = control
|
||||
tmpl := template.Must(template.New("h").Parse(fieldTemplates["h"]))
|
||||
var buf bytes.Buffer
|
||||
tmpl.Execute(&buf, data)
|
||||
return buf.String()
|
||||
|
||||
// Handle input type based on field type
|
||||
element, _ := ui["element"].(string)
|
||||
if element == "input" {
|
||||
if inputType := getInputType(field.Definition, ui); inputType != "" {
|
||||
if !contains(attributes, "type=") {
|
||||
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, " ")
|
||||
}
|
||||
|
||||
func getInputType(fieldDef map[string]any, ui map[string]any) string {
|
||||
// Check ui type first
|
||||
if uiType, ok := ui["type"].(string); ok {
|
||||
return uiType
|
||||
}
|
||||
|
||||
// Map schema types to input types
|
||||
if fieldType, ok := fieldDef["type"].(string); ok {
|
||||
switch fieldType {
|
||||
case "email":
|
||||
return "email"
|
||||
case "password":
|
||||
return "password"
|
||||
case "number", "integer":
|
||||
return "number"
|
||||
case "boolean":
|
||||
return "checkbox"
|
||||
case "date":
|
||||
return "date"
|
||||
case "time":
|
||||
return "time"
|
||||
case "datetime":
|
||||
return "datetime-local"
|
||||
case "url":
|
||||
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
|
||||
if title, ok := fieldDef["title"].(string); ok {
|
||||
return title
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getFieldContentHTML(fieldDef map[string]any, ui map[string]any) string {
|
||||
// Check for HTML content in ui
|
||||
if contentHTML, ok := ui["contentHTML"].(string); ok {
|
||||
return contentHTML
|
||||
}
|
||||
|
||||
// Check for children elements
|
||||
if children, ok := ui["children"].([]any); ok {
|
||||
return renderChildren(children)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func renderChildren(children []any) string {
|
||||
var result strings.Builder
|
||||
for _, child := range children {
|
||||
if childMap, ok := child.(map[string]any); ok {
|
||||
// Create a temporary field info for the child
|
||||
childField := FieldInfo{
|
||||
Name: getMapValue(childMap, "name", ""),
|
||||
Definition: childMap,
|
||||
}
|
||||
result.WriteString(renderField(childField))
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func generateLabel(field FieldInfo, ui map[string]any) string {
|
||||
// Check if label should be generated
|
||||
if showLabel, ok := ui["showLabel"].(bool); !showLabel && ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
title, _ := field.Definition["title"].(string)
|
||||
if title == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
name := getUIValue(ui, "name")
|
||||
if name == "" {
|
||||
name = field.Name
|
||||
}
|
||||
|
||||
// Check if field is required
|
||||
isRequired, _ := field.Definition["isRequired"].(bool)
|
||||
requiredSpan := ""
|
||||
if isRequired {
|
||||
requiredSpan = ` <span class="required">*</span>`
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`<label for="%s">%s%s</label>`, name, title, requiredSpan)
|
||||
}
|
||||
|
||||
func generateOptions(ui map[string]any) string {
|
||||
options, ok := ui["options"].([]any)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var optionsHTML strings.Builder
|
||||
for _, option := range options {
|
||||
if optionMap, ok := option.(map[string]any); ok {
|
||||
// Complex option with attributes
|
||||
value := getMapValue(optionMap, "value", "")
|
||||
text := getMapValue(optionMap, "text", value)
|
||||
selected := ""
|
||||
if isSelected, ok := optionMap["selected"].(bool); ok && isSelected {
|
||||
selected = ` selected="selected"`
|
||||
}
|
||||
disabled := ""
|
||||
if isDisabled, ok := optionMap["disabled"].(bool); ok && isDisabled {
|
||||
disabled = ` disabled="disabled"`
|
||||
}
|
||||
optionsHTML.WriteString(fmt.Sprintf(`<option value="%s"%s%s>%s</option>`,
|
||||
value, selected, disabled, text))
|
||||
} else {
|
||||
// Simple option (just value)
|
||||
optionsHTML.WriteString(fmt.Sprintf(`<option value="%v">%v</option>`, option, option))
|
||||
}
|
||||
}
|
||||
return optionsHTML.String()
|
||||
}
|
||||
|
||||
func getUIValue(ui map[string]any, key string) string {
|
||||
if value, ok := ui[key].(string); ok {
|
||||
return value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getMapValue(m map[string]any, key, defaultValue string) string {
|
||||
if value, ok := m[key].(string); ok {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func contains(slice []string, substr string) bool {
|
||||
for _, item := range slice {
|
||||
if strings.Contains(item, substr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// renderButtons generates HTML for form buttons
|
||||
func renderButtons(formConfig map[string]any) string {
|
||||
var buttonsHTML bytes.Buffer
|
||||
tmpl := template.Must(template.New("button").Parse(fieldTemplates["button"]))
|
||||
|
||||
if submitConfig, ok := formConfig["submit"].(map[string]any); ok {
|
||||
data := map[string]any{
|
||||
"Type": submitConfig["type"],
|
||||
"Class": submitConfig["class"],
|
||||
"Label": submitConfig["label"],
|
||||
}
|
||||
tmpl.Execute(&buttonsHTML, data)
|
||||
buttonHTML := renderButtonFromConfig(submitConfig, "submit")
|
||||
buttonsHTML.WriteString(buttonHTML)
|
||||
}
|
||||
|
||||
if resetConfig, ok := formConfig["reset"].(map[string]any); ok {
|
||||
data := map[string]any{
|
||||
"Type": resetConfig["type"],
|
||||
"Class": resetConfig["class"],
|
||||
"Label": resetConfig["label"],
|
||||
buttonHTML := renderButtonFromConfig(resetConfig, "reset")
|
||||
buttonsHTML.WriteString(buttonHTML)
|
||||
}
|
||||
|
||||
// Support for additional custom buttons
|
||||
if buttons, ok := formConfig["buttons"].([]any); ok {
|
||||
for _, button := range buttons {
|
||||
if buttonMap, ok := button.(map[string]any); ok {
|
||||
buttonType := getMapValue(buttonMap, "type", "button")
|
||||
buttonHTML := renderButtonFromConfig(buttonMap, buttonType)
|
||||
buttonsHTML.WriteString(buttonHTML)
|
||||
}
|
||||
}
|
||||
tmpl.Execute(&buttonsHTML, data)
|
||||
}
|
||||
|
||||
return buttonsHTML.String()
|
||||
}
|
||||
|
||||
func renderButtonFromConfig(config map[string]any, defaultType string) string {
|
||||
var attributes []string
|
||||
|
||||
buttonType := getMapValue(config, "type", defaultType)
|
||||
attributes = append(attributes, fmt.Sprintf(`type="%s"`, buttonType))
|
||||
|
||||
if class := getMapValue(config, "class", ""); class != "" {
|
||||
attributes = append(attributes, fmt.Sprintf(`class="%s"`, class))
|
||||
}
|
||||
|
||||
// Add other button attributes
|
||||
for key, value := range config {
|
||||
switch key {
|
||||
case "type", "class", "label", "content":
|
||||
continue // Already handled
|
||||
default:
|
||||
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
||||
}
|
||||
}
|
||||
|
||||
content := getMapValue(config, "label", getMapValue(config, "content", "Button"))
|
||||
|
||||
return fmt.Sprintf(`<button %s>%s</button>`,
|
||||
strings.Join(attributes, " "), content)
|
||||
}
|
||||
|
Reference in New Issue
Block a user