Table of Contents
- 介绍
- 占位符的基本使用
- 标准占位符
- 视图占位符:结论
介绍
在前面一章,我们主要学习了靓图视图模式,它允许你在全站布局内嵌入单个应用程序视图。在上一章的最后,我们讨论了一些不足之处:
· 怎样盖面页面的title?
· 怎样注入条件脚本或者样式表到全站(sitewide布局)?
· 怎样创建和渲染一个可选的边栏(sidebar)?如果边栏中有一些无条件的内容,其它又是有条件的内容,那该怎么办?(What if there was some content that was unconditional, and other content that was conditional for the sidebar?)
这些问题在复合视图(Composite View)设计模式中解决了(These questions are addressed in the » Composite View design pattern)。与这种模式相近的是为全站布局提供“暗示”或者内容。在Zend Framework中,这是通过被称为“占位符”的专门的视图助手实现的。占位符允许你去聚合内容,然后在其他地方渲染那个聚合的内容。
占位符的基本用法
Zend Framework定义了一个通用的placeholder()视图助手,你可以将它用于你需要的许多自定义的占位符。它也提供了经常需要使用的多种多样的特定占位符工具,比如指定DocType声明、文档标题等等。
所有的占位符都大致使用相同的操作。他们是容器,并且因此允许你作为收集器操作他们,有了他们你可以
- 追加或者预先准备(Append or prepend)项目集合
- 替换整个集合为单个值
- 指定一个字符串,当渲染的时候用它预先准备(prepend)集合的输出
- 指定一个字符串,当渲染的时候用它追加(append)集合的输出
- 指定一个字符串,当渲染的时候用它去分离集合的各项(separate items)
- 捕获内容到集合里
- 渲染聚合内容
典型的,你将无参调用这个助手,它将返回一个你可以操作的容器。然后你可以echo这个容器或者渲染它或者在其上调用方法或者配置它或者填充它。如果容器是空的,渲染它将简单返回一个空字符;否则,内容将根据你配置的规则被汇总
作为一个例子,我们来创建一个包含许多内容的“块(block)”边栏。你可能预先(up-front)知道每个块的结构;我们来假设这个例子看起来像下面这样:
- <div class="sidebar">
- <div class="block">
- <p>
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
- consectetur aliquet odio ac consectetur. Nulla quis eleifend
- tortor. Pellentesque varius, odio quis bibendum consequat, diam
- lectus porttitor quam, et aliquet mauris orci eu augue.
- </p>
- </div>
- <div class="block">
- <ul>
- <li><a href="/some/target">Link</a></li>
- <li><a href="/some/target">Link</a></li>
- </ul>
- </div>
- </div>
这个内容将基于控制器和动作而有所不同,但是结构会是相同的。我们首先在我们bootstrap的资源方法中建立侧边栏:
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- // ...
- protected function _initSidebar()
- {
- $this->bootstrap('View');
- $view = $this->getResource('View');
- $view->placeholder('sidebar')
- // "prefix" -> markup to emit once before all items in collection
- ->setPrefix("<div class=/"sidebar/">/n <div class=/"block/">/n")
- // "separator" -> markup to emit between items in a collection
- ->setSeparator("</div>/n <div class=/"block/">/n")
- // "postfix" -> markup to emit once after all items in a collection
- ->setPostfix("</div>/n</div>");
- }
- // ...
- }
上面定义了一个占位符, “sidebar”,还没有条目。这里配置这个占位符的基本的标签结构,然而却符合我们的需要。
现在,我们假设在“user”控制器中的所有的动作的顶部,包含一些信息的块(let's assume for the "user" controller that for all actions we'll want a block at the top containing some information)。我们能够通过两种方法完成这个:(a)我们可以在控制器的preDispatch()方法中直接将内容加入占位符,或者(b)我们可以从priDispatch()方法里渲染一个视图脚本。我们将使用(b)方法,因为它遵循一个更适当的我们关心的内容的分离(在一个视图脚本中抛开视图相关逻辑和功能(leaving view-related logic and functionality within a view script))。
我们将命名这个视图脚本为“user/_sidebar.phtml”,并且用下面这些来填充它:
- <?php $this->placeholder('sidebar')->captureStart() ?>
- <h4>User Administration</h4>
- <ul>
- <li><a href="<?php $this->url(array('action' => 'list')) ?>">
- List</a></li>
- <li><a href="<?php $this->url(array('action' => 'create')) ?>">
- Create</a></a></li>
- </ul>
- <?php $this->placeholder('sidebar')->captureEnd() ?>
在上面的例子中使用了捕获占位符特性的内容。默认的,容器中的内容作为一个新条目被追加,允许我们去聚合内容。这个例子为了产生标记使用了视图助手和静态HTML ,并且内容然后被捕获和追加到占位符自身中
为了调用上面的视图脚本,我们将在preDispatch()方法中写入下面的内容:
- class UserController extends Zend_Controller_Action
- {
- // ...
- public function preDispatch()
- {
- // ...
- $this->view->render('user/_sidebar.phtml');
- // ...
- }
- // ...
- }
注意,我们没有捕获被渲染的值;没有必要,因为整个视图正在被捕获到一个占位符中。
现在,我们假设在同一个控制器中我们的“视图”动作需要呈现一些信息。在"user/view.phtml" 视图脚本中,我们可能有下面的一些内容片段:
- $this->placeholder('sidebar')
- ->append('<p>User: ' . $this->escape($this->username) . '</p>');
这个例子使用append()方法,并且传递给它一些简单的聚合标记
最后,我们来修改我们的布局视图脚本,并且渲染占位符
- <html>
- <head>
- <title>My Site</title>
- </head>
- <body>
- <div class="content">
- <?php echo $this->layout()->content ?>
- </div>
- <?php echo $this->placeholder('sidebar') ?>
- </body>
- </html>
对于没有填充“sidebar”占位符的控制器和动作,将没有内容被渲染;对于那些做的,输出占位符将根据我们在bootstrap中创建的规则渲染内容,并且我们聚合的内容贯穿整个应用程序。就"/user/view" 动作而言,假设有一个名为“matthew”的用户名,我们将为侧边栏得到如下内容(为了方便阅读,进行了格式化)
- <div class="sidebar">
- <div class="block">
- <h4>User Administration</h4>
- <ul>
- <li><a href="/user/list">List</a></li>
- <li><a href="/user/create">Create</a></a></li>
- </ul>
- </div>
- <div class="block">
- <p>User: matthew</p>
- </div>
- </div>
通过结合占位符和布局脚本你可以做大量的事情;用他们实践,并且阅读有关手册章节得到更多信息。
标准占位符
在前面一节,我们学习了placeholder()视图助手,并知道了它怎样被使用去聚合自定义内容。这一节,我们来具体地看看Zend Framework中自带的一些占位符,并且当创建复杂的综合布局是你怎样高效的去使用它们
大多数自带的占位符是为了产生你的布局中<head>节的内容——你通常不能通过你的应用视图脚本直接操作的地方,但是有时你想去影响的地方。举例来说:你可能想要你的标题在每页中包含特定的内容,但是指定的内容基于控制器和/或动作;你可能想导入基于在你应用程序的哪一节的指定CSS文件;你可能需要在不同的时间导入指定的JavaScript脚本;你可能还想去设置文档类型(docType)声明。
Zend Framework有而不仅仅有为这些情况准备的占位符实现。
设置文档类型(DocType)
文档类型声明是很难去记住,并且为了确保浏览器正确渲染你的内容,在你的文档中经常是必不可少的。doctype()视图助手允许你去使用简单的字符串记忆法去指定所需的文档类型;另外,其他的助手将查询doctype()助手以确保产生的输出符合请求的文档类型。
作为一个例子,如果你想使用XHTML1 Strict DTD,你可以简单的指定:
- $this->doctype('XHTML1_STRICT');
在其他可用的记忆法中,你可以发现这些常见的类型:
XHTML1_STRICT
XHTML 1.0 Strict
XHTML1_TRANSITIONAL
XHTML 1.0 Transitional
HTML4_STRICT
HTML 4.01 Strict
HTML4_Loose
HTML 4.01 Loose
HTML5
HTML 5
你能够在一个调用中指定类型并且渲染它的声明
- echo $this->doctype('XHTML1_STRICT');
然而,更好的方法是去在你的bootstrap种指定类型,然后在布局中渲染它。试着在你的bootstrap类中添加下面的内容:
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- protected function _initDocType()
- {
- $this->bootstrap('View');
- $view = $this->getResource('View');
- $view->doctype('XHTML1_STRICT');
- }
- }
然后,在你的布局脚本中,简单的在文件的顶部echo这个助手
- <?php echo $this->doctype() ?>
- <html>
- <!-- ... -->
这将确保你的文档类型——知道视图助手渲染适当的标记,确保类型在布局渲染之前被设置好,并提供单一地点去改变文档类型。
指定页面标题(page title)
经常的,一个站点将包括站点或业务(business)名作为页面标题的一部分,然后基于站点的位置增加另外的信息。作为一个例子,zend.com网站在所有页面包括“Zend.com”字符串,其前面的信息基于页面:“Zend Server-Zend.com”。在Zend Framework中,headTitle()视图助手能够帮助简化这个任务。
最简单情况下,headTitle()助手允许你为<title>标签聚合内容;当你echo它时,它将基于各个片段加入的顺序进行组装。你可以使用prepend()和append()来控制顺序,并通过setSeparator()方法在各个片段中提供分隔符去使用(and provide a separator to use between segments using the setSeparator() method)。
典型地,你应该在你的bootstrap中指定在所有页面中相同的任何片段,就像我们定义文档类型一样。在这个例子中,为了操作所有各种各样的占位符,我们将定义一个_initPlaceholders()方法,并且指定一个初始的标题以及一个分隔符。
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- // ...
- protected function _initPlaceholders()
- {
- $this->bootstrap('View');
- $view = $this->getResource('View');
- $view->doctype('XHTML1_STRICT');
- // Set the initial title and separator:
- $view->headTitle('My Site')
- ->setSeparator(' :: ');
- }
- // ...
- }
在一个视图脚本中,我们可能想增加另外的片段
- <?php $this->headTitle()->append('Some Page'); // place after other segments ?>
- <?php $this->headTitle()->prepend('Some Page'); // place before ?>
在我们的布局中,我们将简单的echo这个headTitle()助手:
这将产生下面的输出:
- <!-- If append() was used: -->
- <title>My Site :: Some Page</title>
- <!-- If prepend() was used: -->
- <title>Some Page :: My Site</title>
用HeadLink指定样式表
优秀的CSS开发者经常会为全站样式创建一个基本样式表,为站点的特殊章节或者页面创建单个样式表,并且有条件地导入后者以便在每次请求时减少需要传输的数据数量。在你的应用程序中,headLink()占位符使那样的样式表的条件聚合琐碎(The headLink() placeholder makes such conditional aggregation of stylesheets trivial within your application.)。
为了完成这个任务,headLink()定义许多“虚拟”方法(通过重载)去使过程琐碎(trivial)。我们关心的方法是appendStylesheet() 和prependStylesheet()。每一个方法都需要四个参数:$href(样式表相关的路径),$media(MIME类型,默认是“text/css”)、$conditionalStylesheet(它可以用作指定一个“条件”,这个条件用于评价样式表)和$extras(一个键和值的关联数组,通常用作为”media”指定一个键)。在大多数情况下,你将仅仅需要去指定第一个参数,即样式表的相关路径。
在我们的例子中,我们将假设所有的页面需要去导入位于“/styles/site.css”(相对于document root)的样式表;我们将在bootstrap的_initPlaceholders()方法中指定这个。
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- // ...
- protected function _initPlaceholders()
- {
- $this->bootstrap('View');
- $view = $this->getResource('View');
- $view->doctype('XHTML1_STRICT');
- // Set the initial title and separator:
- $view->headTitle('My Site')
- ->setSeparator(' :: ');
- // Set the initial stylesheet:
- $view->headLink()->prependStylesheet('/styles/site.css');
- }
- // ...
- }
后面,在一个控制器或者具体动作(action-specific)的视图脚本中,我们能够增加更多的样式表:
- <?php $this->headLink()->appendStylesheet('/styles/user-list.css') ?>
Within our layout view script, once again, we simply echo the placeholder:
- <?php echo $this->doctype() ?>
- <html>
- <?php echo $this->headTitle() ?>
- <?php echo $this->headLink() ?>
- <!-- ... -->
这将会产生下面的输出:
- <link rel="stylesheet" type="text/css" href="/styles/site.css" />
- <link rel="stylesheet" type="text/css" href="/styles/user-list.css" />
使用HeadScript聚合脚本
另外去阻止长页面导入次数的惯常手法仅导入必要的JavaScript。这就是说,你可能需要多层脚本:也许一个是为你的站点逐渐加强菜单,另一个是为特定页面(page-specific)内容。在这些情况下,headScript()助手提供了一个解决办法。
与headLink()助手相似,headScript()在集合(the collection)中提供追加或前置(append or prepend)脚本的能力,然后echo整个设置。它提供了指定导入自身脚本文件或者指定明确的JavaScript的灵活性。你也可以通过captureStart()/ captureEnd()捕获JavaScript,它允许你简单的内嵌JavaScript而不是需要为你的服务器有额外的调用。
也与headLink()类似, 当指定条目去聚集时headScript()在方便的时候通过重载提供“虚拟(virtual)”方法(headScript() provides "virtual" methods via overloading as a convenience when specifying items to aggregate),通常的方法包括rependFile()、 appendFile()、prependScript()和 appendScript()。前两个允许你的去指定文件,其将在<script>标签的$src属性中引用到;后两个将在<script>标签中获取提供的内容并作为JavaScript字面量进行渲染(the latter two will take the content provided and render it as literal JavaScript within a <script> tag)。
在这个例子中,我们将指定那个脚本,"/js/site.js" 需要在每个页面导入;我们来更新_initPlaceholders()方法来做这件事。
- class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
- {
- // ...
- protected function _initPlaceholders()
- {
- $this->bootstrap('View');
- $view = $this->getResource('View');
- $view->doctype('XHTML1_STRICT');
- // Set the initial title and separator:
- $view->headTitle('My Site')
- ->setSeparator(' :: ');
- // Set the initial stylesheet:
- $view->headLink()->prependStylesheet('/styles/site.css');
- // Set the initial JS to load:
- $view->headScript()->prependFile('/js/site.js');
- }
- // ...
- }
在视图脚本中,我们可能还要加入另外的脚本文件到源文件中,或者在我们的文档中包含一些捕获的JavaScript。
- <?php $this->headScript()->appendFile('/js/user-list.js') ?>
- <?php $this->headScript()->captureStart() ?>
- site = {
- baseUrl: "<?php echo $this->baseUrl() ?>"
- };
- <?php $this->headScript()->captureEnd() ?>
在布局脚本中,我们只要简单的echo这个占位符,就像我们已经有的其他占位符。
- <?php echo $this->doctype() ?>
- <html>
- <?php echo $this->headTitle() ?>
- <?php echo $this->headLink() ?>
- <?php echo $this->headScript() ?>
这将产生下面的输出:
- <script type="text/javascript" src="/js/site.js"></script>
- <script type="text/javascript" src="/js/user-list.js"></script>
- <script type="text/javascript">
- site = {
- baseUrl: "<?php echo $this->baseUrl() ?>"
- };
- </script>
注: InlineScript变体
许多浏览器将经常在<head>节所有脚本和引用的样式表全部导入之前阻塞页面的显示。如果你有许多那样的指令,浏览页面就会等待一段时间。
解决这个问题的一个办法是在你的文档中在关闭<body>之前发出(emit)你的<script>(这是“Y! Slow project”实践的具体建议)
Zend Framework用两种不同的方法支持这个:
· 你能够在你喜欢的layout脚本的任何地方渲染你的headScript()标签;因为标题中提到的“head”并不是意味着它在那个位置需要渲染。
· 或者,你可以使用inlineScript()助手,它是headScript()的一个简单变体,并且保持了同样的行为,但是使用了一个单独的注册表(separate registry)
视图占位符:结论
视图占位符是一个为你的应用程序创建丰富布局的简单而强大的方法。你可以使用各种标准的占位符,不如这里已经讨论的(doctype()、headTitle()、headLink() 和 headScript()),或者使用通常的placeholder()助手去聚集内容并用自定义的方法去渲染。用他们已经公开的功能试验,并在参考指南中查看适当的章节去发现他们提供的其他特性——并且你可以为利用这些功能你的读者创建丰富的内容。