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: avoid

6.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: 0trans: '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 一次)

cssjsdefer,添加链接或脚本;已加载的 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 键没有构建时检查。 一个拼写错误(innerinnr)会被静默忽略。请随时保留此表,或查阅 /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: '淡入',
    )
}

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。