fix pagination

This commit is contained in:
manuel 2023-01-08 15:45:28 +01:00
parent ba83f29ab5
commit e5bdbf5d8d
8 changed files with 140 additions and 91 deletions

View File

@ -246,16 +246,17 @@ pub fn derive_actix_admin_model(input: proc_macro::TokenStream) -> proc_macro::T
.order_by_asc(Column::Id) .order_by_asc(Column::Id)
.paginate(db, posts_per_page); .paginate(db, posts_per_page);
let num_pages = paginator.num_pages().await?; let num_pages = paginator.num_pages().await?;
let entities = paginator
.fetch_page(page - 1)
.await?;
let mut model_entities = Vec::new(); let mut model_entities = Vec::new();
if (num_pages == 0) { return Ok((num_pages, model_entities)) };
let entities = paginator
.fetch_page(std::cmp::min(num_pages - 1, page - 1))
.await?;
for entity in entities { for entity in entities {
model_entities.push( model_entities.push(
ActixAdminModel::from(entity) ActixAdminModel::from(entity)
); );
} }
Ok((num_pages, model_entities)) Ok((num_pages, model_entities))
} }

View File

@ -5,7 +5,7 @@ pub mod comment;
pub mod post; pub mod post;
pub use comment::Entity as Comment; pub use comment::Entity as Comment;
pub use post::Entity as Post; pub use post::Entity as Post;
use sea_orm::prelude::DateTime; use chrono::{Local};
use sea_orm::prelude::Decimal; use sea_orm::prelude::Decimal;
// setup // setup
@ -29,7 +29,7 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
.col(ColumnDef::new(post::Column::Text).string().not_null()) .col(ColumnDef::new(post::Column::Text).string().not_null())
.col(ColumnDef::new(post::Column::TeaMandatory).string().not_null()) .col(ColumnDef::new(post::Column::TeaMandatory).string().not_null())
.col(ColumnDef::new(post::Column::TeaOptional).string()) .col(ColumnDef::new(post::Column::TeaOptional).string())
.col(ColumnDef::new(post::Column::InsertDate).date()) .col(ColumnDef::new(post::Column::InsertDate).date().not_null())
.col(ColumnDef::new(post::Column::Attachment).string()) .col(ColumnDef::new(post::Column::Attachment).string())
.to_owned(); .to_owned();
@ -63,23 +63,24 @@ pub async fn create_post_table(db: &DbConn) -> Result<ExecResult, DbErr> {
let res = create_table(db, &stmt).await; let res = create_table(db, &stmt).await;
for i in 1..101 { for i in 1..1000 {
let row = post::ActiveModel { let row = post::ActiveModel {
title: Set(format!("Test {}", i)), title: Set(format!("Test {}", i)),
text: Set("some content".to_string()), text: Set("some content".to_string()),
tea_mandatory: Set(post::Tea::EverydayTea), tea_mandatory: Set(post::Tea::EverydayTea),
tea_optional: Set(None), tea_optional: Set(None),
insert_date: Set(Local::now().date_naive()),
..Default::default() ..Default::default()
}; };
let _res = Post::insert(row).exec(db).await; let _res = Post::insert(row).exec(db).await;
} }
for i in 1..101 { for i in 1..1000 {
let row = comment::ActiveModel { let row = comment::ActiveModel {
comment: Set(format!("Test {}", i)), comment: Set(format!("Test {}", i)),
user: Set("me@home.com".to_string()), user: Set("me@home.com".to_string()),
my_decimal: Set(Decimal::new(105, 0)), my_decimal: Set(Decimal::new(105, 0)),
insert_date: Set(DateTime::default()), insert_date: Set(Local::now().naive_utc()),
is_visible: Set(i%2 == 0), is_visible: Set(i%2 == 0),
..Default::default() ..Default::default()
}; };

View File

@ -21,15 +21,16 @@ pub struct Model {
pub tea_mandatory: Tea, pub tea_mandatory: Tea,
#[actix_admin(select_list="Tea")] #[actix_admin(select_list="Tea")]
pub tea_optional: Option<Tea>, pub tea_optional: Option<Tea>,
#[sea_orm(column_type = "Date")]
pub insert_date: Date, pub insert_date: Date,
#[actix_admin(file_upload)] #[actix_admin(file_upload)]
pub attachment: String pub attachment: Option<String>
} }
impl Display for Model { impl Display for Model {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match &*self { match &*self {
_ => write!(formatter, "{} {}", &self.title, &self.insert_date), _ => write!(formatter, "{} {}", &self.title, ""/* &self.insert_date*/),
} }
} }
} }

View File

@ -44,7 +44,7 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
let params = web::Query::<Params>::from_query(req.query_string()).unwrap(); let params = web::Query::<Params>::from_query(req.query_string()).unwrap();
let page = params.page.unwrap_or(1); let mut page = params.page.unwrap_or(1);
let entities_per_page = params let entities_per_page = params
.entities_per_page .entities_per_page
.unwrap_or(DEFAULT_ENTITIES_PER_PAGE); .unwrap_or(DEFAULT_ENTITIES_PER_PAGE);
@ -58,13 +58,22 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
match result { match result {
Ok(res) => { Ok(res) => {
let entities = res.1; let entities = res.1;
let num_pages = res.0; let num_pages = std::cmp::max(res.0, 1);
ctx.insert("entities", &entities); ctx.insert("entities", &entities);
ctx.insert("num_pages", &num_pages); ctx.insert("num_pages", &num_pages);
ctx.insert("page", &std::cmp::min(num_pages, page));
page = std::cmp::min(page, num_pages);
let min_show_page = if &page < &5 { 1 } else { let max_page = &page - &5; max_page };
let max_show_page = if &page >= &num_pages { std::cmp::max(1, num_pages - 1) } else { let max_page = &page + &5; std::cmp::min(num_pages - 1, max_page) };
ctx.insert("min_show_page", &min_show_page);
ctx.insert("max_show_page", &max_show_page);
}, },
Err(e) => { Err(e) => {
ctx.insert("entities", &Vec::<ActixAdminModel>::new()); ctx.insert("entities", &Vec::<ActixAdminModel>::new());
ctx.insert("num_pages", &0); ctx.insert("num_pages", &0);
ctx.insert("min_show_page", &1);
ctx.insert("max_show_page", &1);
ctx.insert("page", &1);
errors.push(e); errors.push(e);
} }
} }
@ -79,7 +88,6 @@ pub async fn list<T: ActixAdminAppDataTrait, E: ActixAdminViewModelTrait>(
ctx.insert("entity_name", &entity_name); ctx.insert("entity_name", &entity_name);
ctx.insert("notifications", &notifications); ctx.insert("notifications", &notifications);
ctx.insert("page", &page);
ctx.insert("entities_per_page", &entities_per_page); ctx.insert("entities_per_page", &entities_per_page);
ctx.insert("render_partial", &render_partial); ctx.insert("render_partial", &render_partial);
ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone())); ctx.insert("view_model", &ActixAdminViewModelSerializable::from(view_model.clone()));

View File

@ -12,9 +12,6 @@
</head> </head>
<body> <body>
<div id="loading" class="loader-wrapper htmx-indicator">
<div class="loader is-size-1"></div>
</div>
{% include "navbar.html" %} {% include "navbar.html" %}
<div class="container is-fluid"> <div class="container is-fluid">
<div id="notifications"> <div id="notifications">

View File

@ -7,6 +7,29 @@
<script src="https://unpkg.com/htmx.org@1.8.4"></script> <script src="https://unpkg.com/htmx.org@1.8.4"></script>
<script> <script>
document.onkeydown = function (e) {
switch (e.which) {
case 37: // left
let left_el = document.getElementsByClassName('left-arrow-click').item(0);
if (left_el) { left_el.click(); };
break;
//case 38: // up
// break;
case 39: // right
let right_el = document.getElementsByClassName('right-arrow-click').item(0);
if (right_el) { right_el.click(); };
break;
//case 40: // down
// break;
default: return; // exit this handler for other keys
}
e.preventDefault(); // prevent the default action (scroll / move caret)
};
function checkAll(bx) { function checkAll(bx) {
var cbs = document.getElementsByTagName('input'); var cbs = document.getElementsByTagName('input');
for (var i = 0; i < cbs.length; i++) { for (var i = 0; i < cbs.length; i++) {
@ -19,7 +42,7 @@
function sort_by(column) { function sort_by(column) {
document.getElementById("sort_by").value = column; document.getElementById("sort_by").value = column;
current_sort_order = document.getElementById("sort_order").value; current_sort_order = document.getElementById("sort_order").value;
if(current_sort_order == "asc") { if (current_sort_order == "asc") {
document.getElementById("sort_order").value = "desc"; document.getElementById("sort_order").value = "desc";
} else { } else {
document.getElementById("sort_order").value = "asc"; document.getElementById("sort_order").value = "asc";
@ -57,13 +80,12 @@
<style> <style>
.loader-wrapper { .loader-wrapper {
position: absolute; position: absolute;
top: 0;
left: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
display: flex; display: flex;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
justify-content: center; justify-content: center;
border-radius: 6px;
align-items: center; align-items: center;
z-index: 6; z-index: 6;
pointer-events: none pointer-events: none

View File

@ -33,6 +33,7 @@
<p class="control has-icons-left has-icons-right"> <p class="control has-icons-left has-icons-right">
<input class="input is-rounded" type="search" id="search" value="{{ search }}" name="search" <input class="input is-rounded" type="search" id="search" value="{{ search }}" name="search"
placeholder="Search" placeholder="Search"
hx-push-url="true"
hx-get="/admin/{{ entity_name }}/list?render_partial=true&entities_per_page={{ entities_per_page }}&page={{ page }}&sort_by={{ sort_by }}&sort_order={{ sort_order }}" hx-get="/admin/{{ entity_name }}/list?render_partial=true&entities_per_page={{ entities_per_page }}&page={{ page }}&sort_by={{ sort_by }}&sort_order={{ sort_order }}"
hx-trigger="keyup changed delay:500ms, search" hx-target="#{{ entity_name }}table" hx-trigger="keyup changed delay:500ms, search" hx-target="#{{ entity_name }}table"
hx-indicator="#loading"> hx-indicator="#loading">
@ -68,96 +69,109 @@
<div id="{{ entity_name }}table"> <div id="{{ entity_name }}table">
<form id="checked-rows"> <form id="checked-rows">
<table class="table is-fullwidth is-hoverable is-striped"> <div class="is-relative">
<thead> {% include "loader.html" %}
<tr> <table class="table is-relative is-fullwidth is-hoverable is-striped">
<th> <thead>
<input type="checkbox" onclick="checkAll(this)"> <tr>
</th> <th>
<th onclick="sort_by('{{ view_model.primary_key }}');" class="is-clickable">{{ view_model.primary_key | title }} <input type="checkbox" onclick="checkAll(this)">
{% if sort_by == view_model.primary_key %} </th>
{% if sort_order == "asc" %} <th onclick="sort_by('{{ view_model.primary_key }}');" class="is-clickable">{{
<i class="ml-1 fa-solid fa-caret-up"></i> view_model.primary_key | title }}
{% else %} {% if sort_by == view_model.primary_key %}
<i class="ml-1 fa-solid fa-caret-down"></i>
{% endif %}
{% endif %}
</th>
{% for model_field in view_model.fields -%}
<th onclick="sort_by('{{ model_field.field_name }}');" class="is-clickable">{{ model_field.field_name | split(pat="_") | join(sep=" ") | title }}
{% if sort_by == model_field.field_name %}
{% if sort_order == "asc" %} {% if sort_order == "asc" %}
<i class="ml-1 fa-solid fa-caret-up"></i> <i class="ml-1 fa-solid fa-caret-up"></i>
{% else %} {% else %}
<i class="ml-1 fa-solid fa-caret-down"></i> <i class="ml-1 fa-solid fa-caret-down"></i>
{% endif %} {% endif %}
{% endif %}
</th>
{% for model_field in view_model.fields -%}
<th onclick="sort_by('{{ model_field.field_name }}');" class="is-clickable">{{
model_field.field_name | split(pat="_") | join(sep=" ") | title }}
{% if sort_by == model_field.field_name %}
{% if sort_order == "asc" %}
<i class="ml-1 fa-solid fa-caret-up"></i>
{% else %}
<i class="ml-1 fa-solid fa-caret-down"></i>
{% endif %}
{% endif %}
</th>
{%- endfor %}
<th>
<!-- Edit Action -->
<!-- Delete Action -->
</th>
</tr>
</thead>
<tbody hx-confirm="Are you sure?" hx-target="closest tr" hx-indicator="#loading" hx-swap="outerHTML">
{% for entity in entities -%}
<tr>
<td><input type="checkbox" name="ids" value="{{ entity.primary_key }}"></td>
<td>
<a href="show/{{ entity.primary_key }}">
<i class="fa-solid fa-magnifying-glass"></i> {{ entity.primary_key }}
</a>
</td>
{% for model_field in view_model.fields -%}
{% if model_field.field_type == "Checkbox" %}
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
{% elif model_field.field_type == "FileUpload" %}
<td><a href="static_content/{{ entity.primary_key }}/{{ model_field.field_name }}">{{
entity.values
| get(key=model_field.field_name) }}</a></td>
{% else %}
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
{% endif %} {% endif %}
</th> {%- endfor %}
<td>
<a href="edit/{{ entity.primary_key }}"><i class="fa-solid fa-pen-to-square"></i></a>
<a hx-delete="delete/{{ entity.primary_key }}"><i class="fa-solid fa-trash"></i></a>
</td>
</tr>
{%- endfor %} {%- endfor %}
<th> </tbody>
<!-- Edit Action --> <tfoot>
<!-- Delete Action --> <tr>
</th> <td colspan="{{ view_model.fields | length + 3 }}">
</tr> </td>
</thead> </tr>
<tbody hx-confirm="Are you sure?" hx-target="closest tr" hx-indicator="#loading" hx-swap="outerHTML"> </tfoot>
{% for entity in entities -%} </table>
<tr> </div>
<td><input type="checkbox" name="ids" value="{{ entity.primary_key }}"></td>
<td>
<a href="show/{{ entity.primary_key }}">
<i class="fa-solid fa-magnifying-glass"></i> {{ entity.primary_key }}
</a>
</td>
{% for model_field in view_model.fields -%}
{% if model_field.field_type == "Checkbox" %}
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
{% elif model_field.field_type == "FileUpload" %}
<td><a href="static_content/{{ entity.primary_key }}/{{ model_field.field_name }}">{{ entity.values | get(key=model_field.field_name) }}</a></td>
{% else %}
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
{% endif %}
{%- endfor %}
<td>
<a href="edit/{{ entity.primary_key }}"><i class="fa-solid fa-pen-to-square"></i></a>
<a hx-delete="delete/{{ entity.primary_key }}"><i class="fa-solid fa-trash"></i></a>
</td>
</tr>
{%- endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="{{ view_model.fields | length + 3 }}">
</td>
</tr>
</tfoot>
</table>
</form> </form>
<nav hx-boost="true" hx-indicator="#loading" class="pagination" aria-label="pagination"> <nav hx-boost="true" hx-indicator="#loading" class="pagination is-rounded is-centered" role="pagination" aria-label="pagination">
{% if page > 1 %}
<a href="?page={{ page - 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}" <a href="?page={{ page - 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}"
class="pagination-previous">Previous Page</a> class="pagination-previous left-arrow-click"><i class="fa-solid fa-arrow-left"></i>
</a>
{% endif %}
{% if page < num_pages %}
<a href="?page={{ page + 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}" <a href="?page={{ page + 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}"
class="pagination-next">Next page</a> class="pagination-next right-arrow-click"><i class="fa-solid fa-arrow-right"></i>
</a>
{% endif %}
<ul class="pagination-list"> <ul class="pagination-list">
<!-- <li> <li>
<a class="pagination-link" <a class="pagination-link {% if page == 1 %}is-current{% endif %}"
href="?page={{ page - 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}" href="?page={{ 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}"
aria-label="Goto page 1">1</a> aria-label="Goto page 1">1</a>
</li> --> </li>
<li> <li>
<span class="pagination-ellipsis">&hellip;</span> <span class="pagination-ellipsis">&hellip;</span>
</li> </li>
{% for i in range(end=num_pages) %} {% for i in range(start=min_show_page,end=max_show_page) %}
<li><a class="pagination-link" aria-label="Goto page 45" <li><a class="pagination-link {% if page == i+1 %}is-current{% endif %}" aria-label="Goto page {{ i + 1 }}"
href="?page={{ i + 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}">{{ href="?page={{ i + 1 }}&entities_per_page={{ entities_per_page }}&search={{ search }}">{{
i + 1 }}</a></li> i + 1 }}</a></li>
{%- endfor %} {%- endfor %}
<li> <li>
<span class="pagination-ellipsis">&hellip;</span> <span class="pagination-ellipsis">&hellip;</span>
</li> </li>
<!-- <li> <li>
<a class="pagination-link" aria-label="Goto page {{ num_pages }}">{{ num_pages }} </a> <a href="?page={{ num_pages }}&entities_per_page={{ entities_per_page }}&search={{ search }}" class="pagination-link is-rounded {% if page == num_pages %}is-current{% endif %}" aria-label="Goto page {{ num_pages }}">{{ num_pages }} </a>
</li> --> </li>
</ul> </ul>
</nav> </nav>
</div> </div>

5
templates/loader.html Normal file
View File

@ -0,0 +1,5 @@
<div id="loading" class="loader-wrapper htmx-indicator">
<div>
<div class="loader is-size-1"></div>
</div>
</div>