[ExpressJS] Bài 8 – Viết Code Điều Hành Blog Cá Nhân (Tiếp Theo)

Trong bài này, chúng ta sẽ bắt đầu viết code điều hành cho nhóm route/article. Tuy nhiên trước khi bắt tay vào viết code xử lý cho các route mới, chúng ta hãy cùng nhìn lại code xử lý yêu cầu xem trang chủ đã được xử lý trong bài trước. router.get("/",async(request, response)=>{var data =newData();/*

Trong bài này, chúng ta sẽ bắt đầu viết code điều hành cho nhóm route/article. Tuy nhiên trước khi bắt tay vào viết code xử lý cho các route mới, chúng ta hãy cùng nhìn lại code xử lý yêu cầu xem trang chủ đã được xử lý trong bài trước.

router.get("/",async(request, response)=>{var data =newData();/* --- Data for Meta */var logoText = config.get("logo-text");
   data.set("title",`${logoText} | Trang Chủ`);/* --- Data for Topnav */

   data.set("logo-text", logoText);var categoryList =[];await databaseManager.execute(
      Category.name,"select",
      categoryList,Infinity,"default",["@id","name"]);for(var category of categoryList){var id = category.get("@id");
      category.set("url",`/category/view/${id}`);}

   data.set("category-list", categoryList.slice(0,-1));/* --- Data for Header */

   data.set("heading", config.get("site-heading")).set("description", config.get("site-description"));/* --- Data for Entry */var articleList =[];await databaseManager.execute(
      Article.name,"select",
      articleList,10,"reversed",["@id","title","content"]);var enryList =[];for(var article of articleList.slice(1)){var contentMarkdown = article.get("content");var excerptMarkdown = contentMarkdown.slice(0,300);var entry =newEntry();
      entry.set("title", article.get("title")).set("excerpt",`${excerptMarkdown}...`).set("url",`/article/view/${article.get("@id")}`);
           
      enryList.push(entry);}

   data.set("entry-list", enryList);/* --- Render the Page */

   response.render("index",{
      layout:"home",
      action:null,
      data
   });});// router.get

Tất cả nội dung công việc mà chúng ta đã xử lý trong đoạn code này chỉ là truy vấn dữ liệu từ database, và map (chuyển đổi) sang kiểu dữ liệu phù hợp để cung cấp cho các thành phần trong giao diện trang chủ.

Code điều hành của các route khác về cơ bản sẽ không có gì khác biệt nhiều đối với dạng yêu cầu xem nội dung của một trang đơn nào đó, bất kể đó là trang danh mục hay trang bài viết… Như vậy các tác vụ truy vấn dữ liệu cho mỗi thành phần trong giao diện cần trình bày sẽ có khả năng lặp lại rất cao; Mặc dù quá trình chuyển đổi từ dữ liệu truy vấn được sang kiểu dữ liệu phù hợp cho view có thể sẽ khác ở mỗi route.

Sẽ thật thuận tiện nếu như chúng ta phân tách các tác vụ nhỏ này thành các sub-procedure để sử dụng chung cho các route; Và đồng thời cung cấp các tham số tùy chọn để điều chỉnh phương thức hoạt động của mỗi sub-procedure này để phù hợp với từng route cụ thể. Như vậy chúng ta sẽ có thể tập trung code xử lý cho các route ở cùng một nơi, và có thể so sánh phương thức hoạt động ở các route dễ dàng hơn khi cần thực hiện bất kỳ thao tác điều chỉnh nào.

Cấu trúc lại code với các sub-procedure

[express-blog]
.  |
.  +-----[route]
.           |
.           +-----home.js
.           +-----[sub-procedure]
.                    |
.                    +-----get-data-for-meta.js
.                    +-----get-data-for-topnav.js
.                    +-----get-data-for-header.js
.                    +-----get-data-for-entry.js

Và đây là kết quả mà chúng ta hướng đến sau khi cấu trúc lại code.

const express =require("express");const Data =require("../view/type/Data");const getDataForMeta =require("./sub-procedure/get-data-for-meta");const getDataForTopnav =require("./sub-procedure/get-data-for-topnav");const getDataForHeader =require("./sub-procedure/get-data-for-header");const getDataForEntry =require("./sub-procedure/get-data-for-entry");const router = express.Router();

router.get("/",async(request, response)=>{var data =newData();awaitgetDataForMeta(data,"home");awaitgetDataForTopnav(data);awaitgetDataForHeader(data,"home");awaitgetDataForEntry(data,"home");

   response.render("index",{
      layout:"home",
      action:null,
      data
   });});// router.get

module.exports = router;

Ở đây các thao tác getDataForComponent sẽ lần lượt thực hiện truy vấn và chuyển đổi kiểu dữ liệu để cung cấp cho các thành phần giao diện và thêm vào map Data.

Ngoại trừ thanh điều hướng topnav, tất cả các thành phần khác khi xuất hiện trong các layout khác nhau và với tham số action khác nhau sẽ cần thực hiện thao tác truy vấn và chuyển đổi dữ liệu với logic khác nhau. Do đó chúng ta sẽ cần cung cấp các tham số tùy chọn là layoutaction trong phần định nghĩa của các sub-procedure để code sử dụng ở các route có thể điều chỉnh phương thức hoạt động của các sub-procedure.

get-data-for-meta

const Data =require("../../view/type/Data");const config =require("../../config");

module.exports =async(
   out_data =newData(),
   in_layout ="home"/* home | category | article | admin | oops */)=>{if(in_layout =="home")getMetaForHome(out_data);elsethrownewError("Unsupported layout");};// module.exportsconst getMetaForHome =(
   out_data =newData())=>{var logoText = config.get("logo-text");
   out_data.set("title",`${logoText} | Trang Chủ`);};

Đối với phần nội dung trong thẻ <head> thì hiện tại chúng ta chỉ có <title> thay đổi theo các trang đơn và bạn có thể bổ sung tham số action nếu cảm thấy cần thiết. Tạm thời thì chúng ta có thể xuất phát với khối module.exports đang hỗ trợ duy nhất layout: "home" như ở trên. Những layout khác sẽ được hỗ trợ bổ sung thêm khi chúng ta thực hiện tới.

get-data-for-topnav

const databaseManager =require("../../database/manager");const config =require("../../config");const Category =require("../../database/type/Category");const Data =require("../../view/type/Data");

module.exports =async(
   out_data =newData())=>{var logoText = config.get("logo-text");
   out_data.set("logo-text", logoText);var categoryList =[];await databaseManager.execute(
      Category.name,"select",
      categoryList,Infinity,"default",["@id","name"]);for(var category of categoryList){var id = category.get("@id");
      category.set("url",`/category/view/${id}`);}

   out_data.set("category-list", categoryList.slice(0,-1));};// module.exports

Riêng đối với thành phần topnav thì mình sử dụng thiết kế đơn giản và vì vậy không cần bổ sung thêm tham số tùy chọn nào cả. Trong trường hợp muốn xác định trang đơn đang hiển thị thuộc danh mục nào để tạo hiệu ứng hiển thị cho liên kết tương ứng thì chúng ta sẽ cần thêm cả hai tham số hỗ trợ layoutaction.

get-data-for-header

const Data =require("../../view/type/Data");const config =require("../../config");

module.exports =async(
   out_data =newData(),
   in_layout ="home",/* home | category | oops */
   in_categoryId ="Infinity")=>{if(in_layout =="home")awaitgetHeaderForHome(out_data);elsethrownewError("Unsupported layout");};// module.exportsconst getHeaderForHome =async(
   out_data =newData())=>{
   out_data
      .set("heading", config.get("site-heading")).set("description", config.get("site-description"));};// getHeaderForHome

Khối header được sử dụng lại cho các layout là trang chủ home và các trang đơn danh mục category. Trong trường hợp là một trang đơn hiển thị danh mục, chúng ta cần xác định thông tin về danh mục đó để hiển thị, và vì vậy sẽ cần cung cấp thêm tham số categoryId.

get-data-for-entry

const databaseManager =require("../../database/manager");const Article =require("../../database/type/Article");const Data =require("../../view/type/Data");const Entry =require("../../view/type/Entry");

module.exports =async(
   out_data =newData(),
   in_layout ="home"/* home | category | admin */)=>{if(in_layout =="home")awaitgetEntryListForHome(out_data);elsethrownewError("Unsupported Layout");};// module.exportsconst getEntryListForHome =async(
   out_data =newData())=>{var entryList =[];var articleList =[];await databaseManager.execute(
      Article.name,"select",
      articleList,10,"reversed",["@id","title","content"]);for(var article of articleList.slice(1)){var entry =newEntry().set("title", article.get("title")).set("excerpt", article.get("content")).set("url",`/article/view/${article.get("@id")}`);

      entryList.push(entry);}// for .. of

   out_data.set("entry-list", entryList);};// getEntryListForHome

Khối entry được hiển thị các trích đoạn ngắn và liên kết tới các bài viết tương ứng được sử dụng cho trang chủ home, các trang đơn danh mục category, và trong trường hợp hiển thị trong một trang quản trị admin thì các liên kết sẽ được sử dụng để trỏ tới /article/edit/:id để mở giao diện chỉnh sửa bài viết.

Kết thúc bài viết

Như vậy là chúng ta đã thực hiện xong việc tái cấu trúc lại code xử lý cho route cơ bản đầu tiên và chuẩn bị cho khối route/article. Trong bài viết tiếp theo, chúng ta sẽ viết code cho route đầu tiên của khối này để xử lý yêu cầu xem nội dung của một bài viết.

(Sắp đăng tải) [ExpressJS] Bài 9 – Viết Code Điều Hành Blog Cá Nhân (Tiếp Theo)

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ