fix pagination
This commit is contained in:
parent
ba83f29ab5
commit
e5bdbf5d8d
@ -246,10 +246,11 @@ 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)
|
||||||
|
@ -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()
|
||||||
};
|
};
|
||||||
|
@ -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*/),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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", ¬ifications);
|
ctx.insert("notifications", ¬ifications);
|
||||||
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()));
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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,13 +69,16 @@
|
|||||||
|
|
||||||
<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">
|
||||||
|
{% include "loader.html" %}
|
||||||
|
<table class="table is-relative is-fullwidth is-hoverable is-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
<input type="checkbox" onclick="checkAll(this)">
|
<input type="checkbox" onclick="checkAll(this)">
|
||||||
</th>
|
</th>
|
||||||
<th onclick="sort_by('{{ view_model.primary_key }}');" class="is-clickable">{{ view_model.primary_key | title }}
|
<th onclick="sort_by('{{ view_model.primary_key }}');" class="is-clickable">{{
|
||||||
|
view_model.primary_key | title }}
|
||||||
{% if sort_by == view_model.primary_key %}
|
{% if sort_by == view_model.primary_key %}
|
||||||
{% 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>
|
||||||
@ -84,7 +88,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
{% for model_field in view_model.fields -%}
|
{% 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 }}
|
<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_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>
|
||||||
@ -113,7 +118,9 @@
|
|||||||
{% if model_field.field_type == "Checkbox" %}
|
{% if model_field.field_type == "Checkbox" %}
|
||||||
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
<td>{{ entity.values | get(key=model_field.field_name) | get_icon | safe }}</td>
|
||||||
{% elif model_field.field_type == "FileUpload" %}
|
{% 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>
|
<td><a href="static_content/{{ entity.primary_key }}/{{ model_field.field_name }}">{{
|
||||||
|
entity.values
|
||||||
|
| get(key=model_field.field_name) }}</a></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
<td>{{ entity.values | get(key=model_field.field_name) }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -132,32 +139,39 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</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">…</span>
|
<span class="pagination-ellipsis">…</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">…</span>
|
<span class="pagination-ellipsis">…</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
5
templates/loader.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div id="loading" class="loader-wrapper htmx-indicator">
|
||||||
|
<div>
|
||||||
|
<div class="loader is-size-1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user