介紹上回提到的 ASP.NET Core Razor Page 下拉選單連動寫法。如果你對 Razor Page 還沒有概念,推薦幾篇文章:

回到主題,目標是要寫出類似以下的下拉選單連動效果。每個主分類有對應的次分類,例如:主分類選「系統開發」,次分類會切換成系統清單;主分類為「請假」時,次分類則換成特休、事假、病假... 等項目。

連動式下拉選單的前端做法很多,但這次我不想不打算動用 Vue.js、Angular 之類的前端框架,計劃對 Razor Page 編輯介面範本簡單加工,靠幾行 jQuery 把它寫出來就好。

在 Visual Studio 點 CRUD 海陸豪華餐自動產生的主分類、次分類欄位原本長這樣:

<div class="form-group">
    <label asp-for="TimeSheetEntry.Category" class="control-label"></label>
    <input asp-for="TimeSheetEntry.Category" class="form-control" />
    <span asp-validation-for="TimeSheetEntry.Category" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="TimeSheetEntry.SubCategory" class="control-label"></label>
    <input asp-for="TimeSheetEntry.SubCategory" class="form-control" />
    <span asp-validation-for="TimeSheetEntry.SubCategory" class="text-danger"></span>
</div>    

在前端會以 <input type="text"> 形式呈現,而我們想把兩個欄位改成連動式下拉選單。主分類部分比較簡單,將 input 改成 select,再加上 asp-items="SelectList 物件" 就可以了,在 ASP.NET Core CRUD 傻瓜範例 (4) - 介面客製調整我有試過呼叫 Html.GetEnumSelectList<CRUDExample.Models.StatusFlags>() 產生 asp-items 需要的 SelectList 物件,這次用的分類資料來自上回文章寫好的 FileDataStore,new SelectList(IEnumerable 物件) 即可產生 asp-items 需要的資料來源,我的分類選項 option value 與 text 相同,傳入 string[] 就好,SelectList 還有其他建構式可以指定 value、text 對應屬性。至於次分類由於要在主分類切換時改變選項清單,稍稍複雜一點,要將儲存次分類值的欄位改成 <input type="hidden" >,另外新增 select。於是 .cshtml 的兩個 input 改寫如下:

<div class="form-group">
    <label asp-for="TimeSheetEntry.Category" class="control-label"></label>
    <select asp-for="TimeSheetEntry.Category" asp-items="Model.CategoryList" class="form-control">
    </select>
    <span asp-validation-for="TimeSheetEntry.Category" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="TimeSheetEntry.SubCategory" class="control-label"></label>
    <select id="SubCategory" name="SubCategory" class="form-control">
    </select>
    <input type="hidden" asp-for="TimeSheetEntry.SubCategory" class="form-control" />
    <span asp-validation-for="TimeSheetEntry.SubCategory" class="text-danger"></span>
</div>

連動部分這次我是用 jQuery 寫,主要在主分類下拉選單 change 事件加上邏輯,發出 AJAX 從後端取得該主分類對映的次分類清單,產生次分類下拉選項,若次分類欄位已有值,則要比對符合選項標為己選取,才能反映現有資料狀態。次分類下拉選單的 change 事件則要將選取結果填入次分類 hidden 欄位:

var subCatgSelector = $("#SubCategory");
$("#TimeSheetEntry_Category").change(function () {
    $.getJSON("?handler=SubCategories&category=" + $(this).val()).done(function (options) {
        subCatgSelector.empty();
        var currVal = $("#TimeSheetEntry_SubCategory").val();
        for (var i = 0; i < options.length; i++) {
            var option = options[i];
            var opEl = $("<option></option>").attr("value", option).text(option);
            if (currVal == option) opEl.attr("selected", "selected");
            opEl.appendTo(subCatgSelector);
        }
    });
}).change();
subCatgSelector.change(function () {
    $("#TimeSheetEntry_SubCategory").val($(this).val());
});

後端部分,主要是在建構式從 DI 取得 FileDataStore 讀取主分類 string[] 及次分類 Dictionar<string, string[]>,並寫了一個 OnGetSubCategories 供前端 AJAX 呼叫傳入主分類查對映次分類項目:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using PTSWeb.Models.Data;
using PTSWeb.Models.Entities;
using PTSWeb.Models.Page;

namespace PTSWeb.Pages.Entry
{
    public class CreateModel : PageModelBase
    {
        private readonly PTSWeb.Models.Data.PtsDbContext _context;
        private readonly FileDataStore fileStore;

        public CreateModel(PTSWeb.Models.Data.PtsDbContext context, FileDataStore fileStore)
        {
            _context = context;
            this.fileStore = fileStore;
            Categories = fileStore.Categories;
            SubCategories = fileStore.SubCategories;
        }

        public IActionResult OnGet()
        {
            TimeSheetEntry = new TimeSheetEntry() { UserId = this.User.Identity.Name.Split('\\').Last() };
            return Page();
        }


        [BindProperty]
        public TimeSheetEntry TimeSheetEntry { get; set; }

        public string[] Categories { get; set; } 
        public Dictionary<string, string[]> SubCategories { get; set; }

        public SelectList CategoryList => new SelectList(Categories);
        public JsonResult OnGetSubCategories(string category)
        {
            return new JsonResult(this.SubCategories[category]);
        }


        // To protect from overposting attacks, please enable the specific properties you want to bind to, for
        // more details see https://aka.ms/RazorPagesCRUD.
        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Entries.Add(TimeSheetEntry);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

這樣子,個簡易版的 Razor Page 下拉選單連動就完成囉。

Tutorial of using Razor Page and jQuery to build cascading drop-down lists.


Comments

Be the first to post a comment

Post a comment