mirror of
https://github.com/acaloiaro/frm
synced 2026-07-21 18:29:12 +00:00
feat: form deletion, create drafts from existing
This commit is contained in:
parent
92378b5d1d
commit
2ef152f83e
13 changed files with 382 additions and 425 deletions
|
|
@ -13,6 +13,10 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
workspaceID = uuid.New()
|
||||
)
|
||||
|
||||
func TestCreateAndUpdateForm(t *testing.T) {
|
||||
const nameUpdate = "Tell us about you"
|
||||
fieldID := uuid.MustParse("1afd4bf9-42a4-4dfe-b359-d46a65ce5ba5")
|
||||
|
|
@ -60,7 +64,7 @@ func TestCreateAndUpdateForm(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
frms, err := frm.New(frm.Args{
|
||||
PostgresURL: os.Getenv("POSTGRES_URL"),
|
||||
WorkspaceID: uuid.New(),
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
|
@ -82,7 +86,7 @@ func TestCreateAndUpdateForm(t *testing.T) {
|
|||
}
|
||||
|
||||
f, err = internal.Q(ctx, frms.PostgresURL).SaveDraft(ctx, internal.SaveDraftParams{
|
||||
ID: 1,
|
||||
ID: f.ID,
|
||||
Name: nameUpdate,
|
||||
Fields: updatedFields,
|
||||
WorkspaceID: frms.WorkspaceID,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ CREATE TYPE form_status AS ENUM (
|
|||
-- workspaces represent users, tenants, groupings, etc. in external systems
|
||||
CREATE TABLE IF NOT EXISTS forms (
|
||||
id BIGINT PRIMARY KEY DEFAULT nextval('form_ids'),
|
||||
form_id BIGINT REFERENCES forms(id),
|
||||
form_id BIGINT REFERENCES forms(id) ON DELETE CASCADE,
|
||||
workspace_id UUID NOT NULL,
|
||||
name text not null,
|
||||
fields jsonb default '{}' NOT NULL,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ FROM forms
|
|||
WHERE workspace_id = @workspace_id
|
||||
AND id = @id;
|
||||
|
||||
-- name: DeleteForm :exec
|
||||
|
||||
DELETE
|
||||
FROM forms
|
||||
WHERE workspace_id = @workspace_id
|
||||
AND id = @id;
|
||||
|
||||
-- name: GetDraft :one
|
||||
|
||||
SELECT *
|
||||
|
|
@ -17,7 +24,11 @@ WHERE workspace_id = @workspace_id
|
|||
|
||||
SELECT *
|
||||
FROM forms
|
||||
WHERE workspace_id = @workspace_id;
|
||||
WHERE workspace_id = @workspace_id
|
||||
AND status = any(CASE
|
||||
WHEN cardinality(@statuses::form_status[]) > 0 THEN @statuses::form_status[]
|
||||
ELSE enum_range(NULL::form_status)::form_status[]
|
||||
END::form_status[]);
|
||||
|
||||
-- name: ListDrafts :many
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
|
||||
21
frm.go
21
frm.go
|
|
@ -8,6 +8,10 @@ import (
|
|||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
EventDraftCreated = "frmDraftCreated" // htmx event sent when new drafts are created
|
||||
)
|
||||
|
||||
var ErrCannotDetermineWorkspace = errors.New("workspace cannot be determine without WorkspaceID or WorkspaceIDUrlParam")
|
||||
|
||||
// Frm is the primary API into frm
|
||||
|
|
@ -24,6 +28,8 @@ type Args struct {
|
|||
WorkspaceIDUrlParam string
|
||||
}
|
||||
|
||||
type FormStatus internal.FormStatus
|
||||
|
||||
// New initializes a new frm instance
|
||||
//
|
||||
// If the frm database hasn't been initiailized, the database is initialized
|
||||
|
|
@ -61,10 +67,21 @@ func (f *Frm) GetForm(ctx context.Context, id int64) (form Form, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
type ListFormsArgs struct {
|
||||
Statuses []FormStatus
|
||||
}
|
||||
|
||||
// ListForms lists all forms for the current workspace
|
||||
func (f *Frm) ListForms(ctx context.Context) (forms []Form, err error) {
|
||||
func (f *Frm) ListForms(ctx context.Context, args ListFormsArgs) (forms []Form, err error) {
|
||||
var fs Forms
|
||||
fs, err = internal.Q(ctx, f.PostgresURL).ListForms(ctx, f.WorkspaceID)
|
||||
statuses := []internal.FormStatus{}
|
||||
for _, s := range args.Statuses {
|
||||
statuses = append(statuses, (internal.FormStatus)(s))
|
||||
}
|
||||
fs, err = internal.Q(ctx, f.PostgresURL).ListForms(ctx, internal.ListFormsParams{
|
||||
WorkspaceID: f.WorkspaceID,
|
||||
Statuses: statuses,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/acaloiaro/frm/types"
|
||||
"github.com/acaloiaro/frm/ui"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
|
@ -34,8 +35,8 @@ func StaticAssetHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.FileServer(http.FS(static.Assets)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// FormEditor displays the form editor and previewer
|
||||
func FormEditor(w http.ResponseWriter, r *http.Request) {
|
||||
// DraftEditor displays the form editor and previewer
|
||||
func DraftEditor(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
f, err := instance(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -554,8 +555,50 @@ func DeleteField(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// NewDraft creates a new draft an existing form
|
||||
// NewDraft creates a new draft from scratch or an existing form
|
||||
func NewDraft(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
formID, _ := formID(ctx)
|
||||
f, err := instance(ctx)
|
||||
if err != nil {
|
||||
slog.Error("unable to create draft", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
q := internal.Q(ctx, f.PostgresURL)
|
||||
|
||||
draftParams := &internal.SaveDraftParams{
|
||||
WorkspaceID: f.WorkspaceID,
|
||||
Fields: types.FormFields{},
|
||||
}
|
||||
if formID != nil {
|
||||
form, err := q.GetForm(ctx, internal.GetFormParams{
|
||||
WorkspaceID: f.WorkspaceID,
|
||||
ID: *formID,
|
||||
})
|
||||
if err != nil && errors.Is(err, pgx.ErrNoRows) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
} else if err != nil {
|
||||
slog.Error("unable to create draft", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
draftParams.FormID = &form.ID
|
||||
draftParams.Name = form.Name
|
||||
draftParams.Fields = form.Fields
|
||||
} else {
|
||||
draftParams.Name = "New form"
|
||||
}
|
||||
|
||||
draft, err := q.SaveDraft(ctx, *draftParams)
|
||||
if err != nil {
|
||||
slog.Error("unable to create draft", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("HX-Trigger", fmt.Sprintf("{\"%s\": {\"draft_id\": \"%d\"}}", frm.EventDraftCreated, draft.ID))
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
// PublishDraft copies drafts to published forms
|
||||
|
|
@ -567,7 +610,7 @@ func PublishDraft(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
formID, err := formID(ctx)
|
||||
draftID, err := formID(ctx)
|
||||
if err != nil {
|
||||
slog.Error("unable to publish draft", "error", err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
|
@ -576,7 +619,7 @@ func PublishDraft(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
draft, err := internal.Q(ctx, f.PostgresURL).GetDraft(ctx, internal.GetDraftParams{
|
||||
WorkspaceID: f.WorkspaceID,
|
||||
ID: *formID,
|
||||
ID: *draftID,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("unable to publish draft", "error", err)
|
||||
|
|
@ -594,6 +637,35 @@ func PublishDraft(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteForm deletes forms
|
||||
func DeleteForm(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
f, err := instance(ctx)
|
||||
if err != nil {
|
||||
slog.Error("unable to delete form", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
formID, err := formID(ctx)
|
||||
if err != nil {
|
||||
slog.Error("unable to delete form", "error", err)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
err = internal.Q(ctx, f.PostgresURL).DeleteForm(ctx, internal.DeleteFormParams{
|
||||
WorkspaceID: f.WorkspaceID,
|
||||
ID: *formID,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("unable to delete form", "error", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// instance returns the frm instance from the request context
|
||||
func instance(ctx context.Context) (i *frm.Frm, err error) {
|
||||
var ok bool
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ var pool *pgxpool.Pool
|
|||
|
||||
type Forms []Form
|
||||
|
||||
var enumTypes = []string{
|
||||
"form_status",
|
||||
"_form_status", // array of form statuses
|
||||
}
|
||||
|
||||
// getPool returns a database pool for the specified connection string
|
||||
func getPool(ctx context.Context, databaseURL string) (p *pgxpool.Pool, err error) {
|
||||
if pool == nil {
|
||||
|
|
@ -38,6 +43,19 @@ func getPool(ctx context.Context, databaseURL string) (p *pgxpool.Pool, err erro
|
|||
return
|
||||
}
|
||||
|
||||
// this after conect hook allows pgx to correclty encode enum types as query params
|
||||
// reference: https://github.com/jackc/pgx/issues/1549#issuecomment-1467107173
|
||||
poolConfig.AfterConnect = func(ctx context.Context, conn *pgx.Conn) error {
|
||||
for _, typ := range enumTypes {
|
||||
t, err := conn.LoadType(ctx, typ)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.TypeMap().RegisterType(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = InitializeDB(ctx, databaseURL)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("database failed to initialize: %v", err)
|
||||
|
|
@ -92,7 +110,7 @@ func InitializeDB(ctx context.Context, postgresURL string) (err error) {
|
|||
// TODO not all db crdentials have permission to do this. Fail gracefully when user lacks permission
|
||||
func createIfNotExist(ctx context.Context, cfg *pgx.ConnConfig) (err error) {
|
||||
dbName := cfg.Database
|
||||
cfg.Database = ""
|
||||
cfg.Database = "postgres"
|
||||
cfg.RuntimeParams = nil
|
||||
|
||||
conn, err := pgx.ConnectConfig(context.Background(), cfg)
|
||||
|
|
@ -147,7 +165,6 @@ func pgConnectionString(postgresURL string, disableSSL bool, options ...string)
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
options = append(options, "x-migrations-table=frm_migrations")
|
||||
|
||||
if disableSSL {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,30 @@ import (
|
|||
uuid "github.com/google/uuid"
|
||||
)
|
||||
|
||||
const deleteForm = `-- name: DeleteForm :exec
|
||||
|
||||
DELETE
|
||||
FROM forms
|
||||
WHERE workspace_id = $1
|
||||
AND id = $2
|
||||
`
|
||||
|
||||
type DeleteFormParams struct {
|
||||
WorkspaceID uuid.UUID `json:"workspace_id"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
// DeleteForm
|
||||
//
|
||||
// DELETE
|
||||
// FROM forms
|
||||
// WHERE workspace_id = $1
|
||||
// AND id = $2
|
||||
func (q *Queries) DeleteForm(ctx context.Context, arg DeleteFormParams) error {
|
||||
_, err := q.db.Exec(ctx, deleteForm, arg.WorkspaceID, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getDraft = `-- name: GetDraft :one
|
||||
|
||||
SELECT id, form_id, workspace_id, name, fields, status, created_at, updated_at
|
||||
|
|
@ -139,15 +163,28 @@ const listForms = `-- name: ListForms :many
|
|||
SELECT id, form_id, workspace_id, name, fields, status, created_at, updated_at
|
||||
FROM forms
|
||||
WHERE workspace_id = $1
|
||||
AND status = any(CASE
|
||||
WHEN cardinality($2::form_status[]) > 0 THEN $2::form_status[]
|
||||
ELSE enum_range(NULL::form_status)::form_status[]
|
||||
END::form_status[])
|
||||
`
|
||||
|
||||
type ListFormsParams struct {
|
||||
WorkspaceID uuid.UUID `json:"workspace_id"`
|
||||
Statuses []FormStatus `json:"statuses"`
|
||||
}
|
||||
|
||||
// ListForms
|
||||
//
|
||||
// SELECT id, form_id, workspace_id, name, fields, status, created_at, updated_at
|
||||
// FROM forms
|
||||
// WHERE workspace_id = $1
|
||||
func (q *Queries) ListForms(ctx context.Context, workspaceID uuid.UUID) ([]Form, error) {
|
||||
rows, err := q.db.Query(ctx, listForms, workspaceID)
|
||||
// AND status = any(CASE
|
||||
// WHEN cardinality($2::form_status[]) > 0 THEN $2::form_status[]
|
||||
// ELSE enum_range(NULL::form_status)::form_status[]
|
||||
// END::form_status[])
|
||||
func (q *Queries) ListForms(ctx context.Context, arg ListFormsParams) ([]Form, error) {
|
||||
rows, err := q.db.Query(ctx, listForms, arg.WorkspaceID, arg.Statuses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,10 @@ func Mount(router chi.Router, mountPoint string, f *frm.Frm) {
|
|||
r.NotFound(handlers.StaticAssetHandler)
|
||||
|
||||
rc := r.With(addRequestContext)
|
||||
rc.Get(fmt.Sprintf("/{%s}", urlParamFormID), handlers.FormEditor)
|
||||
rc.Get(fmt.Sprintf("/{%s}/draft", urlParamFormID), handlers.NewDraft)
|
||||
rc.Post("/draft", handlers.NewDraft)
|
||||
rc.Get(fmt.Sprintf("/{%s}", urlParamFormID), handlers.DraftEditor)
|
||||
rc.Delete(fmt.Sprintf("/{%s}", urlParamFormID), handlers.DeleteForm)
|
||||
rc.Post(fmt.Sprintf("/{%s}/draft", urlParamFormID), handlers.NewDraft)
|
||||
rc.Put(fmt.Sprintf("/{%s}/publish", urlParamFormID), handlers.PublishDraft)
|
||||
rc.Put(fmt.Sprintf("/{%s}/fields/order", urlParamFormID), handlers.UpdateFieldOrder)
|
||||
rc.Get(fmt.Sprintf("/{%s}/logic_configurator/{%s}/step3", urlParamFormID, urlParamFieldID), handlers.LogicConfiguratorStep3)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,179 +0,0 @@
|
|||
<svg data-src=\"
|
||||
\" class=\"h-5 w-5\"></svg>
|
||||
<button
|
||||
class=\"
|
||||
\">
|
||||
</button>
|
||||
<div
|
||||
class=\"
|
||||
\">
|
||||
</div>
|
||||
<div id=\"form-builder-nav-title\" data-hx-swap-oob=\"true\" class=\"flex-grow flex justify-center\"><h3 tabindex=\"0\" class=\"hover:bg-gray-100 dark:hover:bg-gray-800 rounded px-2 cursor-pointer relative font-medium py-1 text-md w-1/3 text-gray-500 truncate form-editor-title\" style=\"height: auto;\" id=\"form-editor-title\">
|
||||
</h3></div>
|
||||
<div id=\"form-builder-nav\" data-hx-swap-oob=\"true\" class=\"w-full border-b p-2 flex gap-x-2 items-center bg-white\"><div id=\"form-editor-navbar-tabs\"><div role=\"tablist\" aria-orientation=\"horizontal\" class=\"tabs tabs-boxed bg-gray-50 dark:bg-gray-800 rounded-lg p-1 h-auto grid grid-cols-2 items-center gap-x-1.5 px-2\">
|
||||
</div></div>
|
||||
<div class=\"flex items-stretch gap-x-2\"><div class=\"inline-flex items-center relative\"><a href=\"#\" class=\"text-sm p-2 hover:bg-gray-100 cursor-pointer rounded-lg text-gray-500 hover:text-gray-800 cursor-pointer\"><span class=\"iconify i-heroicons:question-mark-circle w-5 h-5\" aria-hidden=\"true\"></span></a><!----></div><div class=\"relative inline-flex\"><button type=\"button\" class=\"focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:cursor-not-allowed aria-disabled:opacity-75 flex-shrink-0 font-medium rounded-md text-sm gap-x-1.5 shadow-sm text-white dark:text-gray-900 bg-primary-500 hover:bg-primary-600 disabled:bg-primary-500 aria-disabled:bg-primary-500 dark:bg-primary-400 dark:hover:bg-primary-500 dark:disabled:bg-primary-400 dark:aria-disabled:bg-primary-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 dark:focus-visible:outline-primary-400 inline-flex items-center px-8 md:px-4 py-2\" data-hx-put=\"
|
||||
\" data-hx-trigger=\"click\" data-hx-swap=\"none\"><!----><svg class=\"w-4 h-4 text-white inline mr-1 -mt-1\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M17 21V13H7V21M7 3V8H15M19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H16L21 8V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21Z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"></path></svg> Save Form <!----></button><!----></div></div></div>
|
||||
<head><title>
|
||||
::
|
||||
</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
||||
<link href=\"
|
||||
\" rel=\"stylesheet\"><script type=\"text/javascript\" src=\"
|
||||
\" nonce=\"
|
||||
\"></script> <script type=\"text/javascript\" src=\"
|
||||
\" nonce=\"
|
||||
\"></script> <script type=\"text/javascript\" src=\"
|
||||
\" nonce=\"
|
||||
\"></script> <script ytpe=\"text/javascript\" src=\"
|
||||
\" nonce=\"
|
||||
\"></script> <script type=\"text/javascript\" src=\"https://unpkg.com/external-svg-loader@latest/svg-loader.min.js\" nonce=\"
|
||||
\" async></script> <script type=\"text/javascript\" src=\"https://unpkg.com/sortablejs@latest/Sortable.min.js\" nonce=\"
|
||||
\"></script> <script type=\"text/javascript\">\n\t\t\t\thtmx.onLoad(function(content) {\n\t\t\t\t var sortables = content.querySelectorAll(\".sortable\");\n\t\t\t\t for (var i = 0; i < sortables.length; i++) {\n\t\t\t\t var sortable = sortables[i];\n\t\t\t\t var sortableInstance = new Sortable(sortable, {\n\t\t\t\t animation: 150,\n\t\t\t\t\t\t draggable: \".sortme\",\n\t\t\t\t onMove: function (evt) {\n\t\t\t\t return evt.related.className.indexOf('htmx-indicator') === -1;\n\t\t\t\t },\n\t\t\t\t onEnd: function (evt) {\n\t\t\t\t this.option(\"disabled\", true);\n\t\t\t\t }\n\t\t\t\t });\n\t\t\t\t // Re-enable sorting on the `htmx:afterSwap` event\n\t\t\t\t sortable.addEventListener(\"htmx:afterSwap\", function() {\n\t\t\t\t sortableInstance.option(\"disabled\", false);\n\t\t\t\t });\n\t\t\t\t }\n\t\t\t\t})\n\n\t\t\t\tfunction formValueChanged(fieldID, newValue) {\n\t\t\t\t\tvar formMetadata = JSON.parse(document.getElementById('form-metadata').getAttribute(\"data-data\"));\n\t\t\t\t\tif (formMetadata == null) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\t// collect the fields that have logic monitoring the changed field \n\t\t\t\t\tvar watchingFields = Object.values(formMetadata.form.fields).filter(function(field) {\n\t\t\t\t\t\treturn field.logic != null && fieldID === field.logic.target_field_id\n\t\t\t\t\t});\n\n\t\t\t\t\t// no fields watch the one that changed\n\t\t\t\t\tif (watchingFields.length == 0) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tvar fieldElement = document.getElementById(fieldID)\n\t\t\t\t\tfor (i in watchingFields) {\n\t\t\t\t\t\tlet watchingField = watchingFields[i]\n\t\t\t\t\t\tlet match = false\n\t\t\t\t\t\tvar watcherFieldContainerID = `field-container-${watchingField.id}` // the DOM element that contains the watching field\n\t\t\t\t\t\tlogic = watchingField.logic\n\n\t\t\t\t\t\t// check whether the new value is coming from a Choices.js field, in which case the new value\n\t\t\t\t\t\t// is the array of chosen values, joined by commas, otherwise newValue is used as it was passed in\n\t\t\t\t\t\tif (fieldElement != null && fieldElement._choices != null) {\n\t\t\t\t\t\t\t// Choics.getValue() returns scalar for single selects and array for multi. Use Array.of\n\t\t\t\t\t\t\t// to treat everything it returns as an array\n\t\t\t\t\t\t\tnewValue = Array.of(fieldElement._choices.getValue(true)).join(',')\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// find if _any_ trigger values match the new value\n\t\t\t\t\t\tswitch (logic.field_comparator) {\n\t\t\t\t\t\t\tcase 'equal':\n\t\t\t\t\t\t\t\tmatch = watchingField.logic.trigger_values.every(val => newValue.localeCompare(val, 'en', {sensitivity: \"base\"}) == 0)\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase 'contains':\n\t\t\t\t\t\t\t\tmatch = watchingField.logic.trigger_values.some(val => newValue.toLowerCase().includes(val.toLowerCase()))\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Most actions are likely to be performed upon the containing element, such as show/hide/require.\n\t\t\t\t\t\t// This may of course change or be expanded, but for now, only the watcher field container is relevant\n\t\t\t\t\t\t// to applying actions.\n\t\t\t\t\t\tlet el = document.getElementById(watcherFieldContainerID)\n\t\t\t\t\t\tlet actions = watchingField.logic.actions\n\n\t\t\t\t\t\t// we have a match, execute the action\n\t\t\t\t\t\tfor (i in actions) {\n\t\t\t\t\t\t\tswitch (actions[i]) {\n\t\t\t\t\t\t\t\tcase \"field_logic_trigger_show\":\n\t\t\t\t\t\t\t\t\tif (match) {\n\t\t\t\t\t\t\t\t\t\tel.classList.remove(\"hidden\");\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tel.classList.add(\"hidden\");\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</script> <link rel=\"stylesheet\" href=\"
|
||||
\" nonce=\"
|
||||
\">
|
||||
</head>
|
||||
<!doctype html><html lang=\"eng\">
|
||||
<body class=\"\"><div id=\"errors\"></div><main hx-ext=\"response-targets\">
|
||||
</main>
|
||||
</body></html>
|
||||
<section id=\"app-container\">
|
||||
<section id=\"builder-main\" class=\"flex w-full\"><!-- Left column -->
|
||||
<!-- Middle Column -->
|
||||
<!-- Right Column -->
|
||||
</section></section>
|
||||
<div id=\"settings-main\" class=\"hidden\"><form id=\"settings-form\" data-hx-put=\"
|
||||
\" data-hx-trigger=\"
|
||||
\" data-hx-swap-oob=\"true\"><div class=\"pt-3\"><label for=\"form-name-field\" class=\"pr-1\">Form name</label>
|
||||
</div><input id=\"form-name-field\" name=\"name\" type=\"text\" class=\"w-full rounded-md\" value=\"
|
||||
\" placeholder=\"Enter a good name for your form\" autocomplete=\"off\" _=\"
|
||||
\"></form></div>
|
||||
<section id=\"builder-main-left-col\" class=\"flex flex-col gap-3 w-1/4 min-w-max h-full p-4 text-gray-800 rounded-md\">
|
||||
</section>
|
||||
<section id=\"builder-main-middle-col\" class=\"w-full h-screen overflow-y-auto overscroll-auto p-4 bg-gray-50 border-y-1\"><div class=\"mockup-browser border-base-300 border bg-white\"><div class=\"mockup-browser-toolbar\"><div class=\"input border-base-300 border\">https://your-form-domain.com/form</div></div><div class=\"h-full border-base-300 flex border-t px-4 py-8\">
|
||||
</div></div></section>
|
||||
<div class=\"p-1 rounded-md flex items-center justify-center bg-blue-100 text-blue-900 ml-2\">
|
||||
</div>
|
||||
<div id=\"form-fields\" class=\"active-section\">
|
||||
</div>
|
||||
<div id=\"form-fields-form\" data-hx-swap-oob=\"true\" class=\"flex flex-col gap-3 w-full\">
|
||||
<div class=\"w-full border-b pb-2\"></div><form data-hx-put=\"
|
||||
\" data-hx-trigger=\"end\" data-hx-swap=\"outerHTML\" data-hx-target=\"#form-fields-form\" data-hx-indidcator=\"#ind\"><div class=\"flex flex-col gap-1 sortable\">
|
||||
<div class=\"mx-auto w-full border-gray-300 transition-colors bg-gray-50 hover:bg-gray-100 rounded-lg sortme max-w-84 field-row\"><input name=\"order\" type=\"hidden\" value=\"
|
||||
\"><div class=\"group flex items-center gap-x-0.5 py-1.5 pr-1\"><!-- field item --><a href=\"#\" class=\"w-full\" _=\"
|
||||
\"><div class=\"flex flex-col cursor-pointer\"><div tabindex=\"0\" class=\"dark:hover:bg-gray-800 rounded px-2 relative text-gray-700 max-w-72 min-h-6\" style=\"height: auto;\"><p class=\"w-full cursor-pointer truncate\">
|
||||
</p></div></div></a>
|
||||
<div class=\"relative inline-flex\"><button class=\"hidden rounded p-0.5 transition-colors hover:bg-nt-blue-lighter items-center px-1 justify-center md:flex text-red-500\"><div class=\"h-6 text-center text-2xl font-bold text-inherit -mt-0.5\">* </div></button></div>
|
||||
<div class=\"cursor-move\">
|
||||
</div></div></div>
|
||||
</div></form></div>
|
||||
<span class=\"text-red-500 required-dot\">*</span>
|
||||
<section id=\"builder-main-right-col\" class=\"w-1/4 h-full p-4 text-gray-800 rounded-md\"><div id=\"configure-add-field\" class=\"hidden\"><div class=\"h-12 border-b text-lg text-center uppercase\">Add field</div>
|
||||
<div class=\"group flex items-center my-1.5 pr-1\"><a href=\"#\" class=\"w-full\"><div class=\"flex flex-col\"><div tabindex=\"
|
||||
\" class=\"hover:bg-gray-50 dark:hover:bg-gray-800 rounded cursor-pointer relative truncate text-gray-700 min-w-16 min-h-6\" style=\"height: auto;\" data-hx-post=\"
|
||||
\" data-hx-trigger=\"click\" data-hx-vals=\"
|
||||
\" data-hx-swap=\"none\">
|
||||
</div></div></a></div>
|
||||
</div>
|
||||
</section>
|
||||
<form id=\"fields-form\" data-hx-put=\"
|
||||
\" data-hx-trigger=\"
|
||||
\" data-hx-swap=\"none\" data-hx-swap-oob=\"true\">
|
||||
<div id=\"
|
||||
\" class=\"hidden\"><div class=\"mx-auto w-full border-gray-300 transition-colors pb-3\">
|
||||
</div><div id=\"
|
||||
\" class=\"border-b pb-4\"><div role=\"tablist\" aria-orientation=\"horizontal\" class=\"tabs tabs-boxed bg-gray-50 dark:bg-gray-800 rounded-lg h-auto flex gap-2\">
|
||||
</div></div><!-- Form fields settings configurations -->
|
||||
<!-- Form fields logic configurations -->
|
||||
</div>
|
||||
</form>
|
||||
<div id=\"
|
||||
\" class=\"active-configurator-section\"><input name=\"
|
||||
\" type=\"hidden\" value=\"
|
||||
\"> <input name=\"
|
||||
\" type=\"hidden\" value=\"
|
||||
\"> <input name=\"
|
||||
\" type=\"hidden\" value=\"
|
||||
\"><div class=\"pt-3\"><label for=\"
|
||||
\" class=\"pr-1\">Field label</label>
|
||||
</div><input id=\"
|
||||
\" name=\"
|
||||
\" type=\"text\" class=\"w-full rounded-md\" value=\"
|
||||
\" autocomplete=\"off\" _=\"
|
||||
\"><div class=\"pt-3\"><label for=\"
|
||||
\" class=\"pr-1\">Placeholder</label></div><input id=\"
|
||||
\" name=\"
|
||||
\" type=\"text\" class=\"w-full rounded-md\" value=\"
|
||||
\" autocomplete=\"off\" _=\"
|
||||
\">
|
||||
<div class=\"pt-3\"><label for=\"
|
||||
\" class=\"pr-1\">Options</label></div>
|
||||
<div class=\"pt-3\"><label for=\"
|
||||
\" class=\"pr-1\">Required</label></div><input id=\"
|
||||
\" name=\"
|
||||
\" type=\"checkbox\" class=\"checkbox checkbox-primary\"
|
||||
checked
|
||||
_=\"
|
||||
\"><div class=\"pt-3\"><label for=\"
|
||||
\" class=\"pr-1\">Hidden</label></div><input id=\"
|
||||
\" name=\"
|
||||
\" type=\"checkbox\" class=\"checkbox checkbox-primary\"
|
||||
checked
|
||||
_=\"
|
||||
\"><div class=\"py-3 divide-y\"><label for=\"delete-field\" class=\"pr-1\">Danger zone</label></div>
|
||||
</div>
|
||||
<div id=\"
|
||||
\" class=\"flex flex-col gap-5 hidden\"><div><div data-hx-get=\"
|
||||
\" data-hx-trigger=\"
|
||||
\" data-hx-swap=\"innerHTML\" data-hx-target=\"
|
||||
\" data-hx-on:htmx:config-request=\"event.detail.parameters['id'] = event.detail.triggeringEvent.detail.value\">
|
||||
</div></div><div>
|
||||
</div><div><div id=\"
|
||||
\">
|
||||
</div></div><div><p class=\"pb-3\">Choose an action</p><input id=\"
|
||||
\" type=\"checkbox\" class=\"checkbox\" name=\"
|
||||
\"
|
||||
checked
|
||||
_=\"
|
||||
\"> <label for=\"
|
||||
\">Show this field</label></div></div>
|
||||
<input id=\"
|
||||
\" name=\"
|
||||
\" type=\"text\" class=\"bg-gray-50\" placeholder=\"Enter a value\"
|
||||
value=\"
|
||||
\"
|
||||
_=\"
|
||||
\">
|
||||
<div class=\"flex gap-3\">
|
||||
<label class=\"w-full cursor-pointer truncate\">
|
||||
Single-line text
|
||||
Multi-line text
|
||||
Single select
|
||||
Multi select
|
||||
<label class=\"w-full cursor-pointer truncate\">I unno</label>
|
||||
</label></div>
|
||||
<label for=\"
|
||||
\">
|
||||
|
||||
</label>
|
||||
<div id=\"form-viewer\" class=\"flex flex-col w-full justify-top\" data-hx-swap-oob=\"true\"><div id=\"form-metadata\" data-data=\"
|
||||
\"></div><h1 class=\"hover:bg-gray-50 dark:hover:bg-gray-800 rounded cursor-pointer relative mb-2 font-black text-5xl\">
|
||||
</h1><form action=\"
|
||||
\" _=\"on field_change(field_id, value) formValueChanged(field_id, value)\">
|
||||
<div id=\"
|
||||
\"
|
||||
class=\"flex flex-col py-3 hidden\"
|
||||
class=\"flex flex-col py-3\"
|
||||
>
|
||||
<input id=\"
|
||||
\" name=\"
|
||||
\" placeholder=\"
|
||||
\" type=\"text\" autocomplete=\"off\" _=\"
|
||||
\">
|
||||
<textarea id=\"
|
||||
\" name=\"
|
||||
\" class=\"flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 shadow-sm focus:outline-none focus:ring-2 focus:border-transparent focus:ring-opacity-100 rounded-lg px-4 py-2 text-base resize-y block\" name=\"98e021e4-68e0-4273-8576-de89b4459e62\" placeholder=\"
|
||||
\" autocomplete=\"off\" style=\"--tw-ring-color: #3B82F6;\" _=\"
|
||||
\"></textarea>
|
||||
</div>
|
||||
<div class=\"py-3\"></div>
|
||||
</form></div>
|
||||
|
Before Width: | Height: | Size: 14 KiB |
|
|
@ -154,7 +154,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<label for=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -167,7 +167,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -193,22 +193,22 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if args.Required {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"text-red-500 required-dot\">*</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 6)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</label> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 7)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<select id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -221,7 +221,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 8)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" name=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -234,23 +234,23 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 9)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if args.Multiple {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 10)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" multiple")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if args.Required {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 11)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" required=\"true\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 12)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" data-placeholder=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -263,12 +263,12 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 13)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if args.Hyperscript != "" {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 14)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" _=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -281,12 +281,12 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 15)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 16)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("></select><div id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -299,7 +299,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 17)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-args=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -312,7 +312,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 18)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" _=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -325,17 +325,17 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 19)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"></div><!-- when OptionsContent\n are present, their components render inside of this element when chosen -->")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(args.OptionsContent) > 0 {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 20)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!-- content displayed when specific options are selected --> <div id=\"selected-content\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for item, component := range args.OptionsContent {
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 21)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -348,7 +348,7 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 22)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" class=\"hidden\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
@ -356,17 +356,17 @@ func Selector(args SelectArgs) templ.Component {
|
|||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 23)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 24)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 25)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<script>\n\t\t/**\n\t\t * Function to wait for predicates.\n\t\t * @param {function() : Promise.<Boolean> | function() : Boolean} predicate\n\t\t * - A function that returns or resolves a bool\n\t\t * @param {Number} [timeout] - Optional maximum waiting time in ms after rejected\n\t\t */\n\t\tfunction waitFor(predicate, timeout) {\n\t\t return new Promise((resolve, reject) => {\n\t\t let running = true;\n\n\t\t const check = async () => {\n\t\t const res = await predicate();\n\t\t if (res) return resolve(res);\n\t\t if (running) setTimeout(check, 100);\n\t\t };\n\n\t\t check();\n\n\t\t if (!timeout) return;\n\t\t setTimeout(() => {\n\t\t running = false;\n\t\t reject();\n\t\t }, timeout);\n\t\t });\n\t\t}\n\n\t\tasync function initializeSelect(dataElementID) {\n\t\t\t// wait 10 seconds for choices.js to load \n\t\t\ttry {\n\t\t\t\tawait waitFor(() => typeof Choices !== 'undefined', 10000);\n\t\t\t} catch {\n\t\t\t\tconsole.log(\"unable to load choices!\")\n\t\t\t}\n\n\t\t\tconst argsElement = document.getElementById(dataElementID);\n\t\t\tconst args = JSON.parse(argsElement.getAttribute('data-args'));\n\t\t\tif (args.Options != null) {\n\t\t\t\targs.Options.map((option) => {\n\t\t\t\t\tif (option.value === \"\") {\n\t\t\t\t\t\tconsole.error(`option id: (${option.id}) label: (${option.label}) should have a value! ensure every option provided to SelectorArgs has a 'Value'`)\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tvar searchEnabled = true\n\t\t\tif (args.SearchDisabled) {\n\t\t\t\tsearchEnabled = false\n\t\t\t}\n\n\t\t\tvar element = document.getElementById(args.ID)\n\t\t\tvar choices = new Choices(element, {\n\t\t\t\tchoices: args.Options != null ? args.Options : [],\n\t\t\t\tduplicateItemsAllowed: false,\n\t\t\t\tplaceholder: true,\n\t\t\t\tplaceholderValue: args.Placeholder,\n\t\t\t\tsearchEnabled: searchEnabled,\n\t\t\t\teditItems: args.EditItems,\n\t\t\t\tremoveItemButton: true,\n\t\t\t\taddChoices: true,\n\t\t\t\taddItems: true,\n\t\t\t\titemSelectText: \"\",\n\t\t\t});\n\t\t\telement._choices = choices;\n\t\t\n\t\t\t/**\n\t\t\t* When the user's selection(s) have changed, notify a DOM element with an event\n\t\t\t*/\n\t\t\tfunction notifySelected(event) {\n\t\t\t\tif (args.SelectionChangeEvent == \"\") {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar element = document.getElementById(args.ID)\n\t\t\t\tif (element == null) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\telement.dispatchEvent(new CustomEvent(args.SelectionChangeEvent, {bubbles: true, detail: { id: args.ID, value: event.detail.value}}))\n\t\t\t}\n\t\t\t\n\t\t\telement.addEventListener('change', notifySelected, false);\n\t\t}\n\t</script>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
<label for=\"
|
||||
\" class=\"
|
||||
\">
|
||||
|
||||
<span class=\"text-red-500 required-dot\">*</span>
|
||||
</label>
|
||||
<select id=\"
|
||||
\" name=\"
|
||||
\"
|
||||
multiple
|
||||
required=\"true\"
|
||||
data-placeholder=\"
|
||||
\"
|
||||
_=\"
|
||||
\"
|
||||
></select><div id=\"
|
||||
\" data-args=\"
|
||||
\" _=\"
|
||||
\"></div><!-- when OptionsContent\n are present, their components render inside of this element when chosen -->
|
||||
<!-- content displayed when specific options are selected --> <div id=\"selected-content\">
|
||||
<div id=\"
|
||||
\" class=\"hidden\">
|
||||
</div>
|
||||
</div>
|
||||
<script>\n\t\t/**\n\t\t * Function to wait for predicates.\n\t\t * @param {function() : Promise.<Boolean> | function() : Boolean} predicate\n\t\t * - A function that returns or resolves a bool\n\t\t * @param {Number} [timeout] - Optional maximum waiting time in ms after rejected\n\t\t */\n\t\tfunction waitFor(predicate, timeout) {\n\t\t return new Promise((resolve, reject) => {\n\t\t let running = true;\n\n\t\t const check = async () => {\n\t\t const res = await predicate();\n\t\t if (res) return resolve(res);\n\t\t if (running) setTimeout(check, 100);\n\t\t };\n\n\t\t check();\n\n\t\t if (!timeout) return;\n\t\t setTimeout(() => {\n\t\t running = false;\n\t\t reject();\n\t\t }, timeout);\n\t\t });\n\t\t}\n\n\t\tasync function initializeSelect(dataElementID) {\n\t\t\t// wait 10 seconds for choices.js to load \n\t\t\ttry {\n\t\t\t\tawait waitFor(() => typeof Choices !== 'undefined', 10000);\n\t\t\t} catch {\n\t\t\t\tconsole.log(\"unable to load choices!\")\n\t\t\t}\n\n\t\t\tconst argsElement = document.getElementById(dataElementID);\n\t\t\tconst args = JSON.parse(argsElement.getAttribute('data-args'));\n\t\t\tif (args.Options != null) {\n\t\t\t\targs.Options.map((option) => {\n\t\t\t\t\tif (option.value === \"\") {\n\t\t\t\t\t\tconsole.error(`option id: (${option.id}) label: (${option.label}) should have a value! ensure every option provided to SelectorArgs has a 'Value'`)\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\tvar searchEnabled = true\n\t\t\tif (args.SearchDisabled) {\n\t\t\t\tsearchEnabled = false\n\t\t\t}\n\n\t\t\tvar element = document.getElementById(args.ID)\n\t\t\tvar choices = new Choices(element, {\n\t\t\t\tchoices: args.Options != null ? args.Options : [],\n\t\t\t\tduplicateItemsAllowed: false,\n\t\t\t\tplaceholder: true,\n\t\t\t\tplaceholderValue: args.Placeholder,\n\t\t\t\tsearchEnabled: searchEnabled,\n\t\t\t\teditItems: args.EditItems,\n\t\t\t\tremoveItemButton: true,\n\t\t\t\taddChoices: true,\n\t\t\t\taddItems: true,\n\t\t\t\titemSelectText: \"\",\n\t\t\t});\n\t\t\telement._choices = choices;\n\t\t\n\t\t\t/**\n\t\t\t* When the user's selection(s) have changed, notify a DOM element with an event\n\t\t\t*/\n\t\t\tfunction notifySelected(event) {\n\t\t\t\tif (args.SelectionChangeEvent == \"\") {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tvar element = document.getElementById(args.ID)\n\t\t\t\tif (element == null) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\telement.dispatchEvent(new CustomEvent(args.SelectionChangeEvent, {bubbles: true, detail: { id: args.ID, value: event.detail.value}}))\n\t\t\t}\n\t\t\t\n\t\t\telement.addEventListener('change', notifySelected, false);\n\t\t}\n\t</script>
|
||||
Loading…
Reference in a new issue