了解です。
では、前回の レスポンシブ対応(PCホバー/スマホクリック)多階層ドロップダウンメニュー を、
さらに 現在のページURLに基づくアクティブメニュー自動ハイライト機能 を追加して再生成します。
これにより、現在表示しているページに対応するメニュー項目が自動的に強調表示されます。
1. データベース構造(同じ)
CREATE TABLE Menus (
Id INT PRIMARY KEY IDENTITY,
Title NVARCHAR(100) NOT NULL,
Url NVARCHAR(200) NULL,
ParentId INT NULL,
FOREIGN KEY (ParentId) REFERENCES Menus(Id)
);
2. モデルと DbContext
// Models/Menu.cs
using System.Collections.Generic;
public class Menu
{
public int Id { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public int? ParentId { get; set; }
public Menu Parent { get; set; }
public ICollection<Menu> Children { get; set; } = new List<Menu>();
}
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
public DbSet<Menu> Menus { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Menu>()
.HasOne(m => m.Parent)
.WithMany(m => m.Children)
.HasForeignKey(m => m.ParentId)
.OnDelete(DeleteBehavior.Restrict);
}
}
3. メニュー取得サービス
// Services/MenuService.cs
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public interface IMenuService
{
Task<List<Menu>> GetMenuHierarchyAsync();
}
public class MenuService : IMenuService
{
private readonly ApplicationDbContext _context;
public MenuService(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<Menu>> GetMenuHierarchyAsync()
{
var allMenus = await _context.Menus
.AsNoTracking()
.ToListAsync();
return BuildHierarchy(allMenus, null);
}
private List<Menu> BuildHierarchy(List<Menu> allMenus, int? parentId)
{
return allMenus
.Where(m => m.ParentId == parentId)
.Select(m => new Menu
{
Id = m.Id,
Title = m.Title,
Url = m.Url,
ParentId = m.ParentId,
Children = BuildHierarchy(allMenus, m.Id)
})
.ToList();
}
}
4. ViewComponent
// ViewComponents/MenuViewComponent.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
public class MenuViewComponent : ViewComponent
{
private readonly IMenuService _menuService;
public MenuViewComponent(IMenuService menuService)
{
_menuService = menuService;
}
public async Task<IViewComponentResult> InvokeAsync()
{
var menuHierarchy = await _menuService.GetMenuHierarchyAsync();
return View(menuHierarchy);
}
}
5. ViewComponent のビュー(レスポンシブ + アクティブハイライト対応)
Views/Shared/Components/Menu/Default.cshtml
@model List<Menu>
@{
// 現在のURLパス(クエリ除去)
var currentPath = Context.Request.Path.Value?.TrimEnd('/').ToLower() ?? "/";
}
@functions {
void RenderMenuItems(List<Menu> menus, bool isSubMenu = false)
{
foreach (var menu in menus)
{
bool isActive = !string.IsNullOrEmpty(menu.Url) &&
currentPath == menu.Url.TrimEnd('/').ToLower();
if (menu.Children.Any())
{
var liClass = isSubMenu ? "dropdown-submenu" : "nav-item dropdown";
var aClass = isSubMenu ? "dropdown-item dropdown-toggle" : "nav-link dropdown-toggle";
if (isActive) aClass += " active";
<li class="@liClass">
<a class="@aClass" href="@(!string.IsNullOrEmpty(menu.Url) ? menu.Url : "#")" id="menu-@menu.Id" role="button" data-bs-toggle="dropdown" aria-expanded="false">
@menu.Title
</a>
<ul class="dropdown-menu" aria-labelledby="menu-@menu.Id">
@RenderMenuItems(menu.Children.ToList(), true)
</ul>
</li>
}
else
{
if (isSubMenu)
{
<li><a class="dropdown-item @(isActive ? "active" : "")" href="@menu.Url">@menu.Title</a></li>
}
else
{
<li class="nav-item"><a class="nav-link @(isActive ? "active" : "")" href="@menu.Url">@menu.Title</a></li>
}
}
}
}
}
<ul class="navbar-nav flex-grow-1">
@RenderMenuItems(Model)
</ul>
<style>
/* 多階層ドロップダウンの位置調整 */
.dropdown-submenu {
position: relative;
}
.dropdown-submenu > .dropdown-menu {
top: 0;
left: 100%;
margin-top: -0.5rem;
}
/* PC時のみホバー展開 */
@media (min-width: 992px) {
.dropdown:hover > .dropdown-menu {
display: block;
}
.dropdown-submenu:hover > .dropdown-menu {
display: block;
}
}
/* アクティブメニュー強調 */
.nav-link.active, .dropdown-item.active {
background-color: #0d6efd;
color: #fff !important;
}
</style>
<script>
// スマホではクリック展開を維持(Bootstrap標準動作)
document.addEventListener('DOMContentLoaded', function () {
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
dropdownToggles.forEach(function (toggle) {
toggle.addEventListener('click', function (e) {
if (window.innerWidth >= 992) {
e.preventDefault(); // PCではクリックリンク無効化(ホバーで開くため)
}
});
});
});
</script>
6. _Layout.cshtml に組み込み
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
@await Component.InvokeAsync("Menu")
</div>
7. Program.cs でサービス登録
builder.Services.AddScoped<IMenuService, MenuService>();
改良ポイント
- アクティブメニュー自動ハイライト
現在のURLと一致するメニュー項目に active クラスを付与 - レスポンシブ対応
PCではホバー展開、スマ
Powered by Froala Editor