Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F1262575
list.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
list.ts
View Options
import
{
Context
}
from
"koa"
;
import
{
Templatable
,
tempstream
,
FlatTemplatable
}
from
"tempstream"
;
import
{
Page
}
from
"./page.js"
;
import
{
ShapeToType
}
from
"@sealcode/ts-predicates"
;
import
{
FormControl
}
from
"../forms/controls/controls.js"
;
import
{
FormField
}
from
"../forms/fields/field.js"
;
import
{
naturalNumbers
,
UrlWithNewParams
}
from
"../utils/utils.js"
;
import
{
makeHiddenInputs
}
from
"../make-hidden-inputs.js"
;
import
qs
from
"qs"
;
import
{
NumberField
}
from
"../forms/fields/number.js"
;
import
{
SimpleFormField
}
from
"../forms/fields/simple-form-field.js"
;
import
{
FormDataValue
}
from
"../forms/form-types.js"
;
export
const
BasePagePropsShape
=
<
const
>
{};
export
type
BasePageProps
=
ShapeToType
<
typeof
BasePagePropsShape
>
;
export
const
DEFAULT_ITEMS_PER_PAGE
=
12
;
export
const
BaseListPageFields
=
<
const
>
{
page
:
new
NumberField
(
false
,
1
),
itemsPerPage
:
new
NumberField
(
false
,
DEFAULT_ITEMS_PER_PAGE
),
sort
:
new
SimpleFormField
(
false
),
};
export
type
ListSort
=
{
field
:
string
;
order
:
"asc"
|
"desc"
};
const
SORT_SEPARATOR
=
":"
;
function
decodeSort
(
s
:
unknown
)
:
ListSort
|
null
{
if
(
typeof
s
!==
"string"
||
!
s
.
includes
(
SORT_SEPARATOR
))
{
return
null
;
}
let
order
=
s
.
split
(
SORT_SEPARATOR
)[
1
];
const
field
=
s
.
split
(
SORT_SEPARATOR
)[
0
];
if
(
order
!==
"asc"
&&
order
!==
"desc"
)
{
order
=
"asc"
;
return
{
field
,
order
:
order
as
"asc"
,
};
}
else
{
return
{
field
,
order
};
}
}
function
encodeSort
(
field
:
string
,
order
:
"asc"
|
"desc"
)
:
string
{
return
[
field
,
order
].
join
(
SORT_SEPARATOR
);
}
export
abstract
class
ListPage
<
ItemType
,
F
extends
typeof
BaseListPageFields
>
extends
Page
<
F
>
{
abstract
getItems
(
ctx
:
Context
,
page
:
number
,
itemsPerPage
:
number
|
null
,
values
:
Record
<
string
,
FormDataValue
>
)
:
Promise
<
ItemType
[]
>
;
abstract
getTotalPages
(
ctx
:
Context
,
itemsPerPage
:
number
,
values
:
Record
<
string
,
FormDataValue
>
)
:
Promise
<
number
>
;
abstract
renderItem
(
ctx
:
Context
,
item
:
ItemType
,
index
:
number
)
:
Promise
<
FlatTemplatable
>
;
filterFields
:
Record
<
string
,
FormField
>
=
{};
filterControls
:
FormControl
[]
=
[];
init
()
:
void
{
super
.
init
();
for
(
const
[
fieldname
,
field
]
of
Object
.
entries
(
this
.
filterFields
))
{
void
field
.
init
(
fieldname
);
}
}
renderListContainer
(
_
:
Context
,
content
:
Templatable
)
:
FlatTemplatable
{
return
tempstream
`<div>
${
content
}
</div>`
;
}
async
getPaginationConfig
(
ctx
:
Context
)
{
const
values
=
await
this
.
extractRawValues
(
ctx
);
let
{
parsed
:
page
}
=
await
this
.
fields
.
page
.
getValue
(
ctx
,
values
);
if
(
!
page
)
{
page
=
1
;
}
const
{
parsed
:
itemsPerPage
}
=
await
this
.
fields
.
itemsPerPage
.
getValue
(
ctx
,
values
);
return
{
page
,
itemsPerPage
};
}
async
renderItems
(
ctx
:
Context
,
values
?:
Record
<
string
,
FormDataValue
>
,
items
?:
ItemType
[]
)
:
Promise
<
FlatTemplatable
>
{
if
(
!
values
)
{
values
=
await
this
.
extractRawValues
(
ctx
);
}
const
{
itemsPerPage
,
page
}
=
await
this
.
getPaginationConfig
(
ctx
);
const
items_promise
=
this
.
getItems
(
ctx
,
page
,
itemsPerPage
,
values
);
return
tempstream
`
${
(
items
?
Promise
.
resolve
(
items
)
:
items_promise
).
then
((
items
)
=>
items
.
map
((
item
,
index
)
=>
this
.
renderItem
(
ctx
,
item
,
index
))
)
}
`
;
}
async
renderPagination
(
ctx
:
Context
,
values
:
Record
<
string
,
FormDataValue
>
)
:
Promise
<
FlatTemplatable
>
{
const
{
itemsPerPage
,
page
}
=
await
this
.
getPaginationConfig
(
ctx
);
const
totalIems
=
await
this
.
getTotalPages
(
ctx
,
itemsPerPage
||
DEFAULT_ITEMS_PER_PAGE
,
values
);
return
tempstream
/* HTML */
`<div class="list-pagination">
<div class="list-pagination__left">
${
page
>
1
?
this
.
renderPageButton
(
ctx
,
1
,
"Pierwsza strona"
)
:
""
}
${
page
>
1
?
this
.
renderPageButton
(
ctx
,
page
-
1
,
"Poprzednia strona"
)
:
""
}
</div>
<div class="list-pagination__center">
<select
title="choose page"
onchange="if (this.value) Turbo.visit(this.value)"
>
${
Array
.
from
(
naturalNumbers
(
1
,
totalIems
)).
map
(
(
n
)
=>
/* HTML */
`<option
value="
${
UrlWithNewParams
(
ctx
,
//eslint-disable-next-line @typescript-eslint/consistent-type-assertions
this
.
propsParser
.
overwriteProp
(
ctx
,
{
page
:
n
,
}
as Partial<Record<string, unknown>>)
)}"
${
page
===
n
?
"selected"
:
""
}
>
${
n
}
</option>`
)
}
</select>
</div>
<div class="list-pagination__right">
${
page
<
totalIems
?
this
.
renderPageButton
(
ctx
,
page
+
1
,
"Następna strona"
)
:
""
}
${
page
<
totalIems
?
this
.
renderPageButton
(
ctx
,
totalIems
,
"Ostatnia strona"
)
:
""
}
</div>
</div>`
;
}
private
renderPageButton
(
ctx
:
Context
,
page
:
number
,
text
:
string
)
{
return
/* HTML */
`<a
href="
${
UrlWithNewParams
(
ctx
,
//eslint-disable-next-line @typescript-eslint/consistent-type-assertions
this
.
propsParser
.
overwriteProp
(
ctx
,
{
page
,
}
as Partial<Record<string, unknown>>)
)}"
>
${
text
}
</a
>`
;
}
async
getFilterValues
(
ctx
:
Context
)
:
Promise
<
Record
<
string
,
unknown
>>
{
const
filter
=
{}
as
Record
<
string
,
unknown
>
;
const
raw_values
=
await
this
.
extractRawValues
(
ctx
);
for
(
const
[
fieldname
,
field
]
of
Object
.
entries
(
this
.
filterFields
))
{
// eslint-disable-next-line no-await-in-loop
const
{
parsed
}
=
await
field
.
getValue
(
ctx
,
raw_values
);
filter
[
fieldname
]
=
field
.
mapToFilter
(
parsed
);
}
return
filter
;
}
async
getSort
(
ctx
:
Context
)
:
Promise
<
ListSort
|
null
>
{
const
{
sort
}
=
await
this
.
extractRawValues
(
ctx
);
const
decoded
=
decodeSort
(
sort
);
if
(
decoded
===
null
)
{
return
this
.
getDefaultSort
(
ctx
);
}
else
{
return
decoded
;
}
}
makeSortLink
(
ctx
:
Context
,
field
:
string
,
order
:
"asc"
|
"desc"
)
:
string
{
const
url
=
new
URL
(
ctx
.
url
,
"https://example.com"
);
const
params
=
qs
.
parse
(
url
.
search
.
slice
(
1
));
params
.
sort
=
encodeSort
(
field
,
order
);
url
.
search
=
qs
.
stringify
(
params
);
return
url
.
pathname
+
url
.
search
;
}
async
renderFilters
(
ctx
:
Context
)
:
Promise
<
FlatTemplatable
>
{
const
values
=
await
this
.
extractRawValues
(
ctx
);
return
tempstream
/* HTML */
`<form method="GET">
${
makeHiddenInputs
(
ctx
,
this
.
fields
,
values
,
[
"page"
,
...
Object
.
values
(
this
.
filterFields
).
map
((
f
)
=>
f
.
name
),
])
}
${
this
.
filterControls
.
map
((
control
)
=>
{
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return
control
.
render
(
this
.
makeFormControlContext
(
ctx
,
{
raw_values
:
values
,
messages
:
[]
}
,
false
)
);
})}
</form>`
;
}
async
renderHeading
(
ctx
:
Context
,
field
:
string
,
label
=
field
)
:
Promise
<
FlatTemplatable
>
{
const
current_sort
=
await
this
.
getSort
(
ctx
);
const
current_order
=
current_sort
?
.
field
==
field
?
current_sort
.
order
:
null
;
const
order
=
current_order
==
"desc"
?
"asc"
:
"desc"
;
return
/* HTML */
`<th>
<a href="
${
this
.
makeSortLink
(
ctx
,
field
,
order
)
}
"
>
${
label
}
${
(
current_order
&&
(
current_order
==
"asc"
?
"↑"
:
"↓"
))
||
""
}
</a
>
</th>`
;
}
getDefaultSort
(
_
:
Context
)
:
ListSort
|
null
{
return
null
;
}
renderTableHead
(
ctx
:
Context
,
fields
:
{
field
:
string
;
label
?:
string
}[]
)
:
FlatTemplatable
{
return
tempstream
/* HTML */
`<thead>
<tr>
${
fields
.
map
(({
label
,
field
}
) =>
this.renderHeading(ctx, field, label)
)}
</tr>
</thead>`
;
}
async
render
(
ctx
:
Context
)
:
Promise
<
FlatTemplatable
>
{
const
values
=
await
this
.
extractRawValues
(
ctx
);
return
tempstream
`
${
this
.
renderPagination
(
ctx
,
values
)
}
${
this
.
renderFilters
(
ctx
)
}
${
this
.
renderListContainer
(
ctx
,
this
.
renderItems
(
ctx
,
values
))
}
`
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-java
Expires
Fri, Jan 24, 15:15 (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
598011
Default Alt Text
list.ts (7 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment