Blog Engine with Fresh: Paginating Post Item
Tags: javascript, webdev, react, fresh
Published: 2022-08-10 04:36:00

{% embed https://dev.to/sokhavuth/table-of-contents-41pd %}
GitHub: [https://github.com/Sokhavuth/deno-fresh](https://github.com/Sokhavuth/deno-fresh)
Deno Deploy: [https://khmerweb-fresh.deno.dev/login](https://khmerweb-fresh.deno.dev/login)
<br/>
```jsx
// components/admin/index.jsx

/** @jsx h */
import { h } from "preact";
import Base from "../base.jsx"


function IndexJsx(props){
    const Page = props.data.pageInner;
    const items = props.data.setting.items;
    
    const listItems = items.map((item) =>
    <li>
      <a class="thumb" href={`/post/${item.id}`}>
        <img src={item.thumb} />
        {((item.videos !== "" )&&(item.videos !== "[]")) &&
          <img class="play-icon" src={`/images/play.png`} />
        }
      </a>
      <div class="title">
        <a href={`/post/${item.id}`}>{item.title}</a>
        <div>{(new Date(item.date)).toLocaleDateString('it-IT')}</div>
      </div>
      <div class="edit">
        <a href={`/admin/post/edit/${item.id}`}><img src={`/images/edit.png`} /></a>
        <a href={`/admin/post/delete/${item.id}`}><img src={`/images/delete.png`} /></a>
      </div>
    </li>
    )
    return(
        <section class="Index" >
            <link rel="stylesheet" href="/styles/admin/index.css" />
            <script src="/scripts/paginate.js"></script>
        <header>
          <div class="inner region">
              <div class="title">{props.data.setting.page_title}</div>
              <form action="/admin/search" method="post">
                <select name="admin_search">
                  <option>posts</option>
                  <option>books</option>
                </select>
                <input type="text" name="admin_q" required placeholder="Search" />
                <input type="submit" value="Search" />
              </form>
              <div class="logout"><span>{props.data.setting.username}</span> | <a href="/">Home</a> | <a href="/logout">Logout</a></div>
          </div>
        </header>

        <div class="main region">
          <div class="sidebar">
            <div class="inner">
              <a href="/admin/post"><img src="/images/movie.png" /></a>
              <a href="/admin/post">Post</a>

              <a href="/admin/book"><img src="/images/books.png" /></a>
              <a href="/admin/book">Book</a>

              <a href="/admin/category"><img src="/images/category.png" /></a>
              <a href="/admin/category">Category</a>

              <a href="/admin/upload"><img src="/images/upload.png" /></a>
              <a href="/admin/upload">Upload</a>

              <a href="/admin/user"><img src="/images/users.png" /></a>
              <a href="/admin/user">User</a>

              <a href="/admin/setting"><img src="/images/setting.png" /></a>
              <a href="/admin/setting">Setting</a>
            </div>
          </div>
          <div class="content">
            <Page data={props.data} />
          </div>
        </div>

        <div class="footer region">
          <div class="info">Total amount of items: {props.data.setting.count}</div>
          <ul class="list">
              { listItems }
          </ul>
          <div class="pagination" dangerouslySetInnerHTML={{__html: `
              <img onclick="paginate('${props.data.setting.route}')" src="/images/load-more.png" />
          `}}/>
            
          <div class="credit">&copy; <a href="https://khmerweb.vercel.app/">Khmer Web 2022</a></div>
        </div>
        </section>
    )
}

export default function Index(props){
    props.data.page = IndexJsx
    return(
        <Base data={props.data} />
    )
}
```

```js
//static/scripts/paginate.js

let page = 0

async function paginate(route){
    $('.pagination img').attr('src', '/images/loading.gif')
    page += 1
    
    const resp = await fetch(`/api/paginate/${page}`);
    if (resp.status === 404) {
        alert('no post');
        return;
    }
    
    const data = await resp.json();
    appendItem(data.items, route, data);
}

function appendItem(items, route,data){
    let html = ''
    
    if(items){
        for(const item of items){
            html += `<li>`
                html += `<div class='thumb'>`
                    html += `<a href="/${data.type}/${item.id}">
                                <img src="${item.thumb}"/>`
                                if((item.video)&&(item.video !== '[]')){
                                    html += `<img class="play-icon" src="/images/play.png"/>`
                                }
                    html += `</a>`
                html += `</div>`
                html += `<div class="title">`
                    html += `<a href="/${data.type}/${item.id}">${item.title}</a>`
                    html += `<div>${new Date(item.date).toLocaleDateString('it-IT')}</div>`
                html += `</div>`
                html += `<div class="edit">`
                    html += `<a href="${route}/edit/${item.id}"><img src="/images/edit.png"/></a>`
                    html += `<a href="${route}/delete/${item.id}"><img src="/images/delete.png"/></a>`
                html += `</div>` 
            html += `</li>`
        }
    }
    $('.list').append(html)

    if(route === '/admin/user'){
        $('.Footer .list li').css({'grid-template-columns':'13% auto 25%'})
        $('.Footer .list li .thumb').css({'padding-top':'100%'})
        $('.Footer .list li .thumb img').css({'border-radius':'50%'})
    }

    $('.pagination img').attr('src', '/images/load-more.png')
}
```

```js
// routes/api/paginate/[page].js

import { setting } from 'setting';
import postdb from "../../../models/post.ts";


export const handler = async (_req, ctx) => {
    const config = setting();
    const posts = await postdb.paginatePosts(config.post_amount, ctx.params.page);
    return new Response(JSON.stringify({items: posts, type: "post"}));
};
```

```js
// models/post.ts

import { mydb } from "setting"

interface PostSchema {
    _id: ObjectId;
    id: string; 
    title: string;
    content: string;
    categories: string[];
    thumb: string;
    date: string;
    videos: string;
    userid: string;
}

class Post{
    async count(query={}){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.countDocuments(query)
    }

    async insertPost(req, user_id: string){
        const id = crypto.randomUUID()
        const formData = await req.formData()

        let categories: string[]

        if(formData.get("categories").includes(',')){
            categories = formData.get("categories").split(',')
        }else{
            categories = [formData.get("categories")]
        }
        
        const new_post = {
            id: id, 
            title: formData.get("title"),
            content: formData.get("content"),
            categories: categories,
            thumb: formData.get("thumb"),
            date: formData.get("datetime"),
            videos: formData.get("videos"),
            userid: user_id,
        }
 
        const posts = mydb.collection<PostSchema>("posts")
        await posts.insertOne(new_post)
    }

    async getPosts(amount: number, query={}){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.find(query).sort({date:-1,_id:-1}).limit(amount).toArray()
    }

    async getPost(post_id: string){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.findOne({id: post_id})
    }

    async updatePost(req, post_id: string){
        const formData = await req.formData()

        let categories: string[]

        if(formData.get("categories").includes(',')){
            categories = formData.get("categories").split(',')
        }else{
            categories = [formData.get("categories")]
        }

        const edited_post = {$set:{
            title: formData.get("title"),
            content: formData.get("content"),
            categories: categories,
            thumb: formData.get("thumb"),
            date: formData.get("datetime"),
            videos: formData.get("videos"),
        }}

        const posts = mydb.collection<PostSchema>("posts")
        await posts.updateOne({id: post_id}, edited_post)
    }

    async deletePost(post_id: string){
        const posts = mydb.collection<PostSchema>("posts")
        await posts.deleteOne({id: post_id})
    }

    async paginatePosts(amount: number, page: number){
        const posts = mydb.collection<PostSchema>("posts")
        return await posts.find().skip(amount*page).sort({date:-1,_id:-1}).limit(amount).toArray();
    }

}

export default new Post()
```