こんにちは、安宅です。
私は主に受託開発をしていますが、今まで「CMS (content management system)」を使った案件の引き合いを結構いただいております。1からスクラッチで構築したケースやオープンソースを利用したケース、費用や管理視点から機能を部分的に適用したケースなどいろいろと携わりましたが、お客様の運用におけるコスト削減を実現するため、どのようなサイト設計にするかはいつも悩むところです。
最近は 「WebRelease2」という CMS を使う機会がありました。「WebRelease2」は株式会社フレームワークスソフトウェアが開発した国産の企業向け商用 CMS です。
こちらはサーバに「WebRelease2」をインストールするだけで起動することができる有償版ソフトウェアです。起動後は管理画面にログインをしてリンクから「ユーザーズマニュアル」のページを見ることができます。テンプレートの仕組みやページの作り方や利用できる関数索引があり、設計方法やサイト構成の例が見つけられず、また他社のブログや技術情報共有サービスからも見つけられませんでしたので、最初は手探りで設計とサイト構築をしました。同じように悩まれている方の少しでも参考になればと思い、今回は実際に構築した設計パターンを記載してみたいと思います。
今回使用した「WebRelease2」のバージョンは2.70Kです。本記事は株式会社フレームワークスソフトウェアにレビューいただきました。ご協力いただき誠にありがとうございました。
1. 前提と簡単な紹介
「WebRelease2」は CMS として稼働しているサーバ(以下、管理サーバ)と 公開サーバ(以下、Webサーバ)で分けて考える必要があります。管理サーバで登録した情報から HTML などの静的ファイルを生成して、 FTP または SFTP プロトコル で Webサーバ へファイル転送をします。
そのため Webサーバ は静的ファイルのみで構成された状態となります。動的ファイルはもちろん置くこともできますが、今回は全て静的ファイル構成という前提で進めていきます。また「WebRelease2」で提供されている機能のみを利用して設計をしていきます。
動作環境等の説明に関しては以下をご参照ください。
動作環境
全て静的ファイルで構成されているため、Webサーバ から管理サーバへのアクセスはありません。管理サーバでページが公開されたタイミングで Webサーバ へファイル転送し静的ファイルが更新されます。「WebRelease2」では依存関係の設定も可能なため、あるページが公開されたタイミングで、関連するファイル全てが Webサーバ へ転送されます。
例えば Aページ が Bページを参照している状態で、 BページのURLを変更し Bページ が公開された場合、
Aページ と Bページ 両方転送されます。Aページ 内の Bページ の URL が変更されたためです。
※ただしリンク要素で リンク先として Bページ が追加されていた場合に限る。
公開予約などもできるため、適切な設計を行えば複数のページを毎回更新せずに簡単に管理することができます。今回はサイトのナビゲーションに使われる「パンくず」の設計例を説明していきたいと思います。今回は事例紹介のため「WebRelease2」に関する基本的な情報は割愛します。知りたい方はWebReleaseとは?をご参照ください。
2. パンくずの作成
「パンくず」を「WebRelease2」で表示させるためにいくつかの方法があります。
- 「パンくず」を表示したいページで設定をする
- 「パンくず」を表示したいページのテンプレートで設定をする
- 「パンくず」専用のテンプレート(コンポーネント)を作成し、各テンプレートから呼び出す
「1」の方法は非常に作るのが楽ですがサイト構成が変わった場合に修正箇所が膨大になりますので、「2」か「3」が適していると考えます。パンくずを表示するルールは共通化できると思いますので、同処理を複数テンプレートに記載するのではなく、専用のテンプレートを作成し各テンプレートが参照する「3」の方式で進めます。
図1 「パンくず」テンプレートの要素
まずは新規テンプレートとして、「パンくず」テンプレートを作成してみます。要素を階層構造として作成してきます。今回は一旦4階層まで登録できるようにしました。
図2 「パンくず」のページ
利用者に見えるパンくずのページはこのようになります。利用者が視覚的に階層構造がわかりやすいようにしました。矢印のアイコンから要素を選択、追加することができます。
新規ページができた際にはこちらにも登録が必要となりますが、一度登録すれば新規ページのURLが変わっても依存関係がありますので、自動的に更新され Webサーバへ転送されます。次に各テンプレートで呼び出すためのメソッドを作成します。
viewBreads(target_page)
1 2 3 4 5 6 7 |
<div class="container"> <ul> <wr-for list="getBreads(target_page, main_group.sub_group.selector)" variable="x"> %x% </wr-for> </ul> </div> |
パンくず情報を取得するメソッドを呼び出し、結果を出力しています。「target_page」は各テンプレートから「thisPage()」の値が指定されてきます。
getBreads(target_page, selector)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<wr-variable name="breads" /> <wr-variable name="bread" /> <wr-for list="selector" variable="x"> <wr-if condition="x.isSelected(\"link\")"> <wr-then> <wr-if condition="isTargetPage(target_page, x)"> <wr-then><wr-append name="breads">%getBreadSpanTag(x.link)%</wr-append><wr-break /></wr-then> <wr-else><wr-variable name="bread">%getBreadATag(x.link)%</wr-variable></wr-else> </wr-if> </wr-then> <wr-else> <wr-if condition="x.isSelected(\"sub_group\")"> <wr-then> <wr-variable name="children" /> <wr-for list="getBreads(target_page, x.sub_group.selector)" variable="y" ><wr-append name="children">%y%</wr-append></wr-for> <wr-if condition="isNotNull(children)"> <wr-append name="breads">%bread%</wr-append> <wr-for list="children" variable="z"><wr-append name="breads">%z%</wr-append></wr-for> </wr-if> </wr-then> </wr-if> </wr-else> </wr-if> </wr-for> <wr-return value="breads" /> |
こちらでパンくずを生成しています。再帰的にメソッドを呼び出しています。「パンくず」を「breads」という配列に追加していき、最終その配列を戻します。
getBreadATag(page)
1 |
<wr-return><li><a title="%pageTitle(page)%" href="%page%" itemprop="url"><span itemprop="title">%pageTitle(page)%</span></a></li></wr-return> |
getBreadSpanTag(page)
1 |
<wr-return><li><span itemprop="title">%pageTitle(page)%</span></li></wr-return> |
「breads」に追加するパンくずのHTMLを生成します。
※引数の page は Page オブジェクトになりますが、「breads」の配列(wr-variable)に追加した場合はPage オブジェクトとして要素を呼び出すことができなくなります。
isTargetPage(target_page, selector)
1 |
<wr-return value="pageID(selector.link) == pageID(target_page)" /> |
引数のページと「パンくず」のページIDが一致したかを判定します。
次に呼び出す側のテンプレートを見ていきます。すでに作成済みの「汎用」テンプレートを利用します。
図3 「汎用」テンプレートの要素
「breads」という目次要素を追加し、「掲載するページのテンプレート」に「パンくず」を設定します。この設定で「汎用」テンプレートの展開から「パンくず」テンプレートのメソッドが利用できるようになりました。先程作ったメソッドを「展開」から呼び出します。
「パンくず」テンプレートの展開
1 2 3 |
<nav id="breadcrumbs" role="navigation"> %breads.viewBreads(thisPage())% </nav> |
「汎用」テンプレートから作成したページをプレビューで確認してみます。
図4 「汎用」テンプレートのページリスト
図5 機能ページと課題分析ページのプレビュー
「汎用テンプレート」から作成した機能ページと課題分析ページの抜粋になります。これで「パンくず」ページに登録すれば自動で表示されるようになりました。ページの階層や配置替えをしたい場合は、「パンくず」ページを修正するだけで自動的に変わるようになりました。薄い灰色がリンク、濃い灰色がテキストになります。
※ページが数万に及ぶ場合は「パンくず」ページを複数作成し、呼び出し方法を制御をしなければ Webサーバへの転送速度が遅くなるので注意が必要です。
2. パンくずからの延長
「パンくず」テンプレートを作ったのにはもう一つ理由があります。このテンプレートを利用して以下も作成することができます。
- サイトマップ
まずは先程つくった「パンくず」テンプレートを「公開テンプレート」へ変更し、サフィックスを json に修正します。また「パンくず」のように新たにメソッドを作成します。
getSiteMap(selector, level)
1 2 3 4 5 6 7 8 9 10 |
<wr-variable name="sites" /> <wr-for list="selector" variable="x"> <wr-if condition="x.isSelected(\"link\")"> <wr-then><wr-append name="sites">%getJsonValue(x.link, level)%</wr-append></wr-then> <wr-else> <wr-for list="getSiteMap(x.sub_group.selector, add(level, 1))" variable="y" ><wr-append name="sites">%y%</wr-append></wr-for> </wr-else> </wr-if> </wr-for> <wr-return value="sites" /> |
「パンくず」と同じように再帰的にメソッドを呼び出しています。ここでは全てのページを出力しますが、階層情報も出力したいので引数に階層を追加しています。「sites」の配列に追加し、最終その配列を戻します。
getJsonValue(page, level)
1 |
{"url": "%page%", "title": "%pageTitle(page)%", "level": %level%} |
「sites」に追加するJSONを生成します。
「パンくず」テンプレートの「展開」
1 2 3 |
<wr-variable name="level" value="1"/>[ <wr-for list="getSiteMap(main_group.sub_group.selector, level)" variable="x" index="i"><wr-if condition="i != 0">,</wr-if>%x% </wr-for>] |
図6 「パンくず」ページのプレビュー
「パンくず」ページが json 形式で出力されるようになりました。続いてサイトマップを表示させるテンプレートを作成します。今回は「サイト内検索」というテンプレート名にしました。先程作成した「パンくず」ページをリンク要素として追加します。
図7 「サイト内検索」テンプレートの要素
これで先程作成した「パンくず」のjson ページが呼び出せるようになりました。実際に展開で ajax で呼び出してみます。
図8 「サイト内検索」のページ
パンくずに設定した json の情報が表示されるようになりました。後はデザインに合わせて適用すればページとしては完成となります。
3. パンくずからのおまけ
json データがあるので、検索ページも作ることができます。 「パンくず」のjson ページ に検索に必要なデータを出力するようにして、キーワードを入力して ajax もしくは同ページへパラメータ付きでリクエストすれば静的サイトでもある程度動的サイトを表現することができます。
※ページが膨大な場合はクライアントサイドに大きな負荷がかかるため、状況に応じてページング処理を入れたり、取得件数を制限する等ご判断ください。
今回は「パンくず」を例にサイト設計例の説明をいたしました。ご要望がありましたら引き続き事例を書いていきたいと思います。