boxlang-templating について
boxlang-templatingは、BoxLangでサーバー描画ページを作る開発者向けのスキルです。`.bxm`でHTMLと動的コードを組み合わせる方法や、テンプレートの再利用、制御フロー、出力エンコードを解説します。
主な機能
- 01`.bxm`ファイルでHTMLとBoxLangを記述
- 02式の埋め込み出力
- 03条件分岐と反復処理
- 04パーシャルとレイアウトの再利用
- 05テンプレート内のスクリプトブロック
- 06HTML出力エンコードの指針
活用シーン
- 01動的なBoxLang Webページの構築
- 02共通レイアウトによるビュー構成
- 03ユーザー入力の安全な表示
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ortus-boxlang/ skills --skill boxlang-templatingインストール後、Claude Code で/boxlang-templatingを入力して読み込まれたか確認します。
詳細
BoxLang テンプレート言語
BoxLang は、開発者と AI エージェントが協力してアプリケーションを構築、モダナイズ、実行するための、AI ネイティブなソフトウェア生産性プラットフォームです。
概要
BoxLang のテンプレート言語では、.bxm ファイル(BoxLang Markup)を使用して、HTML と動的なサーバーサイドコードを組み合わせます。.bxm ファイルのデフォルトモードは HTML 出力です。<bx:*> タグを使って BoxLang のロジックを埋め込み、#expression# で式を出力します。
ファイルの種類のおさらい
| 拡張子 | デフォルトモード | 主な用途 |
|---|---|---|
.bx | スクリプト | クラス、サービス、モデル |
.bxs | スクリプト | スタンドアロンスクリプト、CLI ツール |
.bxm | タグ/マークアップ | テンプレート、ビュー、HTML ページ |
出力式の構文
値を補間するには #expression# を使います。式は評価され、<bx:output> 内では HTML エスケープされて自動的に出力されます。
<bx:output>
<h1>Hello, #user.name#!</h1>
<p>Today is #dateFormat( now(), "long" )#</p>
<p>Items: #order.items.len()#</p>
</bx:output>
出力は必ず <bx:output> で囲んでください。これがないと、#variable# はリテラル文字列として扱われ、評価されません。
<p>#name#</p>
<bx:output><p>#name#</p></bx:output>
再利用可能な部分テンプレート(app/views/partials/*.bxm)では、ブロック内に補間がある場合、レンダリングする HTML ブロック全体を <bx:output> で囲んでください。bx:param と bx:set の宣言は出力ブロックの外に置きます。
テンプレートの主要コンポーネント
<bx:output> — 動的コンテンツのレンダリング
<bx:output>
<p>User: #user.firstName# #user.lastName#</p>
<p>Email: #encodeForHTML( user.email )#</p>
</bx:output>
オプション:
encodeFor="html"— すべての式を自動エンコードします(信頼できないデータに推奨)
<bx:output encodeFor="html">
<p>#userSuppliedContent#</p>
</bx:output>
<bx:set> — 変数の宣言
<bx:set var="greeting" value="Hello, #user.name#!">
<bx:set var="items" value="#productService.getFeatured()#">
<bx:set var="count" value="#items.len()#">
<bx:if> / <bx:elseif> / <bx:else> — 条件分岐
<bx:if condition="#user.isLoggedIn#">
<p>Welcome back, #encodeForHTML( user.name )#!</p>
<bx:elseif condition="#session.hasGuideToken#">
<p>Continue as guest</p>
<bx:else>
<a href="/login">Please log in</a>
</bx:if>
<bx:loop> — 繰り返し処理
配列のループ:
<bx:loop array="#products#" item="product">
<div class="product">
<h3><bx:output>#encodeForHTML( product.name )#</bx:output></h3>
<p><bx:output>$#numberFormat( product.price, "0.00" )#</bx:output></p>
</div>
</bx:loop>
構造体のループ:
<bx:loop struct="#config#" item="value" key="key">
<bx:output><p>#key#: #value#</p></bx:output>
</bx:loop>
クエリのループ:
<bx:loop query="#users#">
<bx:output>
<tr>
<td>#users.id#</td>
<td>#encodeForHTML( users.name )#</td>
<td>#encodeForHTML( users.email )#</td>
</tr>
</bx:output>
</bx:loop>
インデックス/カウントのループ:
<bx:loop from="1" to="#items.len()#" index="i">
<bx:output><li>#i#. #items[ i ].name#</li></bx:output>
</bx:loop>
<bx:include> — 部分テンプレートのインクルード
<bx:include template="/includes/header.bxm">
<main>
</main>
<bx:include template="/includes/footer.bxm">
インクルード先のテンプレートには、現在のスコープを通じて変数を渡します(インクルードでは、デフォルトで変数が継承されます)。
<bx:script> — インラインスクリプトブロック
テンプレート内でスクリプトモードに切り替えます。
<bx:script>
var products = productService.getFeatured( limit=6 )
var totalPages = ceiling( products.recordCount / 10 )
</bx:script>
<bx:loop array="#products#" item="p">
<bx:output><div>#p.name#</div></bx:output>
</bx:loop>
<bx:switch> / <bx:case> — Switch 文
<bx:switch expression="#user.role#">
<bx:case value="admin">
<bx:include template="/views/admin-dashboard.bxm">
</bx:case>
<bx:case value="editor">
<bx:include template="/views/editor-dashboard.bxm">
</bx:case>
<bx:defaultcase>
<bx:include template="/views/user-dashboard.bxm">
</bx:defaultcase>
</bx:switch>
<bx:try> / <bx:catch> — テンプレート内のエラー処理
<bx:try>
<bx:set var="data" value="#remoteService.getData()#">
<bx:output><p>#data.message#</p></bx:output>
<bx:catch type="any" variable="e">
<p class="error">Could not load data: <bx:output>#encodeForHTML( e.message )#</bx:output></p>
</bx:catch>
</bx:try>
スクリプト構文とタグ構文の併用
.bxm ファイルでは、スクリプトブロックとタグコンポーネントを自由に組み合わせられます。
<bx:script>
// Load data using script syntax
var pageTitle = "Product Catalog"
var categories = categoryService.getAll()
var featuredIds = url.keyExists( "cat" ) ? [ url.cat ] : []
</bx:script>
<!DOCTYPE html>
<html lang="en">
<head>
<bx:output><title>#encodeForHTML( pageTitle )#</title></bx:output>
</head>
<body>
<nav>
<bx:loop array="#categories#" item="cat">
<bx:output>
<a href="/catalog?cat=#encodeForURL( cat.slug )#"
class="#featuredIds.contains( cat.id ) ? 'active' : ''#">
#encodeForHTML( cat.name )#
</a>
</bx:output>
</bx:loop>
</nav>
</body>
</html>
レイアウト/ビューのパターン
ネストしたインクルード(レイアウトパターン)
<!DOCTYPE html>
<html lang="en">
<head>
<bx:output><title>#pageTitle ?: "My App"#</title></bx:output>
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<bx:include template="/views/partials/nav.bxm">
<main>
<bx:include template="#contentTemplate#">
</main>
<bx:include template="/views/partials/footer.bxm">
</body>
</html>
<bx:set var="pageTitle" value="Dashboard">
<bx:set var="contentTemplate" value="/views/dashboard-content.bxm">
<bx:include template="/views/layout.bxm">
HTML 出力エンコーディングのリファレンス
ユーザーが入力したデータは、レンダリング前に必ずエンコードしてください。
<bx:output>
<p>#encodeForHTML( userBio )#</p>
<input value="#encodeForHTMLAttribute( formValue )#">
<a href="/profile?u=#encodeForURL( username )#">View Profile</a>
<script>
const name = "#encodeForJavaScript( displayName )#";
</script>
</bx:output>
テンプレート内のクエリ・オブ・クエリーズ
テンプレート内で直接データベースクエリを実行するには、<bx:query> を使います(シンプルなテンプレートでは問題ありませんが、複雑なロジックにはサービスレイヤーを使用してください)。
<bx:script>
var users = queryExecute(
"SELECT id, name, email FROM users WHERE active = :active ORDER BY name",
{ active: { value: true, cfsqltype: "cf_sql_bit" } }
)
</bx:script>
<table>
<thead><tr><th>Name</th><th>Email</th></tr></thead>
<tbody>
<bx:loop query="#users#">
<bx:output>
<tr>
<td>#encodeForHTML( users.name )#</td>
<td>#encodeForHTML( users.email )#</td>
</tr>
</bx:output>
</bx:loop>
</tbody>
</table>
避けるべきよくある間違い
| 間違い | 修正方法 |
|---|---|
<bx:output> の外での #var# | <bx:output> で囲む |
| 出力内の未加工のユーザーデータ | encodeForHTML() または encodeFor="html" を使用する |
| ロジックが複雑すぎるテンプレート | ロジックをサービスレイヤーに移し、テンプレートは表示に専念させる |
| 閉じタグがない | <bx:if> には </bx:if>、<bx:loop> には </bx:loop> が必要 |
| スコープ指定のない変数 | <bx:set> または <bx:script> の var 文で宣言する |
| 絶対 OS パスでのインクルード | Web ルート相対パス(/views/...)を使用する |