1. 前提条件
-
DBおよびWebプロジェクトの構築が完了していることが前提です。もし構築がまだ完了していない場合は、以下の記事を参照してください。
-
今回の実装では、メニューの表示のみです。事前にSpringからMybatisを使用したCRUDの基本実装が理解されていること。
-
また、メニューの実装にはjquery用のプラグインであるjsTreeを採用しております、jsTreeの知識をある程度理解されていること。
-
OracleのSTART WITH ... CONNECT BYを使いこなせること。
DBの構築手順:
WEBプロジェクトの構築手順:
CRUDの基本実装:
このシリーズの完全版はこちら:
jsTreeの公式サイト:
この記事のソースコードはGitHubにアップロードしましたので、ぜひダウンロードして確認してください。
出来上がったメニューのイメージ:
2. データモデルの設計
テーブル設計の正規化原則に従って、メニュー関連テーブルは以下のように設計する。Demo用のDDLとDMLはgithubに上げてるのでぜひご参考して下さい。
後ほどSTART WITH ... CONNECT BYを利用するため、テーブル構造を十分に理解して下さい。
2.1.メニューの基本情報テーブル
2.2.カテゴリ/サブカテゴリ(メニュー/サブメニュー)テーブル
2.3.メニュー&カテゴリ関係テーブル
2.4.アイコンテーブル
2.5.ユーザーテーブル
2.6.ロールテーブル
2.7.ロールメニュー関係テーブル
各テーブルの説明は省略します。
3. メニューの実装、ソースの解説
プログラムの構成です、赤い線のファイルは今回追加したものです。
代表ソースだけを解説します。
3.1.PomにjsTreeライブラリを追加してください。
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jstree</artifactId>
<version>3.3.12</version>
</dependency>
3.2.UserMenuRepository.xmlを実装
ログインユーザーの権限により、見えるメニューだけ抽出するSQL文及び項目変換ソースを貼ります。
select id="selectMenuByUserID":jsTreeに必要な項目を抽出するSQL文。UNIONで結合された上半分がカテゴリ、下半分が機能。START WITH ... CONNECT BY ... を使ってツリーメニューのデータを抽出している。
#{userID}:パラメータ。ログインユーザーID。
com.example.demo.bean.TreeMenu:jsTree用ツリーメニューの項目Bean。
resultMap id="ResultTreeMenuMap":DB項目とjsTreeの項目の変換関係。
<resultMap id="ResultTreeMenuMap" type="com.example.demo.bean.TreeMenu">
<id column="CATEGORY" jdbcType="VARCHAR" property="id" />
<result column="CATEGORY_NAME" jdbcType="VARCHAR" property="text" />
<result column="CATEGORY_PID" jdbcType="VARCHAR" property="parent" />
<result column="POSITION" jdbcType="VARCHAR" property="position" />
<result column="ACTION" jdbcType="VARCHAR" property="action" />
<result column="ICON_TYPE" jdbcType="VARCHAR" property="iconType" />
<result column="ICON_IMAGE" jdbcType="VARCHAR" property="icon" />
<result column="TYPE" jdbcType="VARCHAR" property="type" />
<result column="LEVEL" jdbcType="VARCHAR" property="level" />
</resultMap>
<select id="selectMenuByUserID" parameterType="java.lang.String" resultMap="ResultTreeMenuMap">
SELECT
T.CATEGORY,
T.CATEGORY_NAME,
T.CATEGORY_PID,
T.POSITION,
T.ACTION,
T.ICON_TYPE,
T.ICON_IMAGE,
T.TYPE,
LEVEL
FROM
(
SELECT
C.CATEGORY,
C.CATEGORY_NAME,
C.CATEGORY_PID,
C.POSITION,
'#' ACTION,
'01' ICON_TYPE,
I.ICON_IMAGE,
'C' TYPE
FROM
CATEGORY C
LEFT JOIN ICON I ON '01' = I.ICON_TYPE
UNION
SELECT
RM.MENU_ID,
M.MENU_NAME,
MC.CATEGORY,
M.POSITION,
NVL(M.ACTION,'#') ACTION,
M.ICON_TYPE,
I.ICON_IMAGE,
'M' TYPE
FROM
USERS U
JOIN ROLE_MENU RM ON U.ROLE = RM.ROLE
JOIN MENU M ON RM.MENU_ID = M.MENU_ID
JOIN MENU_CATEGORY MC ON M.MENU_ID = MC.MENU_ID
JOIN CATEGORY C ON MC.CATEGORY = C.CATEGORY
LEFT JOIN ICON I ON M.ICON_TYPE = I.ICON_TYPE
WHERE
U.USER_ID = #{userID}
AND M.IS_AVAILABLE = '1'
) T
START WITH
T.CATEGORY_PID = '#'
CONNECT BY
PRIOR T.CATEGORY = T.CATEGORY_PID
ORDER SIBLINGS BY
POSITION
</select>
3.3.TreeMenu.javaを実装
jsTree用ツリーメニューの項目を説明します。
private String id; // メニューID
private String parent; // 親メニューID
private String text; // メニュー名
private String iconType; // メニューアイコンコード
private String icon; // メニューアイコンのパス
private String position; // メニュー表示順番
private String action; // メニューURL、jsTree用項目ではない
private String target; // URLを開くフレームやウインドウを指定する
private String type; // C:カテゴリ;M:メニュー、jsTree用項目ではない
private String level; // メニューの階層、jsTree用項目ではない
private Map<String,String> a_attr; // actionのURLを格納する
3.4.MenuController.javaを実装
List<TreeMenu> treeMenu = userService.selectMenuByUserID(userID);
// SQLから取得したTreeMenuデータは、そのままではjsTreeでの使用に適していません。以下のような加工が必要です。
treeMenu.forEach(menu -> {
Map<String, String> href = new HashMap<>();
href.put("href", menu.getAction());
menu.setA_attr(href); // jsTreeの中に<A>を生成するため
});
model.addObject("treeMenu", treeMenu);
3.5.menu.htmlを実装
jquery及びjstreeのリソースをリンクする
<link rel="stylesheet" type="text/css" th:href="@{/webjars/jstree/3.3.12/themes/default/style.min.css}">
<script type="text/javascript" th:src="@{/webjars/jquery/3.6.0/dist/jquery.min.js}"></script>
<script type="text/javascript" th:src="@{/webjars/jstree/3.3.12/jstree.min.js}"></script>
ツリーメニューを生成する
$(function(){
$('#treeMenu')
// listen for event
.on('changed.jstree', function (e, data) {
console.log(data.changed.selected);
console.log(data.changed.deselected);
var obj = data.instance.get_node(data.selected[0]);
if(obj && data.instance.is_leaf(obj) && "#" != obj.a_attr.href){
window.open(obj.a_attr.href, obj.id);// メニューをクリックする時、新たなWindowでURLを開く
} else {// カテゴリをクリックする時、カテゴリをオープン/クローズ
if(data.instance.is_open(obj)){
data.instance.close_node(obj);
} else {
data.instance.open_node(obj);
}
}
})
// create the instance
.jstree({
'core' : {
"animation" : 0,
"check_callback" : true,
"themes" : { "stripes" : true },
'data' : /*[[${treeMenu}]]*/
},
"plugins" : [
"changed", "state", "types", "wholerow"
]
});
});
jstreeの使い方についての説明は今回省けます、興味がある方は公式サイトをご参考下さい。
4. 完成
下図は以下のURLを叩いた結果です。
左(ADMIN権限):http://localhost:8000/menu?userID=ADMIN
中(管理職権限):http://localhost:8000/menu?userID=DEMO001
右(スタッフ):http://localhost:8000/menu?userID=DEMO002
まだ改善の余地がありますが(例えば空のカテゴリを表示しないなど)、とりあえず権限に基づいてメニューが表示されるようになりました。
おわりに
全体のソースコードはGitHubにアップロードしましたので、ぜひダウンロードして確認してください。
また、この記事では一部のソースコードしか説明していませんが、説明を希望する特定の部分があればリクエストしていただければと思います。詳細や特定の質問についてお知らせいただければ、それに基づいて訂正や説明を提供できます。
次回はメイン画面(フレーム分割) を実装していきたいと思います、お楽しみして待ってください。
それではまた。