docs(32-01): form extensions in admin-controllers, cabana and pact READMEs
- admin-controllers: placement, values, Load/Save, permissions, widgets, partials and boot errors, with the ExtrasPlugin snippet - Example_formExtension compiles an owner form with the extension - coming-from-wintercms maps extendFormFields, addFields, addTabFields and removeField to the form extension - cabana README feature bullet and Activate row
This commit is contained in:
147
modules/cabana/example_form_extension_test.go
Normal file
147
modules/cabana/example_form_extension_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package cabana
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing/fstest"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
)
|
||||
|
||||
// ExtraValue is a row of the acme.extras plugin's own table: the subtitle
|
||||
// and the featured flag it keeps for a post of the acme.blog plugin.
|
||||
type ExtraValue struct {
|
||||
PostID uint `gorm:"column:post_id;primaryKey;autoIncrement:false"`
|
||||
Subtitle string `gorm:"column:subtitle"`
|
||||
Featured bool `gorm:"column:featured"`
|
||||
}
|
||||
|
||||
func (ExtraValue) TableName() string { return "acme_extras_values" }
|
||||
|
||||
// extrasFields is the extension's fields document: fields.yaml keys plus
|
||||
// before:/after: anchors, and a tabs: list for the new tab.
|
||||
const extrasFields = `tabs:
|
||||
- name: acme.extras::lang.tab.publishing
|
||||
after: acme.blog::lang.tab.content
|
||||
fields:
|
||||
subtitle:
|
||||
label: acme.extras::lang.subtitle
|
||||
type: text
|
||||
span: left
|
||||
after: title
|
||||
featured:
|
||||
label: acme.extras::lang.featured
|
||||
type: switch
|
||||
tab: acme.extras::lang.tab.publishing
|
||||
`
|
||||
|
||||
// ExtrasPlugin adds a subtitle after the title and a Publishing tab to the
|
||||
// acme.blog posts form, which it does not own, and hides the owner's summary
|
||||
// field. It stores its values in its own table.
|
||||
type ExtrasPlugin struct{}
|
||||
|
||||
func (ExtrasPlugin) ID() string { return "acme.extras" }
|
||||
func (ExtrasPlugin) Requires() []string { return []string{"acme.blog"} }
|
||||
func (ExtrasPlugin) Register(*backpack.App) error { return nil }
|
||||
func (ExtrasPlugin) Boot(*backpack.App) error { return nil }
|
||||
func (ExtrasPlugin) AdminFS() fs.FS {
|
||||
return fstest.MapFS{"extensions/posts.yaml": &fstest.MapFile{Data: []byte(extrasFields)}}
|
||||
}
|
||||
|
||||
var _ pact.HasAdminActionExtensions = ExtrasPlugin{}
|
||||
|
||||
// AdminActionExtensions adds fields and a tab to the acme.blog posts form.
|
||||
func (ExtrasPlugin) AdminActionExtensions() []pact.AdminActionExtension {
|
||||
return []pact.AdminActionExtension{{
|
||||
Controller: "acme.blog.posts",
|
||||
Form: &pact.AdminFormExtension{
|
||||
Fields: "extensions/posts.yaml", // in this plugin's AdminFS
|
||||
Hide: []string{"summary"},
|
||||
Permissions: []string{"acme.extras.edit"},
|
||||
Load: func(ctx context.Context, in pact.AdminFormLoad) (map[string]any, error) {
|
||||
var row ExtraValue
|
||||
err := in.DB.Where("post_id = ?", in.ID).Limit(1).Find(&row).Error
|
||||
return map[string]any{"subtitle": row.Subtitle, "featured": row.Featured}, err
|
||||
},
|
||||
Save: func(ctx context.Context, in pact.AdminFormSave) (pact.AdminFormSaveResult, error) {
|
||||
subtitle, _ := in.Values["subtitle"].(string)
|
||||
if len(subtitle) > 120 {
|
||||
return pact.AdminFormSaveResult{}, pact.AdminFormErrors{"subtitle": {"acme.extras::lang.subtitle_too_long"}}
|
||||
}
|
||||
id, err := strconv.ParseUint(in.ID, 10, 64)
|
||||
if err != nil {
|
||||
return pact.AdminFormSaveResult{}, err
|
||||
}
|
||||
featured, _ := in.Values["featured"].(bool)
|
||||
row := ExtraValue{PostID: uint(id), Subtitle: subtitle, Featured: featured}
|
||||
// in.DB is the record's save transaction: an error rolls back
|
||||
// the owner's row too.
|
||||
return pact.AdminFormSaveResult{}, in.DB.Save(&row).Error
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// exampleBlog is the owner: acme.blog with a posts form of its own.
|
||||
type exampleBlog struct{}
|
||||
|
||||
func (exampleBlog) ID() string { return "acme.blog" }
|
||||
func (exampleBlog) Requires() []string { return nil }
|
||||
func (exampleBlog) Register(*backpack.App) error { return nil }
|
||||
func (exampleBlog) Boot(*backpack.App) error { return nil }
|
||||
func (exampleBlog) AdminControllers() []pact.AdminController {
|
||||
return []pact.AdminController{feCtl{id: "acme.blog.posts"}}
|
||||
}
|
||||
func (exampleBlog) AdminFS() fs.FS {
|
||||
return feOwnerPlugin{id: "acme.blog", fields: `fields:
|
||||
title:
|
||||
label: acme.blog::lang.title
|
||||
type: text
|
||||
span: left
|
||||
summary:
|
||||
label: acme.blog::lang.summary
|
||||
type: text
|
||||
body:
|
||||
label: acme.blog::lang.body
|
||||
type: text
|
||||
tab: acme.blog::lang.tab.content
|
||||
meta:
|
||||
label: acme.blog::lang.meta
|
||||
type: text
|
||||
tab: acme.blog::lang.tab.meta
|
||||
`}.AdminFS()
|
||||
}
|
||||
|
||||
// Example_formExtension compiles the owner's form with the extension, as
|
||||
// the admin does at boot, and prints each tab with its fields.
|
||||
func Example_formExtension() {
|
||||
plugins := []party.Plugin{exampleBlog{}, ExtrasPlugin{}}
|
||||
reg, err := feCompile(plugins...)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
cc, _ := reg.Get("acme.blog.posts")
|
||||
var tabs []string
|
||||
fields := map[string][]string{}
|
||||
for _, field := range cc.Form.Fields {
|
||||
tab := displayTab(field.Tab)
|
||||
if _, seen := fields[tab]; !seen {
|
||||
tabs = append(tabs, tab)
|
||||
}
|
||||
fields[tab] = append(fields[tab], field.Name)
|
||||
}
|
||||
for _, tab := range tabs {
|
||||
fmt.Printf("%s: %s\n", tab, strings.Join(fields[tab], ", "))
|
||||
}
|
||||
// Output:
|
||||
// default: title, subtitle
|
||||
// acme.blog::lang.tab.content: body
|
||||
// acme.extras::lang.tab.publishing: featured
|
||||
// acme.blog::lang.tab.meta: meta
|
||||
}
|
||||
Reference in New Issue
Block a user