《应用Yii1.1和PHP5进行敏捷Web开发》

第十一章:迭代8:美化-设计,布局,主题,国际化(i18N)

在之前的迭代中,我们开始着手美化我们的项目,我们的URLs开始对用户和爬过我们网站的搜索引擎变得更有魅力(可能指更友好)。在本次迭代中,我们将更多的注意力投向Yii中页面布局和主题所带来的感观和感觉上。因此我们实现本章将通过将我们应用程序的外观改变的更好看来实现本章的主旨,我们将关注一种可以采取的方法和一种可以用来帮助设计Yii应用程序前台的工具,而不是自己重新设计。所以本次迭代中将更多的关注如何使你的应用程序更漂亮,而不是花费大量时间去专门设计我们TrackStar应用程序(的外观)。

迭代计划

这次迭代是基于前端来完成的。我们想制作一个可以被动态重用的网站的新外观。我们并不想通过重写或删除当前设计的方式来实现。同时,我们也会深入Yii的i18N(internationalization = i18N )功能,所以我们必须清楚的明白如何使应用程序适应来自不同地理位置的用户。

下面是一个high-level(高级/高优先级)任务列表,我们需要完成它以实现我们的目标:

  • 通过创建新的布局,CSS和其他配置文件来建立一个新的主题,并为应用程序提供一个新前端设计
  • 使用Yii的i18N和定位功能将我们应用程序中的一部分翻译成另外一种新语言

设计布局

你可能注意到的一件事是:我们已经添加了很多功能到我们的应用程序,但没有添加任何实质的导航来连接到这些功能。自默认建立的应用程序开始首页没有发生任何改变。自我们建立应用程序开始,导航项没有发生改变。我们需要改变基本导航来更好的反映现在应用程序中的各个功能。

至此,我们并未完全提及我们的应用程序是如何调用用来呈现内容的view文件的。我们知道view文件是用来显示数据和每次页面请求的HTML回发内容的。当我们建立新Controller(控制器)的actions(行为)时,我们也会建立相关的view文件,来承载actions返回的显示内容。大多数view文件只针对单一action存在,没有通用性。然而,有一些,例如主导航菜单,会在网站中很多页面被重复使用。这类UI组件更适合存放置被我们称为布局文件的文件中。

Yii中的布局文件,是一种用来装饰其他view文件的一种特殊的view文件。一般来说布局会包含修饰部分或者包含其他view文件的用户界面元素。在使用布局文件渲染一个view文件时,Yii会将该view文件装入布局。

定制一个布局

定制一个布局主要分为2步操作。其一是CWebApplication的$layout属性。默认值指向protected/views/layouts/main.php。这一项为默认设置,可以在位于protected/config/main.php的主配置文件里进行修改。例如我们在protected/views/layouts/newlayout.php新建了一个配置文件,并且想将其设置为应用程序级布局文件,我们需要按如下方式修改主配置文件修改layout属性:

return array(
    'layout'=>'newlayout',

文件名并不包含.php后缀,并且将CWebApplication的$layoutPath属性默认关联到Webroot/protected/views/layouts(如果你的应用程序路径有所改变,可以使用相同的方式重写该值)。

另外一个需要指定布局的地方是设置Controller(控制器)类$layout属性。这样就允许针对不同Contorller(控制器)以更细的粒度进行设置。这就是在建立最初程序时定制的方法。当使用yiic工具来建立最初应用程序时,基于Webroot/protected/components/Controller.php来自动创建一个Controller(控制器)。打开该文件你将看到$layout属性被设置为”column1”。在Controller(控制器)级对布局文件的设置将覆盖CWebApplication类中的设置。

添加并使用一种布局

在调用CController::render()方法来使用一个布局文件。也就是说,当你调用render()方法来渲染一个view文件时,Yii将会将view文件的内容嵌入controller类或应用程序级设置的layout文件中。你可以通过调用CController::renderParial()方法实例来避免应用layout装饰。

正如前面所说,一个布局文件一般被用来装饰其他view文件。一个使用布局的例子就是每个页面提供相同的页头和页脚。当render()方法被调用,后台先针对某个view文件调用renderPartial()。这一步的输出被保存在一个叫$content的变量中,并且在接下来的布局文件中可用。因此,一个简单的布局文件可能是下面的样子:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="language" content="en" /> 
    </head>
    <body> 
        <div id="header">Some Header Content Here </div>
        <?php echo $content; ?>
        <div id="footer"> Some Footer Content Here</div> 
    </body> 
</html>

让我们动手验证一下。新建一个叫newlayout.php的文件,并将其放入默认布局文件位置:/protected/views/layouts/。将上面的HTML添加进去后保存。下面我们将通过修改我们的站点controller(控制器)来使用这个新布局。打开SiteController.php然后重载layout属性设置,添加如下代码:

class SiteController extends Controller 
{
    public $layout='newlayout';

这样只有这一个controller(控制器)会使用newlayout.php。至此,每一次基于SiteController调用render()方法都会使用newlayout.php布局文件。

登录页面就是由SiteController负责渲染的页面之一。让我们访问一次该页面来确认修改。当我们打开http://localhost/trackstar/site/login(假设我们并未登录),我们将会看到与下图相关的界面:

如果我们简单的注释掉我们之前添加的$layout属性,然后刷新登录页面,我们将重新使用原始的main.php布局,并且页面也变回原来的样子了。

重构main.php布局文件

目前为止,我们所有的应用程序页面都使用了main.php布局文件作为主要的布局标记。在我们着手改动页面布局和设计之前,应该仔细观察一下main 布局文件。下面是该文件的全部内容:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="language" content="en" />
        <!-- blueprint CSS framework -->
        <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/screen.css" 
            media="screen, projection" />
        <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/print.css"
            media="print" />
 
        <!--[if lt IE 8]>
        <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/ie.css" 
            media="screen, projection" />
        <![endif]-->
 
       <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/main.css" />
       <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/form.css" />
 
       <title><?php echo CHtml::encode($this->pageTitle); ?></title> 
    </head>
 
    <body> 
        <div class="container" id="page">
            <div id="header"> 
                <div id="logo"><?php echo CHtml::encode(Yii::app()->name); ?></div> 
            </div>
            <!-- header -->
 
            <div id="mainmenu"> 
            <?php $this->widget('zii.widgets.CMenu',array(
                'items'=>array( 
                    array('label'=>'Home', 'url'=>array('/site/index')), 
                    array('label'=>'About', 'url'=>array('/site/page','view'=>'about')), 
                    array('label'=>'Contact', 'url'=>array('/site/contact')), 
                    array('label'=>'Login', 'url'=>array('/site/login'),
                             'visible'=>Yii::app()->user->isGuest), 
                    array('label'=>'Logout ('.Yii::app()->user->name.')',
                             'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest) ),
                )
            ); ?> 
            </div>
            <!-- mainmenu -->
 
           <?php $this->widget('zii.widgets.CBreadcrumbs', 
                    array( 'links'=>$this->breadcrumbs,
           )); ?>
           <!-- breadcrumbs -->
 
           <?php echo $content; ?>
 
           <div id="footer"> Copyright &copy; <?php echo date('Y'); ?> by My Company.<br/> 
                    All Rights Reserved.<br/> <?php echo Yii::powered(); ?>
           </div><!-- footer -->
        </div><!-- page -->
    </body> 
</html>

我们将从头开始。最开始的5行可能会让你觉得很熟悉。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="language" content="en" />

这几行定义了标准的HTML文件类型申明,接着一个<html>元素开始,接着是<head>元素开始。在<head>标签内,我们首先使用一个meta标签来定义标准,并且这是非常重要的,XHTML编译器使用UTF-8进行编码,接下来的一个<meta>标签申明英文为该网页主要书写语言。

介绍Blueprint CSS框架

接下来以注释开始的几行可能也对你来说很熟悉。Yii的另一个大优势就是其适当利用了其他拥有优良血统的框架,Blueprint CSS 框架就是其中一个例子。

Blueprint CSS 框架在yiic工具初始化应用程序时就被集成到应用程序中。它被用来帮助标准化的CSS开发。Blueprint是一个CSS网格框架。它被用来标准化你的CSS,提供跨浏览器兼容,并且使用标准化HTML元素来减少CSS错误。它拥有基于不同屏幕并且面对打印友好的布局,并且帮助快速实现你的设计,通过它提供的一些你需要的css来帮助css快速实现。获取更多Blueprint framework信息,浏览:http://www.blueprintcss.org/

所以,下面几行代码为Blueprint CSS 框架提供所需支持:

<!-- blueprint CSS framework --> 
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/screen.css" 
    media="screen, projection" /> 
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/print.css" 
    media="print" /> 
<!--[if lt IE 8]> 
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/ie.css" 
    media="screen, projection" /> 
<![endif]-->

理解Blueprint的安装

Yii并没有对Blueprint的使用要求。然而,作为项目默认包含的框架,了解它的安装和使用会很有益。

一般安装Blueprint包含以下步骤:下载框架文件,将其中3个.css文件放入Yii应用程序的主CSS文件夹。如果我们小窥一下我们TrackStar应用程序的主css(Webroot/css )文件夹,我们将看到以下3个文件已经包含进去了:

  • ie.css
  • print.css
  • screen.css

因此,幸运的,在我们使用yiic webapp命令行生成应用程序时,基本安装已经完成了。为了使用这个框架的功能,上面的标签需要被放置在每一个页面的标签中。这也是这些申明被放置在布局文件的原因。

接下来的2个标签:

<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/main.css" /> <link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/form.css" />

定义一些自定义的css用来提供一些额外的布局申明。你必须始终将你定义的css置于Blueprint提供的之下,这样你定义的才可以获得更好的优先级。

设置页面标题

设置不同的并且有意义的页面标题非常重要,可以帮助你的页面在搜索引擎中排序和那些打算收藏该页面到书签的用户。下面的一行定义了页面标题:

<title><?php echo CHtml::encode($this->pageTitle); ?></title>

请牢记view文件中的$this指向渲染该view文件的contorller(控制器)类。$pageTitle属性被定义在Yii的CController基类中,其默认值为controller(控制器)后的action(动作)的名字。这个值很容易在controller类中重置,或者在view文件中重置。

定义一个页面的头部

一个惯例网站被设计拥有很多页面都相同的头部,下面的几行定义了主布局文件的头部内容:

<body> 
    <div class="container" id="page">
        <div id="header"> 
            <div id="logo"><?php echo CHtml::encode(Yii::app()->name); ?></div> 
        </div><!-- header -->

第一个标有”container”类的<div>标签是Blueprint 框架所必须用来显示网格内容的。

再次申明,Yii并未要求使用类似Blueprint CSS框架。只是为了帮助你快速实现你的设计布局。

接下来的三行代码,实现我们在那些页面中看到的顶部内容。它以大写字母的形式应用程序的名字。至此文字‘My Web Application’被显示出来了。到这里估计你有点要抓狂了。虽然我们可能会使用logo图片来做替换,但目前我们只将其替换为该项目的真实名字‘TrackStar’。

我们可以将其直接敲入HTML代码中。但,如果我们以修改应用程序配置的方式实现新名字,这个修改将传播置应用程序中任何一个使用Yii::app()->name被使用的地方。我确定这个修改对你来说非常容易。打开定义应用程序配置信息的主配置文件 /protected/config/main.php 并将‘name ’属性从:

'name'=>'My Web Application',

改为:

'name'=>'TrackStar'

保存文件,刷新你的浏览器,首页头部应该如下图所示:

在上图中我们能立刻发现的是2处被修改了。这一现象只发生在首页内容中,/protected/iews/site/index.php,因为这里也使用了应用程序的name属性。因为我们在应用程序的配置文件中进行了修改,它影响到了这2个地方。

显示导航菜单项

在web应用程序中站点导航经常在多个页面中重复,因此在布局放置导航使之非常容易被重用。下面的代码实现了导航:

<div id="mainmenu"> 
<?php $this->widget('zii.widgets.CMenu',array(
    'items'=>array( 
        array('label'=>'Home', 'url'=>array('/site/index')), 
        array('label'=>'About', 'url'=>array('/site/page', 'view'=>'about')), 
        array('label'=>'Contact', 'url'=>array('/site/contact')), 
        array('label'=>'Login', 'url'=>array('/site/login'),
            'visible'=>Yii::app()->user->isGuest), 
        array('label'=>'Logout ('.Yii::app()->user->name.')',
            'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest) 
    ),
)); ?> 
</div><!-- mainmenu -->

在此处我们使用了官方Zii扩展:CMenu。在第9章我们介绍过Zii。回忆一下,Zii扩展库是Yii开发团队所开发的一系列扩展。它随着Yii框架的安装被安装。在Yii中使用该扩展是非常容易的,只需要简单引用所需扩展类文件,使用zii.path.to.ClassName这样的代理格式。根zii被应用程序预先定义,其他路径是框架的相对文件夹路径。所以,如上所示Zii菜单扩展位于你文件系统 Path-to-your-Yii-Framework/zii/widgets/CMenu.php,在应用程序中可以通过zii.widgets.CMenu的方式来引用。

无需对CMenu有很多的了解,我们会发现它使用一个关联数组,为菜单项提供label和URL来形成一个连接,还有一个布尔值的第三个可选项visible,用来决定菜单项是否显示。这里的‘login’和‘logout’项就是这样操作的。显而易见的,我们只希望用户未登录时显示‘login’菜单项。相反的,只有用户登录后才显示‘Logout’项。visible元素在数组里的使用使得你可以基于用户登录状态动态的控制连接的显示。使用Yii::app()->user->isGuest到达此效果。以游客状态未登录时返回true,用户登录后返回false。在你登录以后‘login’项转换为‘logout’,反之亦然。

让我们更新菜单来导航至特定的TrackStar功能。首先,除了登录我们并不希望匿名用户获得其他实质功能。所以我们要将登录页面设置为匿名用户的首页。同理,已登录用户的首页应该是他们的项目列表。我们将通过以下修改达到上述目标:

  1. 改变项目列表为默认首页,而不是现在的site/index页。
  2. 设置默认controller(控制器)SiteController的默认action(行为)为login。这样任何匿名用户访问顶级域名,http://localhost/trackstar/,将被重定向至登录页。
  3. 修改actionLogin()方法,如果是已登录用户则重定向至项目列表页。
  4. 改变菜单项‘home‘为‘project‘,并更改URL地址为项目列表页。

需要做的修改都非常简单。首先,我们修改应用程序配置文件中的‘homeUrl‘属性。打开protected/config/main.php添加下面的键值对到返回数组:

'homeUrl'=>'/trackstar/project',

这就是修改的全部。

下一个修改,打开protected/controllers/SiteController.php,添加下面一行代码到controller(控制器)类的顶部:

public $defaultAction = 'login';

这样默认action就被修改为‘login’了。现在如果你通过顶级URL,http://localhost/trackstar/ ,访问你的应用程序,你将会看到登录页面。唯一的问题就是不管你是否登录,你看到的都只能是登录页面。我们将通过第三步修改来修正它。修改位于SiteController中的actionLogin()方法,在该方法的开头包含如下代码:

public function actionLogin() 
{
    if(!Yii::app()->user->isGuest) 
    {
        $this->redirect(Yii::app()->homeUrl);
    }

这将会把所有登录用户重定向至应用程序的homeUrl属性,也就是我们之前设置的项目列表页。

最后,让我们修改CMenu部件的输入数组中的‘Home‘菜单项。将如下代码:

array('label'=>'Home', 'url'=>array('/site/index')),

修改为:

array('label'=>'Projects', 'url'=>array('/project')),

通过以上替换,我们之前提出的几处修改都已经完成了。如果现在以匿名用户身份访问TrackStar应用程序,我们将被重定向至登录页。点击Projects连接还是被重定向至登录页。以匿名用户的身份还是可以访问About和Contact页面的。登录后将被重定向至项目列表页。现在点击Projects连接,我们将能看到项目列表。

建立面包屑导航

回到main.php布局文件,在menu部件之后的3行代码定义了一个叫CBreadcrumbs的Zii扩展:

<?php $this->widget('zii.widgets.CBreadcrumbs', array( 
    'links'=>$this->breadcrumbs,
)); ?><!-- breadcrumbs -->

这个部件用来显示用来标识当前页面位置的一系列连接,这个位置相对与整个网站中的其他页面。例如,如下形式的连接导航列表:

Projects >> Project 1 > > Edit

表示用户正在浏览Edit页面,该页面属于Project1。这可以很好的帮助用户回到出发的地方,这里是项目列表,同时也可以很好的看出网站页面之间的继承关系。这也是其被叫做面包屑的原因。很多网站都使用了这种UI导航元素。

为了使用该部件,我们需要配置它的links属性,也就是设置被显示的连接。这里的预期值为一个数组,该数组标识了从开始到当前页面的路径关系。利用上面的例子,我们可以如下定义连接数组:

array( 
    'Projects'=>array('project/index'), 
    'Project 1'=>array('project/view','id'=>1), 
   'Edit', 
)

面包屑部件会默认自动添加最顶级的‘Home‘连接,该连接的值基于应用程序配置中设置的homeUrl。所以上面的面包屑会是如下的样子:

Home >> Projects >> Project 1 >> Edit

因为我们将应用程序的$homeUrl属性设置为项目列表页,所以在这里前2个连接是相同的。在布局文件使用渲染该view文件controller的$breadcrumbs属性来设置link属性。你会发现许多是哟个Gii代码生成工具自动生成的contorller的view文件都是这样设置的。列入,如果你打开protected/views/project/update.php,你会在顶部发现如下代码:

<?php 
$this->breadcrumbs=array(
    'Projects'=>array('index'), 
    $model->name=>array('view','id'=>$model->id), 
    'Update',
);

如果我们导航至该页面,我们会在主导航下面看到如下的面包屑导航:

指定被布局所修饰的内容

布局文件中的下一行,就是显示被该布局文件修饰的view文件内容:

<?php echo $content; ?>

正如本章早些被提到的,当你在controller类使用$this->render()来显示一个视图文件,布局文件也被隐式执行。这个方法执行的步骤就是,就是在布局文件中的$content中显示对应视图文件的内容。如果再拿项目更新视图文件举例,$content的内容就应该是 protected/views/project/update.php文件渲染的结果。

定义页脚

如同页头部分,一般来说网页都会在不同页面拥有相同的页脚,main.php布局文件的最后几行实现了该功能:

<div id="footer"> 
    Copyright &copy; <?php echo date('Y'); ?> by My Company.<br/> 
    All Rights Reserved.<br/> 
    <?php echo Yii::powered(); ?>
</div><!-- footer -->

这里没有什么特殊的,更新为我们网站的相关信息即可。我们可以保留Powered by Yii Framework这一行来帮助Yii的推广。所以,简单的修改‘My Company’为‘TrackStar’,一切都完成了。刷新后页脚如下图所示:

嵌套布局

位于protected/layouts/main.php中的原始布局文件并不是全部的布局文件。在我们的初始应用程序被建立时,所有的controllers(控制器)都被设计成继承自protected/components/Controller.php。如果我们观察一下该文件,我们将发现layout属性被直接定义。但是值并不是main布局文件,而是‘column1’。你也许会发现应用程序建立时protected/views/layouts/文件夹如下:

  • column1.php
  • column2.php
  • main.php

因此,除非在子类中重写该属性,我们的controller都将使用column1.php为主布局文件,而不是main.php。

你也许会问,为什么要大费周章的介绍main.php?因为column1.php是基于main.php实现布局的。也就是说并非view文件可以使用布局文件实现布局,布局文件也可以使用其他布局文件实现布局。这为设计的实现提供了极大的灵活性,同时也减少了view文件之间的代码重复。让我们仔细观察一下column1.php看看是如何实现的。

文件内容如下:

<?php $this->beginContent('/layouts/main'); ?> 
<div class="container">
    <div id="content"> 
        <?php echo $content; ?>
    </div><!-- content --> 
</div>
<?php $this->endContent(); ?>

正如我们所见,使用了一些我们没有见过的方法。基类方法中的beginContent()和endContent()被用来显示被附加上的特殊视图内容。这里特殊显示的是我们的主布局文件‘layouts/main’。beginContent()方法实际使用了Yii内置部件CContentDecorator,该部件主要用于布局文件嵌套。因此介于beginContent()和endContent()之间的内容将作为输出内容显示在beginContent()中指定的view文件中。如果没有指定,将会使用controller级的默认布局文件,如果controller级没有指定将使用应用程序级。

嵌套工作原理如同一个普通的布局文件。当column1.php被渲染的时候,view文件输出内容将保存至$content中,最终这个$content将会传递至main.php布局文件中。

以渲染登录试图来举例,以下代码出现在SiteController::actionLogin()方法中:

$this->render('login');

后台执行步骤如下:

  1. 渲染/protected/views/site/login.php的内容,存储至controller级布局文件可以使用的$content中,本例中为column1.php。
  2. 在column1.php文件中使用了main.php布局文件,beginContent()和endContent()之间的内容做渲染,然后通过$content传递至main.php。
  3. main.php布局文件,进行渲染,并将结果返回至用户,该结果包含了来自指定view文件登录页面的内容也包括了嵌套布局文件column1.php的内容。

另一个在项目中使用的自动生成布局文件为column2.php。你或许一点都不觉得奇怪,该文件使用2列布局。我们可以在项目页找到它,在项目页子菜单Operations部件一直显示在右边。下面为该文件内容,我们可以清晰的看到嵌套布局:

<?php $this->beginContent('/layouts/main'); ?> 
<div class="container">
    <div class="span-19"> 
        <div id="content">
            <?php echo $content; ?> 
        </div><!-- content -->
    </div> 
    <div class="span-5 last">
        <div id="sidebar"> 
        <?php
            $this->beginWidget('zii.widgets.CPortlet', array( 
                'title'=>'Operations',
            )); 
 
            $this->widget('zii.widgets.CMenu', array(
                'items'=>$this->menu,
                'htmlOptions'=>array('class'=>'operations'), 
            ));
            $this->endWidget(); 
        ?>
        </div><!-- sidebar --> 
    </div>
</div> 
<?php $this->endContent(); ?>

创建主题

主题提供了一个体系化的解决方案,来设计网页应用程序的布局。MVC结构的最大好处就是就是将展示层从后台分离出来。主题极大的利用了该分离,允许你随时随地轻松且动态更换网站的整体外观。Yii允许使用一种非常简单的应用程序主题,来为web设计提供极大的自由度。

在Yii中创建主题

在Yii中每一个主题都是由一系列包含view文件,布局文件,相关资源文件,如:图片,CSS,JavaScipt文件等的文件夹集合。主题的名字就是该文件包 (最外层文件包) 的名字。默认的,所有主题都被放置在WebRoot/themes路径下。当然,该路径可以在应用程序中进行设置。你只需要修改themeManager应用程序组件的basePath和baseUrl属性值就可以完成该修改。

一个主题文件夹下内容组织形式应该与应用程序基本路径相同。例如,所有的视图文件必须位于views/下,布局视图文件应该位于views/layouts/下,系统视图文件应当位于views/system/下。举例说明,如果我们创建了一个主题,命名为custom,并且我们想在该主题下建立新的ProjectController的update view,我们需要在应用程序的themes/custom/views/project/update.php建立一个新view文件。

创建一个Yii的主题

让我们动动手,给我们的TrackStar做个小整容手术。我们需要给主题命名,并且在Webroot/themes下建立拥有该名称的文件夹。我们将会创造一些新东西,并且为我们的新主题起名:new。

在Webroot/themes/new建立新目录,同时在该目录下建立2个新文件夹,分别叫css/和views/。前面的文件夹不是主题系统必须的,但是可以帮助我们组织好CSS文件。后面的文件夹是必须的,所有针对默认view文件的修改都在这里进行。我们将对main.php布局文件进行小小修改,我们需要在views/路径建立文件夹layouts(请记住文件结构需要同Webroot/protected/views/相同)。

是时候动手做些事情了。因为我们的view文件的标记语言已经应用了定义在Webroot/css/main.css文件中的类和ID,最快的方式就是以此作为起点,然后对其进行一些修改来实现新设计。当然这不是必须的,我们可以在新主题中重建每一个单独的view文件。但是,为求简单,我们将通过对main.css文件进行修改来实现新主题,该文件是在自动生成主布局文件main.php时同时生成的。

动手拉!让我们将上述2个文件复制到我们的新主题文件夹。复制Webroot/css/main.css 到 Webroot/themes/new/css/main.css,并且也复制Webroot/protected/views/layouts/main.php 到 Webroot/themes/ new/views/layouts/main.php。

现在,打开新复制得到的main.css文件,使用以下内容替换:

body 
{
    margin: 0; 
    padding: 0; 
    color: #555; 
    font: normal 10pt Arial,Helvetica,sans-serif; 
    background: #d6d6d6 url(background.gif) repeat-y center top;
}
 
#page 
{
    margin-bottom: 20px; 
    background: white; 
    border: 1px solid #898989; 
    border-top:none; 
    border-bottom:none;
}
 
#header 
{
    margin: 0; 
    padding: 0; 
    height:100px; 
    background:white url(header.jpg) no-repeat left top; 
    border-bottom: 1px solid #898989;
}
 
#content 
{
    padding: 20px; 
}
 
#sidebar
{
    padding: 20px 20px 20px 0;
}
 
#footer 
{
    padding: 10px; 
    margin: 10px 20px; 
    font-size: 0.8em; 
    text-align: center; 
    border-top: 1px solid #C9E0ED;
}
 
#logo 
{
    padding: 10px 20px; 
    font-size: 200%; 
    /* HIDES LOGO TEXT */ 
    text-indent:-5000px;
}
 
#mainmenu 
{
    background:white url(bg2.gif) repeat-x left top; 
    border-top:1px solid #CCC; 
    border-bottom: 1px solid #7d7d7d;
}
 
#mainmenu ul 
{
    padding:6px 20px 5px 20px; 
    margin:0px;
}
 
#mainmenu ul li 
{
    display: inline;
}
 
#mainmenu ul li a 
{
    color:#333; 
    background-color:transparent; 
    font-size:12px; 
    font-weight:bold; 
    text-decoration:none; 
    padding:5px 8px;
}
 
#mainmenu ul li a:hover, #mainmenu ul li a.active 
{
    color: #d11e1e;
    background-color:#ccc; 
    text-decoration:none;
}
 
div.flash-error, div.flash-notice, div.flash-success 
{
    padding:.8em; 
    margin-bottom:1em; 
    border:2px solid #ddd;
}
 
div.flash-error 
{
    background:#FBE3E4; 
    color:#8a1f11; 
    border-color:#FBC2C4;
}
 
div.flash-notice 
{
    background:#FFF6BF; 
    color:#514721; 
    border-color:#FFD324;
}
 
div.flash-success 
{
    background:#E6EFC2; 
    color:#264409; 
    border-color:#C6D880;
}
 
div.flash-error a 
{
    color:#8a1f11;
}
 
div.flash-notice a 
{
    color:#514721;
}
 
div.flash-success a 
{
    color:#264409;
}
 
div.form .rememberMe label 
{
    display: inline;
} 
 
div.view 
{
    padding: 10px; 
    margin: 10px 0; 
    border: 1px solid #C9E0ED;
}
 
div.breadcrumbs 
{
    font-size: 0.9em; 
    padding: 10px 20px;
}
 
div.breadcrumbs span 
{
    font-weight: bold;
}
 
div.search-form 
{
    padding: 10px; 
    margin: 10px 0; 
    background: #eee;
}
 
.portlet 
{
}
 
.portlet-decoration 
{
    padding: 3px 8px; 
    background:white url(bg2.gif) repeat-x left top;
}
 
.portlet-title 
{
    font-size: 12px; 
    font-weight: bold; 
    padding: 0; 
    margin: 0;
    color: #fff;
}
 
.portlet-content 
{
    font-size:0.9em; 
    margin: 0 0 15px 0; 
    padding: 5px 8px; 
    background:#ccc;
}
 
.operations li a 
{
    font: bold 12px Arial; 
    color: #d11e1e; 
    display: block; 
    padding: 2px 0 2px 8px; 
    line-height: 15px; 
    text-decoration: none;
}
 
.portlet-content ul 
{
    list-style-image:none; 
    list-style-position:outside; 
    list-style-type:none; 
    margin: 0;
    padding: 0;
}
 
.portlet-content li 
{
    padding: 2px 0 4px 0px;
}
 
.operations 
{
    list-style-type: none; 
    margin: 0; 
    padding: 0;
}
 
.operations li 
{
    padding-bottom: 2px;
}
 
.operations li a 
{
    font: bold 12px Arial; 
    color: #0066A4; 
    display: block; 
    padding: 2px 0 2px 8px; 
    line-height: 15px; 
    text-decoration: none;
}
 
.operations li a:visited 
{
    color: #d11e1e;
}
 
.operations li a:hover 
{
    background: #fff;
}

你可能发现修改中使用到的一些图片文件并不存在。我们添加了background.gif图片到body定义中,一个新的bg2.gif图片到#mainmenu ID定义中,一个新的header.jpg图片到#header ID定义中。可以通过如下连接进行查看和下载:http://www.yippyii.com/trackstar/themes/new/css/background.gif, http://www.yippyii.com/trackstar/themes/new/css/bg2.gif, http://www.yippyii.com/trackstar/themes/new/css/header.jpg

我们需要将上面3个图片复制到位于Webroot/themes/new/css/的CSS文件夹下。

上面的操作都完成后,我们需要对我们新主题的layout文件main.php进行少量调整。首先,我们要修改中的标记语言来引用我们新mian.css文件,未修改前main.css文件引用方式如下:

<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >request->baseUrl; ?>/css/main.css" />

引用了基于应用程序(application)请求(request)baseUrl属性构成的CSS文件相对路径。然而我们想使用位于新主题目录下的新main.css文件。所以我们可以使用位于Yii内置CThemeManager.php类的theme manager(主题管理)应用程序组件。如访问其他应用程序组件一样访问theme manager。因此我们将使用theme manager中的url来替换基于请求的url。所以我们对/themes/new/views/layouts/main.php如下修改:

<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()- >theme->baseUrl; ?>/css/main.css" />

一旦我们配置应用程序使用新主题(这一步还没有完成),这里的baseUrl将会解析为我们主题文件夹的相对路径。

另外一处小修改是,移除页头显示的应用程序标题。因为我们在此处使用了图片,所以无需再使用标题。因此,还是/themes/new/views/layouts/main.php,我们只需修改这里:

<div id="header"> 
    <div id="logo"><?php echo CHtml::encode(Yii::app()->name); ?></div>
</div><!-- header -->

为下面的样子:

<div id="header"></div><!-- header image is embeded into the #header declaration in main.css -->

在此我们使用了注释提示我们页头的图片定义的位置。

最后一处修改是针对应用程序中使用的另外2个布局文件,我们并未将其复制到新主题目录,他们是:protected/views/layouts/column1.php和protected/views/layouts/column2.php。如前面嵌套布局所述,这2个布局通过beginContent()和endContent()调用了main布局文件。这些文件是通过Gii自动代码生成工具生成的,并且直接指向protected/views/layouts/文件夹。我们需要对beginContent()方法进行修改,使之对我们的新主题的布局有效。分别打开column1.php和column2.php,将下面的代码:

$this->beginContent('application.views.layouts.main');

修改为:

$this->beginContent('/layouts/main');

现在,一旦我们配置应用程序使用新主题,它将使用新主题文件夹下的mian.php布局文件。

为应用程序配置新主题

好啦,随着我们新主题的建立完成,是时候告诉应用程序使用它了。操作非常之简单。我们只需要修改主配置文件中Application(应用程序)的theme属性。这一操作已经非常熟悉了。在/protected/config/main.php文件的返回数组中添加如下键值对:

'theme'=>'new',

保存之后,名为new的新主题已经被我们的应用程序使用了,并且我们的应用程序已经有了一个全新的样子。观察一下非登录状态下的首页,也就是登录页面,如下图所示:

当然整体改动非常小。我们用最小的改动来创建了一个新主题。应用程序会优先使用主题文件夹下的view文件,如果没有找到才会使用默认路径下的。你可以看到换个皮肤如此的简单。你可以随心所欲的做这件事。

将网站翻译至其他语言

在完成本次迭代之前,我们将要讨论一下Yii中的i18n(国际化)和L10n(本地化)。国际化就是一个使软件程序自动适应不同语言而不需要修改底层引擎的过程。本地化就是使国际软件适应某一区域或者某一语言的过程。Yii通过如下方面实现对他们的支持:

  • Yii为每一种语言和区域提供本地化数据支持
  • Yii提供相关服务帮助翻译文字信息和文件
  • Yii提供语言相关的日期和时间格式化
  • Yii提供语言相关的数字格式化

定义区域和语言

区域是指一系列定义用户语言,国家,和其他用户界面预设信息(例如,用户所在地)的参数。一般来说是由语言和区域标识组成的ID。举例说明,区域ID en_US 代表英语语言,所在区域代表美国。约定俗成的,Yii中所有的区域ID都使用小写的语言ID或语言ID_区域ID(例如, en, en_us)。

在Yii中,本地化数据是CLocal类或其子类的一个实例。它提供了区域化信息,包括货币和数字符号;货币,数字,日期,时间格式;日期描述名称比如月份,星期几,等等。给定一个区域ID,就可以通过静态方法CLocal::getInstance($localeID)或应用程序获得相关CLocal实例。下面就是一个使用应用程序组件获得en_us区域ID实例的例子:

Yii::app()->getLocale('en_us');

Yii几乎包含了所有语言和区域的本地化数据。数据都是从通用语言环境数据信息库(CLDR) (http://cldr.unicode.org/)并且按照其相关语言ID进行命名后储存在Yii 框架的framework/i18n/data/目录。因此,按照上面的例子创建一个新的CLocale实例,用来填充属性的数据来自framework/i18n/data/en_us.php文件。如果你打开这一文件夹,你会发现大量语言和地区信息文件。

现在,回到我们的例子,如果我们想获取和显示月份的名字以英语按照US(美国)地区标准,我们需要执行以下代码:

$locale = Yii::app()->getLocale('en_us'); 
print_r($locale->monthNames);

将会产生如下输出:

Array ( [1] => January [2] => February [3] => March [4] => April [5] => May [6] => June [7] => July [8] => August [9] => September [10] => October [11] => November [12] => December )

正如我们所说的,意大利语的月份名,使用相同的方式,只是实例化不同的CLocale:

$locale = Yii::app()->getLocale('it'); 
print_r($locale->monthNames);

将会产生如下输出:

Array ( [1] => gennaio [2] => febbraio [3] => marzo [4] => aprile [5] => maggio [6] => giugno [7] => luglio [8] => agosto [9] => settembre [10] => ottobre [11] => novembre [12] => dicembre )

第一个实例是基于信息文件framework/i18n/data/en_us.php,后一个是基于framework/i18n/data/it.php。如果有必要,应用程序的localeDataPath可以被配置到任何用户自定义时间信息文件。

实现语言切换

或许i18n最值得期待的功能就是语言切换了。如前面所说的,Yii提供了信息翻译和视图翻译2种。前一种将单独文本消息翻译为目标语言的,后者将整个view文件翻译成目标语言的。

一个翻译请求由被翻译的对象(一段文字或整个文件),该对象的语言以及目标语言组成。在Yii应用程序中源语言和目标语言是不同的。目标语言是我们针对一类目标用户由书写应用程序的源语言翻译而来的语言。目前为止我们的TrackStar应用程序是由英文书写并且正对英文用户。所以目前为止我们的目标和源语言都是相同的。Yii的国际化功能是在针对2种不同语言的情况下进行翻译。

实现消息翻译

消息翻译是通过调用以下应用程序方法:

t(string $category, string $message, array $params=array ( ), string $source=NULL, string $language=NULL)

这个方法将消息由源语言翻译到目标语言。

当翻译一条消息的时候,category(类别)必须指出,才可以实现同一条消息在不同类别实现不同翻译。类别yii在Yii框架核心代码中被预留。

消息可以包含占位符参数,该参数将在Yii::t()被调用时被替换。下面的一个例子实现了对错误信息的翻译。在该消息被翻译的时候{errorCode} 占位符需要被$errorCode的实际值替换:

Yii::t('category', 'The error: "{errorCode}" was encountered during the last request.',    array('{errorCode}'=>$errorCode));

被翻译的消息被存储在一个叫message source(消息源)的信息仓库中。一个消息源代表CMessageSource或者它子类的实例。一旦Yii::t()被调用,将会在消息源中查找消息,如果找到并将返回结果。

Yii拥有以下几种消息源:

  • CPhpMessageSource:这个为默认的消息源。可以被翻译的消息以键值对的形式存储在PHP数组中。原始消息为key,被翻译的消息为value。每一个数组代表实际的类别信息,并且存储在以类别名命名的单独PHP脚本文件中。相同语言的PHP翻译文件储存在以该语言ID命名的文件夹下。所有的文件夹都存储在路径basePath下。
  • CGettextMessageSource:可翻译被存储在GNU Gettext文件中。
  • CDbMessageSource:可翻译信息存储在数据库表中。

一个消息源以应用程序组件的形式被加载。Yii会预先定义一个叫message的应用程序组件来存储用于用户应用程序的信息。默认的,该消息源是一个CPhpMessageSource类型并且PHP翻译文件的存储路径为protected/message。

举一个例子来详细说明,让我们来翻译登录表单的标签内容到一个杜撰的叫Reversish。Reversish就是将英文单词或短语逆向书写。下面是登录表单的Reversish翻译关系。

EnglishReversish
UsernameEmanresu
PasswordDrowssap
Remember me next timeEmit txen em rebmemer

我们将使用默认的CPhpMessageSource来存放翻译消息。所以第一步我们需要做的就是新建一个PHP文件来存放我们的翻译。我们将使用语言ID ‘rev’并且使用‘default’分类。所以我们需要在消息基本目录下建立如下格式的文件夹/localeID/CategoryName.php。按照这个例子,我们的新文件应该存放在/protected/messages/rev/default.php,并且置入如下翻译数组:

<?php return array(
    'Username' => 'Emanresu', 
    'Password' => 'Drowssap', 
    'Remember me next time' => 'Emit txen em rebmemer',
);

接下来我们需要做的是将应用程序的目标语言设置为Reversish。我们可以在应用程序配置文件进行设置,这样该设置将会对整个网站起作用。但是我们只想翻译登录表单,所以我们只需要在SiteController::actionLogin()方法内进行设置,所以该设置只会在渲染登录表单时有效。所以打开该文件,在方法的开头按照如下形式设置目标语言:

public function actionLogin() 
{
    Yii::app()->language = 'rev';

最后我们需要做的就是调用Yii::t()方法来激活翻译。这些表单的标签被定义在LoginForm::attributeLabels()方法中。按照如下格式重写该方法:

/** 
   * Declares attribute labels. 
   */
public function attributeLabels() 
{
    return array( 
        'rememberMe'=>Yii::t('default','Remember me next time'), 
        'username'=>Yii::t('default', 'Username'), 
        'password'=>Yii::t('default', 'Password'),
    );
}

现在如果你访问登录表单,Reversish语言的登录表单将会如图所示显示:

执行文件翻译

Yii也提供了根据设置的目标语言ID使用不同文件的能力。文件级翻译通过调用应用程序方法CApplication::findLocalizedFile()来实现。该方法需要一个文件路径并且会使用相同的名字查找文件,而不是使用一个直接输入的目标语言ID或者由应用程序输入这个ID来进行目录式查找。

让我们试一下。我们所需要做的就是创建所需的翻译文件。依旧是翻译登录表单。所以在/protected/views/site/rev/login.php建立新的view文件,同时将下面的已经被翻译为Reversish的内容复制进去:

<?php 
$this->pageTitle='Nigol'; 
$this->breadcrumbs=array(
    'Nigol',
); 
?>
 
<h1>Nigol</h1> 
 
<p>Slaitnederc nigol ruoy htiw mrof gniwollof eht tuo llif esaelp:</p>
 
<div class="form"> 
<?php $form=$this->beginWidget('CActiveForm', array(
    'id'=>'login-form',
    'enableAjaxValidation'=>true, )); 
?>
    <p class="note">Deriuqer era <span class="required">*</span> htiw sdleif.</p>
    <div class="row"> 
        <?php echo $form->labelEx($model,'username'); ?> 
        <?php echo $form->textField($model,'username'); ?> 
        <?php echo $form->error($model,'username'); ?>
    </div>
    
    <div class="row"> 
        <?php echo $form->labelEx($model,'password'); ?> 
        <?php echo $form->passwordField($model,'password'); ?> 
        <?php echo $form->error($model,'password'); ?> 
        <p class="hint">
            <tt>nimda\nimda</tt> ro <tt>omed\omed</tt> htiw nigol yam uoy:tnih 
        </p>
    </div>
    
    <div class="row rememberMe"> 
        <?php echo $form->checkBox($model,'rememberMe'); ?> 
        <?php echo $form->label($model,'rememberMe'); ?> 
        <?php echo $form->error($model,'rememberMe'); ?>
    </div>
    
    <div class="row buttons"> 
        <?php echo CHtml::submitButton('Nigol'); ?>
    </div>
    
    <?php $this->endWidget(); ?>
</div><!-- form -->

我们已经在SiteController::actionLogin()中设置了目标语言,同时调用本地化文件会在render('login')的时候后台执行。所以,当这些都完成以后,我们的登录表单会显示如下图所示的样子:

小结

在本次迭代中,我们知道了Yii如何迅速且容易的使我们的设计更完美。我们介绍了布局这个概念,并且了解了如何在一个应用程序的多个不同页面通过布局实现设计的统一风格。在这里我们同样介绍了2个内置部件CMenu和CBreadcrumbs,通过这2个部件可以轻松的实现Yii内部导航功能。

接着我们介绍了基于Web应用程序的主题的概念,同时介绍了如何在Yii中实现他们。它允许我们轻松的为Yii应用程序更换皮肤,通过它你可以重新设计你的应用程序而无需对后台功能进行重构。

最后我们学习了如果通过i18n来对网站语言进行切换。我们学习了如何设置应用程序的目标语言信息,通过它我们可以实现本地化设置和语言翻译。

在本章和之前的章节我们都介绍了modules(模块)的概念,但是都没有深入讲解在Yii的具体实现。这就是我们下一章的重点。

评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com