Skip to content

Commit

Permalink
Merge pull request #574 from qor5/validate
Browse files Browse the repository at this point in the history
richeditor/media_box: add ErrorMessages and Disabled
  • Loading branch information
molon authored Sep 12, 2024
2 parents efecfef + 2a4dc32 commit adab6d7
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 18 deletions.
21 changes: 20 additions & 1 deletion docs/docsrc/examples/examples_presets/editing.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,27 @@ func PresetsEditingCustomizationDescription(b *presets.Builder, db *gorm.DB) (

ce.Only("Name", "Email", "CompanyID", "Description")

ce.ValidateFunc(func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {
customer := obj.(*Customer)
if customer.Name == "" {
err.FieldError("Name", "name must not be empty")
}
if customer.Email == "" {
err.FieldError("Email", "email must not be empty")
}
if customer.Description == "" {
err.FieldError("Description", "description must not be empty")
}
return
})

ce.Field("Description").ComponentFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
return richeditor.RichEditor(db, "Body").Plugins([]string{"alignment", "video", "imageinsert", "fontcolor"}).Value(obj.(*Customer).Description).Label(field.Label)
return richeditor.RichEditor(db, "Body").
Plugins([]string{"alignment", "video", "imageinsert", "fontcolor"}).
Value(obj.(*Customer).Description).
Label(field.Label).
Disabled(field.Disabled).
ErrorMessages(field.Errors...)
})

// If you just want to specify the label to be displayed
Expand Down
55 changes: 55 additions & 0 deletions docs/docsrc/examples/examples_presets/editing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"net/http"
"testing"

"github.com/qor5/admin/v3/media"
"github.com/qor5/admin/v3/media/media_library"
"github.com/qor5/admin/v3/presets"
"github.com/qor5/admin/v3/presets/gorm2op"
"github.com/qor5/web/v3"
"github.com/qor5/web/v3/multipartestutils"
"github.com/theplant/gofixtures"
)
Expand Down Expand Up @@ -93,6 +96,58 @@ func TestPresetsEditingCustomizationDescription(t *testing.T) {
},
ExpectPortalUpdate0ContainsInOrder: []string{`Customer Name`, `Customer Email`, `Description`, `redactor`},
},
{
Name: "do new",
Debug: true,
ReqFunc: func() *http.Request {
return multipartestutils.NewMultipartBuilder().
PageURL("/customers?__execute_event__=presets_Update").
AddField("Name", "").
AddField("Email", "").
AddField("Body", "").
AddField("CompanyID", "0").
AddField("Description", "").
AddField("Body_richeditor_medialibrary.Values", "").
BuildEventFuncRequest()
},
ExpectPortalUpdate0ContainsInOrder: []string{`name must not be empty`, `email must not be empty`, `description must not be empty`},
},
{
Name: "do new without avatar",
Debug: true,
HandlerMaker: func() http.Handler {
pb := presets.New().DataOperator(gorm2op.DataOperator(TestDB))
mediaBuilder := media.New(TestDB).AutoMigrate()
defer pb.Use(mediaBuilder)

mb, _, _, _ := PresetsEditingCustomizationDescription(pb, TestDB)
mb.Editing().Field("Avatar").WithContextValue(media.MediaBoxConfig, &media_library.MediaBoxConfig{})
mb.Editing().WrapValidateFunc(func(in presets.ValidateFunc) presets.ValidateFunc {
return func(obj interface{}, ctx *web.EventContext) (err web.ValidationErrors) {
err = in(obj, ctx)
customer := obj.(*Customer)
if customer.Avatar.FileName == "" || customer.Avatar.Url == "" {
err.FieldError("Avatar", "avatar must not be empty")
}
return
}
})
return pb
},
ReqFunc: func() *http.Request {
return multipartestutils.NewMultipartBuilder().
PageURL("/customers?__execute_event__=presets_Update").
AddField("Name", "").
AddField("Email", "").
AddField("Body", "").
AddField("Avatar.Values", "").
AddField("CompanyID", "0").
AddField("Description", "").
AddField("Body_richeditor_medialibrary.Values", "").
BuildEventFuncRequest()
},
ExpectPortalUpdate0ContainsInOrder: []string{`name must not be empty`, `email must not be empty`, `description must not be empty`, `avatar must not be empty`},
},
}

for _, c := range cases {
Expand Down
32 changes: 24 additions & 8 deletions media/media_box.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ func MediaBoxComponentFunc(db *gorm.DB, readonly bool) presets.FieldComponentFun
Label(field.Label).
Config(cfg).
Disabled(field.Disabled).
Readonly(readonly)
Readonly(readonly).
ErrorMessages(field.Errors...)
}
}

Expand All @@ -114,13 +115,14 @@ func MediaBoxSetterFunc(db *gorm.DB) presets.FieldSetterFunc {
}

type QMediaBoxBuilder struct {
fieldName string
label string
value *media_library.MediaBox
config *media_library.MediaBoxConfig
db *gorm.DB
disabled bool
readonly bool
fieldName string
label string
value *media_library.MediaBox
config *media_library.MediaBoxConfig
db *gorm.DB
disabled bool
readonly bool
errorMessages []string
}

func QMediaBox(db *gorm.DB) (r *QMediaBoxBuilder) {
Expand Down Expand Up @@ -160,6 +162,11 @@ func (b *QMediaBoxBuilder) Config(v *media_library.MediaBoxConfig) (r *QMediaBox
return b
}

func (b *QMediaBoxBuilder) ErrorMessages(v ...string) (r *QMediaBoxBuilder) {
b.errorMessages = v
return b
}

func (b *QMediaBoxBuilder) MarshalHTML(c context.Context) (r []byte, err error) {
if b.fieldName == "" {
panic("FieldName required")
Expand All @@ -181,6 +188,15 @@ func (b *QMediaBoxBuilder) MarshalHTML(c context.Context) (r []byte, err error)
mediaBoxThumbnails(ctx, b.value, b.fieldName, b.config, b.disabled, b.readonly),
).Name(mediaBoxThumbnailsPortalName(b.fieldName)),
web.Portal().Name(portalName),
h.Iff(len(b.errorMessages) > 0, func() h.HTMLComponent {
var compos []h.HTMLComponent
for _, errMsg := range b.errorMessages {
compos = append(compos, h.Div().Attr("v-pre", true).Text(errMsg))
}
return h.Div().Class("d-flex flex-column ps-4 py-1 ga-1 text-caption").
ClassIf("text-error", len(b.errorMessages) > 0 && !b.disabled).
ClassIf("text-grey", b.disabled).Children(compos...)
}),
).Class("pb-4").
Rounded(true),
).MarshalHTML(c)
Expand Down
20 changes: 20 additions & 0 deletions richeditor/redactor/redactor.custom.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
.redactor-toolbar {
z-index: 5;
}

.redactor-styles {
max-height: 50vh;
overflow-y: auto;
}

.redactor-error .redactor-box.redactor-styles-on {
border: 1px solid rgb(var(--v-theme-error));
}

.reactor-disable .redactor-box.redactor-styles-on {
border: 1px solid rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity));
}

.redactor-disable-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.05);
pointer-events: all;
z-index: 6;
}
46 changes: 37 additions & 9 deletions richeditor/richeditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ var (
)

type RichEditorBuilder struct {
db *gorm.DB
name string
value string
label string
placeholder string
plugins []string
setPlugins bool
rawConfig interface{}
db *gorm.DB
name string
value string
label string
placeholder string
plugins []string
setPlugins bool
rawConfig interface{}
errorMessages []string
disabled bool
}

func RichEditor(db *gorm.DB, name string) (r *RichEditorBuilder) {
Expand Down Expand Up @@ -69,6 +71,16 @@ func (b *RichEditorBuilder) RawConfig(v interface{}) (r *RichEditorBuilder) {
return b
}

func (b *RichEditorBuilder) ErrorMessages(errMsgs ...string) (r *RichEditorBuilder) {
b.errorMessages = errMsgs
return b
}

func (b *RichEditorBuilder) Disabled(disabled bool) (r *RichEditorBuilder) {
b.disabled = disabled
return b
}

func (b *RichEditorBuilder) MarshalHTML(ctx context.Context) ([]byte, error) {
p := Plugins
if b.setPlugins {
Expand All @@ -80,16 +92,32 @@ func (b *RichEditorBuilder) MarshalHTML(ctx context.Context) ([]byte, error) {
} else {
redactorB.Config(redactor.Config{Plugins: p})
}

r := h.Components(
v.VSheet(
h.Label(b.label).Class("v-label theme--light"),
redactorB,
h.Div().Style("position:relative").
ClassIf("redactor-error", len(b.errorMessages) > 0 && !b.disabled).
ClassIf("redactor-disable", b.disabled).
Children(
redactorB,
h.If(b.disabled, h.Div().Class("redactor-disable-overlay")),
),
h.Div(
media.QMediaBox(b.db).FieldName(fmt.Sprintf("%s_richeditor_medialibrary", b.name)).
Value(&media_library.MediaBox{}).Config(&media_library.MediaBoxConfig{
AllowType: "image",
}),
).Class("hidden-screen-only"),
h.Iff(len(b.errorMessages) > 0, func() h.HTMLComponent {
var compos []h.HTMLComponent
for _, errMsg := range b.errorMessages {
compos = append(compos, h.Div().Attr("v-pre", true).Text(errMsg))
}
return h.Div().Class("d-flex flex-column ps-4 py-1 ga-1 text-caption").
ClassIf("text-error", len(b.errorMessages) > 0 && !b.disabled).
ClassIf("text-grey", b.disabled).Children(compos...)
}),
).Class("pb-4").Rounded(true).Attr("data-type", "redactor").Attr("style", "position: relative; z-index:1;"),
)
return r.MarshalHTML(ctx)
Expand Down

0 comments on commit adab6d7

Please sign in to comment.