Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F996353
form.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
6 KB
Referenced Files
None
Subscribers
None
form.ts
View Options
import
{
Context
}
from
"koa"
;
import
{
randomUUID
}
from
"crypto"
;
import
Router
from
"@koa/router"
;
import
{
FlatTemplatable
,
tempstream
}
from
"tempstream"
;
import
{
hasFieldOfType
,
hasShape
,
is
,
predicates
,
}
from
"@sealcode/ts-predicates"
;
import
{
FormData
,
FormDataValue
,
FormMessage
,
FormReaction
,
}
from
"./form-types.js"
;
import
{
Fields
,
MountableWithFields
,
PageErrorMessage
,
}
from
"../page/mountable-with-fields.js"
;
export
abstract
class
Form
<
F
extends
Fields
,
SubmitResult
>
extends
MountableWithFields
<
F
>
{
defaultSuccessMessage
=
"Done"
;
submitButtonText
=
"Wyślij"
;
action
=
"./"
;
useTurbo
=
true
;
form_id
=
randomUUID
()
as
string
;
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
,
show_field_errors
:
boolean
)
:
Promise
<
FlatTemplatable
>
{
return
tempstream
/* HTML */
`<div class="form-container">
${
this
.
makeOpenFormTag
(
ctx
)
}
${
!
this
.
controls
.
some
((
control
)
=>
control
.
role
==
"messages"
)
?
this
.
renderMessages
(
ctx
,
data
)
:
""
}
${
this
.
renderControls
(
this
.
makeFormControlContext
(
ctx
,
data
,
show_field_errors
)
)
}
${
this
.
controls
.
some
((
control
)
=>
control
.
role
==
"submit"
)
?
""
:
this
.
makeSubmitButton
()
}
${
this
.
makeCloseFormTag
()
}
</div>`
;
}
// 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 enctype="multipart/form-data" method="POST" id="
${
this
.
form_id
}
" action="
${
this
.
action
}
" class="
${
this
.
makeFormClasses
(
ctx
).
then
(
(
classes
)
=>
classes
.
join
(
" "
)
)
}
"
${
this
.
useTurbo
?
""
:
`data-turbo="false"`
}
></form><div class="form">`
;
}
public
makeCloseFormTag
()
:
FlatTemplatable
{
return
`</div>`
;
}
public
async
onValuesInvalid
(
ctx
:
Context
,
form_messages
:
FormMessage
[],
_field_errors
:
Partial
<
Record
<
keyof
Fields
,
string
>>
)
:
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
:
await
this
.
extractRawValues
(
ctx
),
messages
,
},
"show_field_errors"
&&
true
),
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
,
},
"show_field_errors"
&&
true
),
};
}
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
:
await
this
.
getInitialValues
(
ctx
),
messages
,
},
"show_field_errors"
&&
false
),
messages
,
};
}
async
getRawValuesOnSuccess
(
ctx
:
Context
)
{
return
this
.
extractRawValues
(
ctx
);
}
async
handlePost
(
ctx
:
Context
)
:
Promise
<
FormReaction
>
{
const
raw_values
=
await
this
.
extractRawValues
(
ctx
);
const
{
valid
,
form_messages
,
field_errors
}
=
await
this
.
validate
(
ctx
,
raw_values
);
if
(
!
valid
)
{
console
.
debug
(
"Form values invalid: "
,
{
form_messages
,
field_errors
,
});
return
this
.
onValuesInvalid
(
ctx
,
form_messages
,
field_errors
);
}
try
{
ctx
.
status
=
303
;
const
result
=
await
this
.
onSubmit
(
ctx
,
{
raw_values
,
messages
:
[],
});
return
this
.
onSuccess
(
ctx
,
{
raw_values
:
await
this
.
getRawValuesOnSuccess
(
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
,
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
();
});
// for use with other subroutes or middlewares that individual controls
// might register
const
subrouter
=
new
Router
();
for
(
const
control
of
this
.
controls
)
{
control
.
mount
(
subrouter
,
this
);
}
router
.
use
(
path
,
subrouter
.
routes
(),
subrouter
.
allowedMethods
());
router
.
get
(
path
,
async
(
ctx
)
=>
{
ctx
.
type
=
"html"
;
ctx
.
body
=
await
this
.
render
(
ctx
,
{
raw_values
:
await
this
.
extractRawValues
(
ctx
),
messages
:
[],
},
"show_field_errors"
&&
false
);
});
router
.
post
(
path
,
async
(
ctx
)
=>
{
console
.
log
(
"REGULAR FORM HANDLE"
);
const
reaction
=
(
ctx
.
override_reaction
as
FormReaction
|
undefined
)
||
(
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
);
}
});
}
async
extractRawValues
(
ctx
:
Context
)
:
Promise
<
Record
<
string
,
FormDataValue
>>
{
return
Object
.
keys
(
ctx
.
$body
).
length
?
ctx
.
$body
:
this
.
getInitialValues
(
ctx
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Tue, Dec 24, 14:02 (17 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
557202
Default Alt Text
form.ts (6 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment