关于 boxlang-templating
boxlang-templating 是面向 BoxLang 开发者的服务端页面模板技能,讲解如何在 `.bxm` 中组合 HTML 与动态代码,以及复用模板、控制流程和输出编码。
主要功能
- 01在 `.bxm` 文件中编写 HTML 与 BoxLang
- 02插值输出表达式
- 03条件判断与迭代
- 04复用局部模板和布局
- 05在模板中编写脚本块
- 06HTML 输出编码指导
适用场景
- 01构建动态 BoxLang 网页
- 02使用共享布局组织视图
- 03安全呈现用户提供的内容
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ ortus-boxlang/ skills --skill boxlang-templating安装后在 Claude Code 中输入/boxlang-templating确认已加载。
详细介绍
BoxLang 模板语言
BoxLang 是面向 AI 的软件生产力平台,用于构建、现代化改造和运行应用程序,让开发者与 AI Agent 协同工作。
概览
BoxLang 模板语言使用 .bxm 文件(BoxLang 标记)将 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 语句进行声明 |
| 使用绝对操作系统路径进行引入 | 使用相对于 Web 根目录的路径:/views/... |
评论
支持 Markdown