Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F9583917
form.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Referenced Files
None
Subscribers
None
form.ts
View Options
import
{
Context
}
from
"koa"
;
import
Router
from
"@koa/router"
;
import
{
FlatTemplatable
,
tempstream
}
from
"tempstream"
;
import
{
hasFieldOfType
,
hasShape
,
is
,
predicates
,
}
from
"@sealcode/ts-predicates"
;
import
{
FieldsToShape
,
FormField
}
from
"./fields/field"
;
import
{
Fields
,
MountableWithFields
,
PageErrorMessage
,
}
from
"../page/mountable-with-fields"
;
export
type
FormDataPrimitive
=
string
|
string
[]
|
number
|
undefined
;
export
type
FormDataValue
=
|
FormDataPrimitive
|
Record
<
string
,
FormDataPrimitive
>
;
export
type
FieldValueType
<
F
extends
FormField
>
=
F
extends
FormField
<
infer
R
>
?
R
:
never
;
export
type
FormFieldsToValues
<
F
extends
Record
<
string
,
FormField
<
boolean
,
keyof
F
>>
>
=
{
[
Property
in
keyof
F
]
:
FieldValueType
<
F
[
Property
]
>
;
};
export
type
FormMessage
=
{
type
:
"info"
|
"success"
|
"error"
;
text
:
string
};
export
type
FormData
<
Fieldnames
extends
string
=
string
>
=
{
raw_values
:
Record
<
Fieldnames
,
FormDataValue
>
;
messages
:
FormMessage
[];
};
export
type
FormReaction
=
|
{
action
:
"stay"
;
content
:
FlatTemplatable
;
messages
?:
FormMessage
[]
}
|
{
action
:
"redirect"
;
url
:
string
;
messages
?:
FormMessage
[]
};
export
abstract
class
Form
<
F
extends
Fields
,
SubmitResult
extends
any
>
extends
MountableWithFields
<
F
>
{
defaultSuccessMessage
=
"Done"
;
submitButtonText
=
"Wyślij"
;
action
=
"./"
;
useTurbo
=
true
;
async
canAccess
(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_
:
Context
)
:
Promise
<
{
canAccess
:
boolean
;
message
:
string
}
>
{
return
{
canAccess
:
true
,
message
:
""
};
}
async
renderError
(
_
:
Context
,
error
:
PageErrorMessage
)
:
Promise
<
FlatTemplatable
>
{
return
tempstream
/* HTML */
`<div>
${
error
.
message
}
</div>`
;
}
makeSubmitButton
()
:
FlatTemplatable
{
return
/* HTML */
`<input
type="submit"
value="
${
this
.
submitButtonText
}
"
formaction="
${
this
.
action
}
"
${
this
.
form_id
?
`form="
${
this
.
form_id
}
"`
:
""
}
/>`
;
}
async
render
(
ctx
:
Context
,
data
:
FormData
)
:
Promise
<
FlatTemplatable
>
{
return
tempstream
/* HTML */
`
${
this
.
makeOpenFormTag
(
ctx
)
}
${
!
this
.
controls
.
some
((
control
)
=>
control
.
role
==
"messages"
)
?
this
.
renderMessages
(
ctx
,
data
)
:
""
}
${
this
.
renderControls
(
ctx
,
data
)
}
${
this
.
controls
.
some
((
control
)
=>
control
.
role
==
"submit"
)
?
""
:
this
.
makeSubmitButton
()
}
${
this
.
makeCloseFormTag
()
}
`
;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public
async
makeFormClasses
(
_ctx
:
Context
)
:
Promise
<
string
[]
>
{
return
[];
}
public
makeOpenFormTag
(
ctx
:
Context
)
:
FlatTemplatable
{
return
tempstream
`<form method="POST" action="
${
this
.
action
}
" class="
${
this
.
makeFormClasses
(
ctx
).
then
((
classes
)
=>
classes
.
join
(
" "
)
)
}
"
${
this
.
useTurbo
?
""
:
`data-turbo="false"`
}
>`
;
}
public
makeCloseFormTag
()
:
FlatTemplatable
{
return
`</form>`
;
}
public
async
onValuesInvalid
(
ctx
:
Context
,
form_messages
:
FormMessage
[]
)
:
Promise
<
FormReaction
>
{
const
messages
=
form_messages
.
length
?
form_messages
:
[
<
const
>
{
type
:
"error"
,
text
:
"Some fields are invalid"
}];
return
{
action
:
"stay"
,
content
:
await
this
.
render
(
ctx
,
{
raw_values
:
this
.
extractRawValues
(
ctx
),
messages
,
}),
messages
,
};
}
public
async
onError
(
ctx
:
Context
,
data
:
FormData
,
error
:
unknown
)
:
Promise
<
FormReaction
>
{
let
error_message
=
"Unknown error has occured"
;
if
(
is
(
error
,
predicates
.
object
)
&&
hasShape
({
message
:
predicates
.
string
},
error
)
)
{
error_message
=
error
.
message
;
}
const
messages
=
[
<
const
>
{
type
:
"error"
,
text
:
error_message
}];
return
{
action
:
"stay"
,
content
:
await
this
.
render
(
ctx
,
{
raw_values
:
data
.
raw_values
,
messages
:
[],
}),
messages
,
};
}
public
abstract
onSubmit
(
ctx
:
Context
,
data
:
FormData
)
:
SubmitResult
|
Promise
<
SubmitResult
>
;
public
async
onSuccess
(
ctx
:
Context
,
data
:
FormData
,
_submitResult
:
SubmitResult
)
:
Promise
<
FormReaction
>
{
const
messages
=
[
<
const
>
{
type
:
"success"
,
text
:
this
.
defaultSuccessMessage
},
];
return
{
action
:
"stay"
,
content
:
await
this
.
render
(
ctx
,
{
raw_values
:
data
.
raw_values
,
messages
,
}),
messages
,
};
}
async
handlePost
(
ctx
:
Context
)
:
Promise
<
FormReaction
>
{
const
{
valid
,
form_messages
}
=
await
this
.
validate
(
ctx
,
this
.
extractRawValues
(
ctx
)
);
if
(
!
valid
)
{
return
this
.
onValuesInvalid
(
ctx
,
form_messages
);
}
try
{
ctx
.
status
=
303
;
const
result
=
await
this
.
onSubmit
(
ctx
,
{
raw_values
:
this
.
extractRawValues
(
ctx
),
messages
:
[],
});
return
this
.
onSuccess
(
ctx
,
{
raw_values
:
this
.
extractRawValues
(
ctx
),
messages
:
[],
},
result
);
}
catch
(
e
:
unknown
)
{
// eslint-disable-next-line no-console
console
.
dir
(
e
,
{
depth
:
5
});
const
message
=
is
(
e
,
predicates
.
object
)
&&
hasFieldOfType
(
e
,
"message"
,
predicates
.
string
)
?
e
?
.
message
:
is
(
e
,
predicates
.
string
)
?
e
:
"Wystąpił błąd"
;
return
this
.
onError
(
ctx
,
{
raw_values
:
this
.
extractRawValues
(
ctx
),
messages
:
[
{
type
:
"error"
,
text
:
message
,
},
],
},
e
);
}
}
public
mount
(
router
:
Router
,
path
:
string
)
:
void
{
router
.
use
(
path
,
async
(
ctx
,
next
)
=>
{
const
result
=
await
this
.
canAccess
(
ctx
);
if
(
!
result
.
canAccess
)
{
ctx
.
body
=
this
.
renderError
(
ctx
,
{
type
:
"access"
,
message
:
result
.
message
,
});
ctx
.
status
=
403
;
return
;
}
await
next
();
});
router
.
get
(
path
,
async
(
ctx
)
=>
{
ctx
.
type
=
"html"
;
ctx
.
body
=
await
this
.
render
(
ctx
,
{
raw_values
:
this
.
extractRawValues
(
ctx
),
messages
:
[],
});
});
router
.
post
(
path
,
async
(
ctx
)
=>
{
const
reaction
=
await
this
.
handlePost
(
ctx
);
if
(
reaction
.
action
==
"stay"
)
{
ctx
.
status
=
422
;
ctx
.
body
=
reaction
.
content
;
}
else
if
(
reaction
.
action
==
"redirect"
)
{
ctx
.
status
=
303
;
ctx
.
redirect
(
reaction
.
url
);
}
});
}
extractRawValues
(
ctx
:
Context
)
:
Record
<
string
,
FormDataValue
>
{
return
ctx
.
$body
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Sat, Oct 11, 11:08 (18 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
984261
Default Alt Text
form.ts (5 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment