Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F1262654
mountable-with-fields.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
mountable-with-fields.ts
View Options
import
{
ShapeToType
}
from
"@sealcode/ts-predicates"
;
import
{
Context
}
from
"koa"
;
import
{
FlatTemplatable
,
tempstream
}
from
"tempstream"
;
import
{
FormControl
,
FormControlContext
,
}
from
"../forms/controls/form-control.js"
;
import
type
{
FieldsToShape
,
FormField
}
from
"../forms/fields/field.js"
;
import
type
{
FormDataValue
,
FormMessage
,
FormData
,
}
from
"../forms/form-types.js"
;
import
{
Mountable
}
from
"./mountable.js"
;
export
type
PageErrorMessage
=
{
type
:
"access"
|
"internal"
;
message
:
string
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export
type
Fields
=
Record
<
string
,
FormField
<
boolean
,
any
>>
;
type
Resolved
<
T
>
=
T
extends
Promise
<
infer
X
>
?
X
:
never
;
type
ParsedValue
<
T
extends
FormField
>
=
Resolved
<
ReturnType
<
T
[
"getValue"
]
>
>
[
"parsed"
];
export
abstract
class
MountableWithFields
<
F
extends
Fields
=
Fields
>
extends
Mountable
{
fields
:
F
;
field_names_prefix
=
""
;
// useful for multiform, where many forms are merged into one and field assignment is made using the prefix
form_id
=
""
;
// all fields within this mountable will be tied to form of this id
abstract
controls
:
FormControl
[];
constructor
()
{
super
();
if
(
!
this
.
fields
)
this
.
fields
=
{}
as
F
;
}
init
()
:
void
{
for
(
const
[
name
,
field
]
of
Object
.
entries
(
this
.
fields
))
{
void
field
.
init
(
name
);
}
}
makeFormControlContext
(
ctx
:
Context
,
data
:
FormData
,
validate
:
boolean
,
field_name_prefix
=
this
.
field_names_prefix
,
form_id
=
this
.
form_id
)
{
return
new
FormControlContext
(
ctx
,
data
.
raw_values
,
data
.
messages
,
field_name_prefix
,
form_id
,
validate
);
}
// this one is meant to be overwritten
async
validateValues
(
_ctx
:
Context
,
_data
:
Record
<
string
,
FormDataValue
>
)
:
Promise
<
{
valid
:
boolean
;
error
:
string
}
>
{
return
{
valid
:
true
,
error
:
""
,
};
}
async
getInitialValues
(
_ctx
:
Context
)
:
Promise
<
Record
<
string
,
FormDataValue
>>
{
return
{};
}
async
validate
(
ctx
:
Context
,
values
:
Record
<
string
,
FormDataValue
>
)
:
Promise
<
{
valid
:
boolean
;
field_errors
:
Partial
<
Record
<
keyof
Fields
,
string
>>
;
form_messages
:
FormMessage
[];
}
>
{
const
field_errors
=
{}
as
Record
<
keyof
Fields
,
string
>
;
let
valid
=
true
;
const
form_messages
=
[]
as
FormMessage
[];
await
Promise
.
all
(
Object
.
keys
(
this
.
fields
).
map
(
async
(
key
:
keyof
F
)
=>
{
const
field
=
this
.
fields
[
key
];
const
{
valid
:
fieldvalid
,
message
:
fieldmessage
}
=
await
field
.
getValue
(
ctx
,
values
,
true
);
if
(
!
fieldvalid
)
{
valid
=
false
;
field_errors
[
field
.
name
]
=
fieldmessage
;
}
})
);
const
formValidationResult
=
await
this
.
validateValues
(
ctx
,
values
);
if
(
!
formValidationResult
.
valid
)
{
form_messages
.
push
({
type
:
"error"
,
text
:
formValidationResult
.
error
,
});
valid
=
false
;
}
return
{
valid
,
field_errors
,
form_messages
};
}
public
renderControls
(
fctx
:
FormControlContext
)
:
FlatTemplatable
{
return
tempstream
/* HTML */
`
${
this
.
controls
.
map
((
control
)
=>
control
.
render
(
fctx
)
)
}
`
;
}
async
renderError
(
_
:
Context
,
error
:
PageErrorMessage
)
:
Promise
<
FlatTemplatable
>
{
return
error
.
message
;
}
public
renderMessages
(
_
:
Context
,
data
:
FormData
<
keyof
Fields
>
)
:
FlatTemplatable
{
return
tempstream
/* HTML */
`<div class="form-messages">
${
data
.
messages
.
map
(
(
message
)
=>
`<div class="form-message form-message--
${
message
.
type
}
">
${
message
.
text
}
</div>`
)
}
</div>`
;
}
abstract
extractRawValues
(
ctx
:
Context
)
:
Promise
<
Record
<
string
,
FormDataValue
>>
;
async
getParsedValues
(
ctx
:
Context
)
:
Promise
<
{
[
field
in
keyof
F
]
:
F
[
field
]
extends
FormField
<
true
>
?
Exclude
<
ParsedValue
<
F
[
field
]
>
,
null
>
:
ParsedValue
<
F
[
field
]
>
;
}
>
{
const
raw_values
=
await
this
.
extractRawValues
(
ctx
);
const
result
:
Record
<
string
,
unknown
>
=
{};
const
promises
=
Object
.
entries
(
this
.
fields
).
map
(
async
([
key
,
field
])
=>
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const
{
parsed
}
=
await
field
.
getValue
(
ctx
,
raw_values
,
false
);
result
[
key
]
=
parsed
;
}
);
await
Promise
.
all
(
promises
);
// TODO: remove this any. I don't have the strenght to deal with it now.
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return
return
result
as
any
;
}
async
getDatabaseValues
(
ctx
:
Context
)
:
Promise
<
ShapeToType
<
FieldsToShape
<
F
>>>
{
const
raw_values
=
await
this
.
extractRawValues
(
ctx
);
const
result
:
Record
<
string
,
unknown
>
=
{};
const
promises
=
Object
.
entries
(
this
.
fields
).
map
(
async
([
key
,
field
])
=>
{
const
db_value
=
await
field
.
getDatabaseValue
(
ctx
,
raw_values
);
if
(
db_value
!==
undefined
)
{
result
[
key
]
=
db_value
;
}
}
);
await
Promise
.
all
(
promises
);
return
result
as
ShapeToType
<
FieldsToShape
<
F
>>
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Fri, Jan 24, 15:15 (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
600355
Default Alt Text
mountable-with-fields.ts (4 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment