NGW開発BLOG
← 記事一覧へ
·#042

メニューを、データで管理するアプリメニューと NGWT_APP_MENU の JSON

NGWはアプリごとに専用のメニュー体系を出す。40超のアプリに40通りのメニュー。それをHTMLにベタ書きせず、NGWT_APP_MENU テーブルの1列にJSON配列として持つ。起動可能アプリは職員区分ごとに NGWT_BOOT_MANAGER で管理し、メニューで隠し起動で防ぐ。データで持つから、並べ替えも権限の出し分けも自治体ごとの差し替えも、デプロイなしでできる。

アプリの数だけ、メニューがある

NGWは、いま開いているアプリごとに、そのアプリ専用のメニューを画面上部に出す。収受文書を開けば「収受文書を開く/印刷/文書管理カート/添付書類結合…」が並び、会議録を開けば別の顔ぶれになる。文書管理システムだけでも登録アプリは40を超える。つまり、アプリの数だけメニュー体系がある。

文書管理システムのアプリの並び。HOME・環境設定・収受文書・起案文書・会議録・公示令達・棚管理・簿冊管理・簿冊関連・引継廃棄・書庫管理・決裁状況・文書検索の各アプリがアイコンで横に並ぶ

文書管理システムのアプリの並び。HOME・収受文書・起案文書・会議録・公示令達・簿冊管理・書庫管理・決裁状況・文書検索…と、アプリごとにアイコンが並ぶ。このどれかひとつを選ぶと、そのアプリ専用のメニューが画面上部に出る。

これを画面ごとにHTMLでベタ書きしていたら、どうなるか。アプリをひとつ足すたびにメニューのHTMLを書き、並べ替えのたびにHTMLをいじり、「このボタンは管理者だけ」「この自治体では名前が違う」が全部コードに散らばる。40アプリぶん、それが積み上がる。

NGWは、メニューをHTMLではなくデータとして持つことで、これを避けている。

メニューは、JSONで持つ

置き場所は NGWT_APP_MENU テーブル。1アプリ = 1行だ。行にはアプリ名・起動PHP・アイコン・並び順(ORDER_NO)・有効フラグ(ENABLE)などが入る。そして肝心のメニュー項目は、その行の NGWT_APP_MENU_EVENT_JSON 列に、JSON配列として畳まれている。

収受文書アプリなら、中身はおおよそこうだ。

[
  { "NAME": "収受文書を開く", "MODAL": "MENU", "ACTIVE_EVENT": "ALWAYS",
    "FUNC": "InitListColumnDocuBunsho(frmNgw)", "ICON": "far fa-folder-open" },
  { "NAME": "印刷", "MODAL": "MENU", "ACTIVE_EVENT": "ALWAYS",
    "FUNC": "", "ICON": "fal fa-print" },
  { "NAME": "文書管理カート", "MODAL": "MENU",
    "ACTIVE_EVENT": "SELECT+,SELECT_NOT_UPDATE+",
    "FUNC": "ajaxNgwFunc_MenuJumpBlank(frmNgw, '/ngw/docu/...')",
    "ICON": "fal fa-credit-card" }
]

配列の1要素が、メニューの1項目。キーはこう読む。

  • NAME … 表示名。「簿冊」「棚」のような自治体ごとの呼称は、表示するときに差し替える(後述)。
  • ICON … アイコン。Font Awesome のクラス(far fa-folder-open など)をそのまま持つ。
  • FUNC … クリックで動く処理。そのアプリ内の関数InitListColumnDocuBunsho(frmNgw))を呼ぶか、ajaxNgwFunc_MenuJump(...)別画面へ飛ぶか。メニューの「行き先」がこの一文字列で決まる。
  • MODAL … 項目の種別MENU(ふつうのボタン)/SPACER(間隔)/DROPDOWN(ドロップダウン)/LAYERMENU(階層メニュー)/COLORBOX_MODAL(モーダルで開く)/ADMIN_MENU(管理者だけ)…。キー名は経緯から MODAL のままだが、いま入っているのは表示の型だ——こういう化石の名前が残るのも、10年動くシステムの正直なところである。
  • ACTIVE_EVENT … このボタンが押せる(active)になる画面状態ALWAYS(常時)/SELECT+(一覧で行を選んだとき)/SELECT_NOT_UPDATE+(未更新の行を選んだとき)…をカンマで並べる。「どの状態でどのボタンが効くか」を、コードでなくデータで宣言する。

サーバで組み、器に流す

描画は、アプリの仕組みで書いたSubModeの型そのままだ。

クライアントの ajaxNgwFunc_MakeAppMenu が、&SubMode=AppMenu を付けてPOSTする。サーバの ajaxNgwFunc.php はそれを受け、fncMakeAppMenuNGWT_APP_MENU の行を引き、JSONを

$menuArray = json_decode($valNgwtAppMenuEventJson, true);
foreach ($menuArray as $menu) { /* <li>ボタンを組み立てる */ }

とほどいて、<li> のボタン列を組み上げて返す。返ってきたHTMLは、クライアントが $("#AppMenu").html(result) で流し込む。

——この #AppMenu前回の個人設定の記事で見た画面PHPに、<ul id="AppMenu" class="app-menu"></ul> という空っぽの器として置いてあったものだ。どのアプリ画面にも同じ空の棚があり、そこへ、そのアプリ用のメニューがJSONから組まれて載る。

データだから、できること

メニューをデータに追い出すと、こういうことが「デプロイなし」でできるようになる。

  • 増減・並べ替え・改名 … アプリを足す、ボタンを1つ増やす、順番を変える、名前を直す——行とJSONを編集するだけ。PHPを触らない。
  • 権限での出し分け … メニュー生成のSQLは FIND_IN_SET(NGWT_APP_MENU_ID, '$BootAppCsv') で、その職員が起動できるアプリだけに絞る。見せてよいものだけがメニューに出る。可視性を権限で決めるのは、プライバシー保護の記事と同じ発想だ。
  • 状態での活性制御 … 「行を選ぶまで“カートに入れる”は押せない」を、ACTIVE_EVENT に書いておくだけで効く。
  • 用語の方言 … 「簿冊」「棚」といった呼称を、その自治体の言い方へ表示時に差し替える。中身のコードは共通のまま、見え方だけ地域に合わせる。
  • 自治体スペシャル … 同じメニュー項目でも、自治体番号付きの専用PHP(docuKessaiKeiro47000.php のような)が存在すればfile_exists で見つけてそちらを起動する。基本は全自治体で共通、必要な自治体だけそっと差し替え——前回の「みんなの既定+ひとりの上書き」と、まったく同じ形だ。

そのCSVは、誰が決めるのか — 起動管理

さきほどさらっと出てきた BootAppCsv。あれは「その職員が起動してよいアプリのID一覧(CSV)」だが、では誰がそれを決めるのか。担っているのが ngwtBootManager.php(APP起動管理)と、その裏の NGWT_BOOT_MANAGER テーブルだ。

このテーブルは職員区分(役職)ごとに1行を持つ。会計年度任用職員・職員・係長・課長…という区分それぞれに、「起動してよいアプリ」のチェックが並び、そのチェックの集合が BOOT_APP_CSVNGWT_APP_MENU_ID のCSV)になる。

アプリ起動管理の画面。左は職員区分(職員)が起動できるアプリのチェック一覧、右は職員区分ごとの起動制限(会計年度任用職員は11業務に制限、職員・係長は48業務で制限なし、課長は47業務)

アプリ起動管理(`ngwtBootManager.php`)。左で職員区分ごとに起動できるアプリをチェックし、その集合が `BOOT_APP_CSV` になる。右は区分ごとの起動制限——「会計年度任用職員(決裁不可)」は11業務に制限、「職員」「係長」は48業務で制限なし、というように、区分によって触れるアプリの幅が変わる。

メニューを組むとき、NGWはこの区分の BOOT_APP_CSVngwFunc_GetBootAppCsv($ManagerPosition) で引き、FIND_IN_SET(NGWT_APP_MENU_ID, '$BootAppCsv')メニューに出すアプリを絞る。だから、起動権のないアプリは、そもそもメニューに現れない。

ただし——隠すだけでは、守ったことにならない。URLを直打ちすれば画面に入れてしまうなら、非表示はただの気休めだ。そこでNGWは二段構えにしている。アプリ起動時にも ngwFunc_CheckBootableApp($ThisPHP, $ManagerPosition) が走り、その区分の BOOT_APP_CSV にそのアプリが含まれているかを確かめ、含まれなければ起動そのものを止めるメニューで隠し(見せない)、起動で防ぐ(入れない)。 プライバシー保護の記事で書いた「隠す/防ぐ」の二段構えが、アプリの入口でもそのまま効いている。

管理画面もまた、この仕組みで

そのJSONを人が編めるように、専用の管理アプリがある。

アプリメニューの管理画面。上部でアプリ(収受文書・起案文書・会議録など42件)を登録し、下部の「MENU ACTIVE SETTING」でメニュー項目のMENU名・ACTIVE_EVENT・TYPE・FUNC・ICONをグリッドで編集する

アプリメニューの管理画面。上でアプリ(`docuBunsho.php`=収受文書…全42件)を登録し、下の「MENU ACTIVE SETTING」で1項目ずつ MENU名・ACTIVE_EVENT・種別(TYPE)・FUNC・ICON を編集する。この編集結果が `NGWT_APP_MENU_EVENT_JSON` に畳まれる。

上半分がアプリの台帳(NGWT_APP_MENU の行)、下半分の「MENU ACTIVE SETTING」がJSONをグリッドで編集する部分だ。人はJSONの波括弧を直接書くのではなく、行を足し、プルダウンで種別を選び、FUNCICON を入れる。保存すると、それがJSON配列に組み直されて1列に収まる。

面白いのは、この管理画面の上部ツールバー(登録保存/更新保存/削除/一括更新…)それ自体も、同じアプリメニューの仕組みで描かれていることだ。メニューを管理する画面のメニューが、メニューの仕組みで動いている。

AIとの協働作業での学び

HTMLに埋め込んだ構造は「コード」になり、変えるたびにデプロイが要る。データに追い出した構造は「設定」になり、運用の中で変えられる。この線引きが、システムの寿命を左右する。

メニューは、その典型だ。頻繁に変わり、自治体ごとに違い、権限で出し分ける——コードに固めるには不安定すぎる。だからJSON1列に畳み、40を超えるアプリでコードはひとつ、変わるのはデータだけ、という形にした。「どこまでをコードにし、どこからをデータにするか」。地味だが、この判断こそが設計のいちばん太い背骨である。