返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
StarBlog博客开发笔记

StarBlog Blog Development Notes (8): Displaying Category Hierarchy

Series articles Developing a Blog Project with .NetCore StarBlog - (1) Why Do You Need to Write Your Own Blog? Developing a Blog Project with .NetCore StarBlog - (2) Environment Setup and Creating the Project Developing a Blog Project with .NetCore StarBlog - (3) Model Design Developing a Blog Project with .NetCore StarBlog - (4) Batch Importing Markdown Blog Posts Developing a Blog Project with .NetCore StarBl

曦远 (DealiAxy)约 31 分钟
目录8 个章节

Series Articles

Preface

The previous article (6) Page Development: Blog Post List covered the development of the post list. On the page, the left side is the category list and the right side shows the posts under that category. This layout looks decent at first glance, but considering that this project supports multi-level categories, the category list simply displays all categories mechanically, which cannot reflect the category hierarchy and takes up a lot of vertical page space. Therefore, this article will refactor the category list so that it can represent multi-level categories and save page space.

Regarding tree structure components, I looked around, and components that work with bootstrap (based on jQuery) are hard to find; most of them are very old. I only found bootstrap-treeview, which is a bit more usable. Looking at its GitHub project homepage, it similarly hasn't been updated for a long time. It even targets bootstrap version 3.x. It's already 2022 now, and bootstrap has been updated to version 5.x, but I couldn't find anything better, so I'll make do with it~ (If it really doesn't work I can still clone its code and hack it)

Installation

This component is fairly old

It depends on bower. If you don't have bower, you need to install it first

npm install -g bower

Then run the following command in the StarBlog.Web directory to install the dependency

npm install bootstrap-treeview

Because all our static assets are under wwwroot, frontend resources installed via npm still need to be automatically copied into wwwroot using the gulp tool. This was covered in a previous article. If you've forgotten, you can check this earlier post: Developing a Blog Project with .NetCore StarBlog - (5) Starting to Build the Web Project

Edit the gulpfile.js file and add a line in the const libs configuration

//使用 npm 下载的前端组件包
const libs = [
    // ...
    {name: "bootstrap-treeview", dist: "./node_modules/bootstrap-treeview/dist/**/*.*"},
];

Then just run the gulp task

gulp move

After completion, you can see that a bootstrap-treeview directory has been added under wwwroot/lib

Next we can reference it in the page

Usage

Before we officially start, let's first understand how this component is used

Include the dependencies

<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap-treeview/dist/bootstrap-treeview.min.js"></script>

Place a container in the web page

<div id="categories">

According to the official example, activate the component with js

const instance = $('#categories').treeview({
    data: collections,
});

The format of collections is as follows

const collections = [
    {
        text: 'Parent 1',
        href: '#parent1',
        nodes: [
            {
                text: 'Child 1',
                href: '#child1',
                nodes: [
                    {
                        text: 'Grandchild 1',
                        href: '#grandchild1',
                    },
                    {
                        text: 'Grandchild 2',
                        href: '#grandchild2',
                    }
                ]
            },
            {
                text: 'Child 2',
                href: '#child2',
            }
        ]
    },
    {
        text: 'Parent 2',
        href: '#parent2',
    },
    {
        text: 'Parent 3',
        href: '#parent3',
    },
    {
        text: 'Parent 4',
        href: '#parent4',
    },
    {
        text: 'Parent 5',
        href: '#parent5',
    }
];

The default effect from the official site

image-20220524160344748

However, after my testing, this official example has some issues under bootstrap5 — the default icons don't display at all. We need to customize it a bit by adding icon configuration. The icons used are the FontAwesome Icons we installed earlier

const instance = $('#categories').treeview({
    data: collections,
    collapseIcon: "fa fa-caret-down",
    expandIcon: "fa fa-caret-right",
    emptyIcon: 'fa fa-circle-o',
});

Processing Category Data

To make it convenient to use this component, we need to wrap the category hierarchy on the backend into the form this component requires.

First, define a node class

public class CategoryNode {
    public string text { get; set; } = "";
    public string href { get; set; } = "";
    public List<CategoryNode>? nodes { get; set; }
}

Then add a new method in Services/CategoryyService.cs to generate the category tree structure. For convenience of writing the code, I implemented it directly with recursion.

public List<CategoryNode>? GetNodes(int parentId = 0) {
    var categories = _cRepo.Select
        .Where(a => a.ParentId == parentId).ToList();

    if (categories.Count == 0) return null;

    return categories.Select(category => new CategoryNode {
        text = category.Name,
        nodes = GetNodes(category.Id)
    }).ToList();
}

The output data looks like this

[
    {
      "text": "Android开发",
      "href": "",
      "nodes": null
    },
    {
      "text": "AspNetCore",
      "href": "",
      "nodes": [
        {
          "text": "Asp-Net-Core学习笔记",
          "href": "",
          "nodes": null
        },
        {
          "text": "Asp-Net-Core开发笔记",
          "href": "",
          "nodes": null
        }
      ]
    }
]

Oh, almost forgot we still need to add the href parameter to each node

Hardcoding is out of the question. The ControllerBase instance comes with a Url property of type IUrlHelper by default, which can be used with its Link() method to resolve address routing.

However, this method is written in the Service and there is no ControllerBase instance, so in this case we can only use dependency injection. But I saw a statement on Stack Overflow that after AspNetCore 3.x, using LinkGenerator is better.

Here's the code. First register the service

builder.Services.AddHttpContextAccessor();

Then dependency injection

private readonly IHttpContextAccessor _accessor;
private readonly LinkGenerator _generator;

public CategoryService(IHttpContextAccessor accessor, LinkGenerator generator) {
    _accessor = accessor;
    _generator = generator;
}

Modify the GetNodes method above, and add this in the CategoryNode initializer

href = _generator.GetUriByAction(
    _accessor.HttpContext!,
    nameof(BlogController.List),
    "Blog",
    new {categoryId = category.Id}
)

The specific code can be seen on GitHub: https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Services/CategoryService.cs

The generated link looks like this:

{
    "text": "Android开发",
    "href": "http://localhost:5038/Blog/List?categoryId=2",
    "nodes": null
}

Frontend Rendering

The data is ready. Now we run into a problem: the data needs to be processed in js, so should I use an async request like fetch to get the category data and then display the tree categories? The benefit of this is that it's more straightforward to write. However, our project's blog site is server-side rendered. Mixing async requests into the blog list page now would cause a sense of disconnection — the post list on the right is server-rendered and displayed in the browser, while the categories on the left still need to be requested asynchronously.

After some thought, I decided that this category list should also use server-side rendering. Although it's a bit counterintuitive, according to the bootstrap-treeview component documentation, it can render categories using json. So I just need to serialize the category data into json format on the backend, then render it into the js code in the view.

Let's get started~

Edit the StarBlog.Web/ViewModels/BlogListViewModel.cs file and add two fields

public List<CategoryNode> CategoryNodes { get; set; }
// 将上面的分类层级数据转换成Json字符串
public string CategoryNodesJson => JsonSerializer.Serialize(
    CategoryNodes,
    new JsonSerializerOptions {Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping}
);

Then modify the Controller, StarBlog.Web/Controllers/BlogController.cs. First inject CategoryService

Then modify the List method

public IActionResult List(int categoryId = 0, int page = 1, int pageSize = 5) {
    var categories = _categoryRepo.Where(a => a.Visible)
        .IncludeMany(a => a.Posts).ToList();
    categories.Insert(0, new Category {Id = 0, Name = "All", Posts = _postRepo.Select.ToList()});

    return View(new BlogListViewModel {
        CurrentCategory = categoryId == 0 ? categories[0] : categories.First(a => a.Id == categoryId),
        CurrentCategoryId = categoryId,
        Categories = categories,
        // 增加这一行
        CategoryNodes = _categoryService.GetNodes(),
        Posts = _postService.GetPagedList(new PostQueryParameters {
            CategoryId = categoryId,
            Page = page,
            PageSize = pageSize,
            OnlyPublished = true
        })
    });
}

The last step is to modify the View, StarBlog.Web/Views/Blog/List.cshtml, adding JS references and some JS code at the bottom. I've already wrapped the treeview component configuration into an initTreeView method, which can be used directly.

@section bottom {
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap-treeview/dist/bootstrap-treeview.min.js"></script>
    <script src="~/js/blog-list.js"></script>
    <script>
        const categories = '@Html.Raw(Model.CategoryNodesJson)'
        initTreeView(categories);
    </script>
}

The key code in the View is just these few lines. The complete code can be found at: https://github.com/Deali-Axy/StarBlog/blob/master/StarBlog.Web/Views/Blog/List.cshtml

Final Result

The final result after completion is as follows. Category hierarchy is now supported, but it's still not perfect—there are a few issues:

  • Cannot highlight the currently selected category
  • Category post counts are not displayed (the previous version had this)
  • Cannot customize the list-group-item style; the underlines look unattractive
  • ...

I'll leave these issues for later optimization~ That's enough tinkering for now…

image-20220524230203064

Development of the blog project is basically complete. The project code is fully open source—if you're interested, feel free to give it a star~

References

_EOF_

StarBlog Blog Development Notes (8): Displaying Category Hierarchy

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

StarBlog博客开发笔记(1):项目介绍

前言 OK,我也来造轮子了 博客系统从一开始用WordPress,再到后来用hexo、hugo之类的静态博客生成放github托管,一直在折腾 折腾是为了更好解决问题,最终还是打算自己花时间搞一个好了 本系列文章将记录博客的开发过程~ 后端基于最新的.Net6和Asp.Net Core框架,遵循RESTFul接口规范,前端基于Vue+ElementUI开发,可作为 .Net Core 入门项目学习

2022-04-05 · StarBlog博客开发笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…