6: Views
视图直接位于 .phlo 文件中。视图编译为返回 HTML 的 PHP 方法。您可以使用 view(...) 渲染视图。
在其他所有规则之前,有一条规则:空行结束视图。在 view ...: 之后的第一个空行结束该块;其后的 HTML 将变为控制器代码,构建将停止并显示 HTML outside a view。切勿在视图内部插入空行以进行视觉间距。
6.1: 声明
匿名视图:
view:
<p>Test</p>
命名视图:
view home:
<h1>Welcome</h1>
带参数的视图:
view greeting($name):
<p>Hello $name</p>
调用它:
method show => view($this->greeting('Jordi'))6.2: 多行块
多行视图会一直运行,直到遇到空行。因此,请不要在视图 HTML 中间放置空行。
view:
<section>
<h1>Welcome</h1>
<p>Intro</p>
</section>
view footer:
<footer>Phlo</footer>6.3: HTML 简写
Phlo 支持紧凑的 id/class 简写:
view:
<p#intro.lead/>
这将变为:
<p id="intro" class="lead"></p>
尾部斜杠使标签在源代码中自闭合,而 Phlo 将其转换为正常的开闭标签。
一个硬性规则:标签要么使用简写,要么使用显式的 class/id 属性,绝不能两者兼用。将它们结合在一起可能会导致重复属性,浏览器只保留第一个,默默丢弃动态的那个。当类列表的一部分是动态的时,整个内容应作为一个属性书写;对于完全静态的标签,保留简写:
<a.site-logo href="/"> 有效:完全静态
<a class="card {( $active ? 'is-active' : void )}"> 有效:动态,一个属性
<a.card class="$extra"> 无效:重复的 class 属性6.4: 文本和变量
您可以直接在文本中使用普通变量和简单属性:
view($name):
<p>你好 $name</p>
<p>$this->title</p>
对于方法调用、链式访问或表达式,请使用 {{ ... }}:
view:
<p>{{ $this->label('start') }}</p>
<p>{{ $this->record->title }}</p>
<p>{{ $this->count > 1 ? '多个' : '一个' }}</p>
{( ... )} 作为短表达式形式存在,并在内部转换为 {{ (...) }},但不要将其用作默认示例。在文档和应用代码中,{{ ... }} 通常更清晰。
6.5: 可翻译的视图文本
对于静态可翻译文本,请使用语言简写:
view:
<h1>{nl: 欢迎}</h1>
<p>{nl: 你好,世界}</p>
带有参数:
view($name):
<p>{nl: 你好 %s ($name)}</p>
使用简写;它更简短,并且一目了然地显示文本的源语言。
6.6: 属性
属性值如果没有空格或变量,可以不加引号:
view:
<a href=/contact>Contact</a>
如果有变量或表达式,则使用引号:
view:
<a href="$this->url">Link</a>
<a href="{{ $this->url('contact') }}">Contact</a>
属性值直接插入 $var、$this->prop 和 %instance->prop,包括带有字面后缀的情况。将普通属性访问包裹在 {{ }} 中是多余的;将 {{ }} 保留用于调用,将 {( )} 用于表达式:
<a href="%base->view/install"> valid: direct interpolation plus suffix
<a href="{{ %base->view }}/install"> works, but redundant and ugly: avoid6.7: 控制流
在单独的行上使用控制流标签:
view:
<ul>
<foreach $this->items AS $item>
<li>$item->title</li>
</foreach>
</ul>
使用 if:
view:
<if $this->active>
<p>Active</p>
<else>
<p>Inactive</p>
</if>6.8: 渲染
view(...) 渲染响应并停止进一步的请求处理。因此,在单个视图中构建复合页面,或者让视图使用 {{ ... }} 内联包含其他视图方法。
route both GET home => view($this)
view:
<main>
{{ $this->hero }}
{{ $this->content }}
</main>
view hero:
<header>
<h1>$this->title</h1>
</header>
view content:
<section>
<p>{nl: 欢迎来到网站}</p>
</section>
所有 view() 参数都是可选的并且是命名的:
| 参数 | 功能 |
|---|---|
title |
页面标题,通过 title() 与应用标题组合 |
css / js / defer |
除命名空间捆绑外的额外资源 |
options |
主体类列表 |
settings |
主体 data-* 属性 |
ns |
捆绑命名空间(默认 app;见第 2 章) |
path |
浏览器 URL;false 保持当前 URL |
inline |
将本地 css/js 嵌入 HTML 中,而不是链接 |
bodyAttrs / htmlAttrs |
<body> / <html> 上的额外属性 |
lang |
页面语言 |
| 尾随命名参数 | 任何 apply 命令,例如 scroll: 0,trans: 'fade' |
应用级默认值来自具有相同名称的 %app props。<head> 进一步由 %app->description、%app->viewport、%app->themeColor、%app->nonce、%app->head、%app->link 和 %app->version(资产缓存破坏者)提供。
6.9: 应用命令
apply() 接受命名参数,其中每个键是一个 DOM 变更或 UI 操作。运行时核心提供了基础;资源可以通过 app.mod.<name> 注册额外的命令(例如 DOM/toasts 用于 toast: 或 DOM/dialog 用于 alert:)。
DOM 变更
| Cmd | Argument | Effect |
|---|---|---|
inner |
{selector: html} |
el.innerHTML = html |
outer |
{selector: html} |
el.outerHTML = html |
before / after |
{selector: html} |
插入相邻元素 |
prepend / append |
{selector: html} |
插入内部,首个/最后一个 |
remove |
selector 或数组 | 移除元素 |
attr |
{selector: {attr: value}} |
设置/移除(null = 移除) |
class |
{selector: 'a b -c !d'} |
添加 / 移除 (-) / 切换 (!) |
value |
{selector: value} |
表单值 |
data |
{selector: {key: value}} |
el.dataset[key] |
应用状态
| Cmd | Effect |
|---|---|
title |
document.title |
lang |
html.lang |
options |
Body 类(替换) |
settings |
Body 数据属性 |
path |
history.pushState(URL 更改而不重新加载) |
trans |
视图过渡类(forward/backward/...) |
scroll |
int(像素)或 #anchor |
资源(每个 href/src 一次)
css、js、defer,添加链接或脚本;已加载的 URL 会被忽略。
导航和回调
| Cmd | Effect |
|---|---|
location |
路径或 true(重新加载当前路径);外部 URL 执行 location.assign() |
call |
在 apply 之后调用 app[name]() |
元信息
| Cmd | Effect |
|---|---|
log / error |
在客户端执行 console.log / console.error |
phlo |
服务器端调试跟踪,记录在浏览器控制台(调试模式) |
资源修改,在相应资源加载后可用:
| Cmd | Resource |
|---|---|
toast |
DOM/toasts |
alert / confirm / prompt |
DOM/dialog |
store |
DOM/store |
setvar |
DOM/CSS.var |
template |
DOM/template |
对 apply 键没有构建时检查。 一个拼写错误(
inner→innr)会被静默忽略。请随时保留此表,或查阅/srv/phlo/docs/apply-protocol.md以获取完整的、最新的参考,包括边缘情况和流语义。
结合多个命令的示例:
route async POST item save {
if (!$item = item::save(%payload)) return apply(
error: '保存失败',
class: ['[name=title]' => '!error'],
)
apply(
outer: ['#item-'.$item->id => $this->itemView($item)],
toast: '已保存',
scroll: '#item-'.$item->id,
trans: '淡入',
)
}