tjtjtjのメモ

自分のためのメモです

yiiext/twig-rendererのビューでメッセージを扱いたい

これをどうするか

<?php echo Yii::t('yii','{attribute} is invalid.', array('{attribute}'=>'hoge')); ?>

Yii クラスをアクセス可能にする

Yiiクラス を globals に登録する

protected/config/main.php

        'viewRenderer'=>array(
            'class'=>'ext.etwigviewrenderer.ETwigViewRenderer',
            'globals' => array(
                'Yii' => 'Yii',
            ),
        ),

ビュー

{{ Yii.t('yii','{attribute} is invalid.', {'{attribute}':'hoge'}) }}

あるいは Yii::t メソッドをアクセス可能にする

Yii::tメソッド を functions に登録する

config/main.php

        'viewRenderer'=>array(
            'class'=>'ext.etwigviewrenderer.ETwigViewRenderer',
            'functions' => array(
                't' => 'Yii::t',
            ),
        ),

ビュー

{{ t('app','{attribute} is invalid.', {'{attribute}':'hoge'}) }}


どちらも Yii::t と短かったのでお得感は少ないかな。

yiiext/twig-rendererでページの一部分をテンプレート化したい

yii は $this->rendarPartial ですが、Twig はincludeタグで書きます。

<?php $this->renderPartial('_view', array('data'=>$model)); ?>
↓↓↓↓↓
{% include '/views/post/_view.twig' with {'data':model} %}

見たまんま書き換えできると思います。そしてrenderPartialでなくていいのかとつぶやくはず。私はつぶやきました。

yiiext/twig-rendererでビューテンプレートを継承したい

追記 2012.03.15

やだなーと思っていた{{void(...)}}ですが、{%do...%}で書くべきと気がつき、修正しました。


yiiのスケルトンappでは ページレイアウトの大枠を layouts/main.php、内部のカラム構造を layouts/column1.php, column2.php に実装しています。Twig ではこれをテンプレート継承で実装します。yiiext/twig-rendererというかTwigを使うならやはりテンプレート継承を使って実装したいところ。
yiiのスケルトンappからの書き換えはおもったより簡単です。こちらが参考になりました。

サンプルコード

親テンプレート

子に委譲するところをblockにする

protected/views/layouts/main.twig のごく一部

    <?php echo $content; ?>
↓↓↓↓↓↓↓
    {% block container %}
    {% endblock container %}
子テンプレート

親の上書きしたいブロックを実装する。{{content|raw}}にはコントローラで$this->render()した結果をはめ込みます。ここはyii風。

protected/views/layouts/column1.twig

{% extends 'views/layouts/main.twig' %}

{% block container %}
<div class="container">
    <div id="content">
    {% block content %}
    {{content|raw}}
    {% endblock content %}
    </div><!-- content -->
</div>
{% endblock container %}

protected/views/layouts/column2.twig

{% extends 'views/layouts/main.twig' %}

{% block container %}
<div class="span-19">
    <div id="content">
        {% block content %}
        {{content|raw}}
        {% endblock content %}
    </div><!-- content -->
</div>
<div class="span-5 last">
    <div id="sidebar">
    {% do this.beginWidget('zii.widgets.CPortlet', {
        'title':'Operations',
    }) %}
    {% do this.widget('zii.widgets.CMenu', {
        'items':this.menu,
        'htmlOptions':{'class':'operations'}
    }) %}
    {% do this.endWidget %}
    </div><!-- sidebar -->
</div>
{% endblock container %}

yiiext/twig-rendererでWidgetを使いたい

追記 2012.03.15

やだなーと思っていた{{void(...)}}ですが、{%do...%}で書くべきと気がつき、修正しました。


ほぼ前のと同じですが、素直に this.beginWidgetでもうまくいきました。
前の{% set form = this.createWidget() %}...{{ form.run }}は何かの役に立つかもしれないので残しておきます。

サンプルコード

ポイント

  • void を利用して {% do %}でコントローラー pageTile, breadcrumbs に代入
  • Controller#setBreadcrumbs を追加する
  • this.beginWidget の 戻り値を form に代入する
  • this.endWidget は{% do %} void中 に書く

protected/views/site/login.twig

{% do this.setPageTitle(App.name~' - Login') %}
{% do this.setBreadcrumbs({0:'Login'}) %}

<h1>Login</h1>

<p>Please fill out the following form with your login credentials:</p>

<div class="form">

{% set form = this.beginWidget('system.web.widgets.CActiveForm', {
    'id':'login-form',
    'enableClientValidation':true,
    'clientOptions':{
        'validateOnSubmit':true,
    },
}) %}

    <p class="note">Fields with <span class="required">*</span> are required.</p>

    <div class="row">
        {{ form.labelEx(model,'username')|raw }}
        {{ form.textField(model,'username')|raw }}
        {{ form.error(model,'username')|raw }}
    </div>

    <div class="row">
        {{ form.labelEx(model,'password') |raw}}
        {{ form.passwordField(model,'password')|raw }}
        {{ form.error(model,'password')|raw }}
        <p class="hint">
            Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>.
        </p>
    </div>

    <div class="row rememberMe">
        {{ form.checkBox(model,'rememberMe')|raw }}
        {{ form.label(model,'rememberMe')|raw }}
        {{ form.error(model,'rememberMe')|raw }}
    </div>

    <div class="row buttons">
        {{ C.html.submitButton('Login') |raw }}
    </div>

{% do this.endWidget %} 
</div><!-- form -->


void はphpコードが必要なとき多用するかもしれない。contactページではこんな感じで使います。すみません {% do ... %} でお願いします。

<?php $this->widget('CCaptcha'); ?>
↓
{{ void(this.widget('CCaptcha')) |raw }}
↓
{% do this.widget('CCaptcha') %}

yiiext/twig-renderer

beignWidget/endWidgetでも書けます。->こちら
ここのcreateWidget/wedgit.run は何かの役に立つ(アンチパターンとか)かもしれないので残しておきます。

検証中のコードですがおいておきます。
{% set form = this.createWidget() %} すればwidgetをあきらめなくてもよさげです。this.endWidget のかわりに form.run してます。

<h1>Login</h1>

<p>Please fill out the following form with your login credentials:</p>

<div class="form">

{% set form = this.createWidget('system.web.widgets.CActiveForm', {
	'id':'login-form',
	'enableClientValidation':true,
	'clientOptions':{
		'validateOnSubmit':true,
	},
})  %}

	<p class="note">Fields with <span class="required">*</span> are required.</p>

	<div class="row">
		{{ form.labelEx(model,'username')|raw }}
		{{ form.textField(model,'username')|raw }}
		{{ form.error(model,'username')|raw }}
	</div>

	<div class="row">
		{{ form.labelEx(model,'password') |raw}}
		{{ form.passwordField(model,'password')|raw }}
		{{ form.error(model,'password')|raw }}
		<p class="hint">
			Hint: You may login with <tt>demo/demo</tt> or <tt>admin/admin</tt>.
		</p>
	</div>

	<div class="row rememberMe">
		{{ form.checkBox(model,'rememberMe')|raw }}
		{{ form.label(model,'rememberMe')|raw }}
		{{ form.error(model,'rememberMe')|raw }}
	</div>

	<div class="row buttons">
		{{ C.html.submitButton('Login') |raw }}
	</div>

{{ form.run }}
</div><!-- form -->

モデルを使わないCGridViewサンプル

続いてモデルを使わずzii.widgets.CGridtViewを使ってみる。

サンプルコード

ボタンカラムは、データ各要素にprimaryKeyプロパティを要するのでコメントアウトしています。もちろん回避する方法はあるだろうが今回そこまでやらない。

protected/controllers/SiteController.php

<?php
    public function actionIndex()
    {
        $data = array(
            (object)array('id'=>1,'title'=>'アルファ','create_time'=>time(),
                'content'=>'<div>content</div>',
                'category'=>(object)array('name'=>'コメディ'),
                'author'=>(object)array('username'=>'アルファのauthor'),
            ),
            (object)array('id'=>2, 'title'=>'ベータ', 'create_time'=>time(),
                'content'=>'<div>content</div>',
                'category'=>(object)array('name'=>'アクション'),
                'author'=>(object)array('username'=>'ベータのauthor'),
            ),
            (object)array('id'=>3, 'title'=>'チャーリー', 'create_time'=>time(),
                'content'=>'<div>content</div>',
                'category'=>(object)array('name'=>'アニメ'),
                'author'=>(object)array('username'=>'チャーリーのauthor'),
            ),
            (object)array('id'=>4, 'title'=>'デルタ', 'create_time'=>time(),
                'content'=>'<div>content</div>',
                'category'=>(object)array('name'=>'ホラー'),
                'author'=>(object)array('username'=>'デルタのauthor'),
            ),
        );
    
        $dataProvider = new CArrayDataProvider($data);

        $this->widget('zii.widgets.grid.CGridView', array(
            'dataProvider'=>$dataProvider,
            'columns'=>array(
                'title',
                'category.name',  // display the 'name' attribute of the 'category' relation
                'content:html',   // display the 'content' attribute as purified HTML
                array(
                    'name'=>'create_time',
                    'value'=>'date("M j, Y", $data->create_time)',
                ),
                array(            // display 'author.username' using an expression
                    'name'=>'authorName',
                    'value'=>'$data->author->username',
                ),
//                array(            // display a column with "view", "update" and "delete" buttons
//                    'class'=>'CButtonColumn',
//                ),
            ),
        ));
    }

'category.name' としたり、array('value'=>'$data->author->username') としたりしています。

得られたHTML

table が得られた。単純なarrayからtableへの整形にも使えそうです。

<div id="yw0" class="grid-view">
  <div class="summary">Displaying 1-4 of 4 result(s).</div>
  <table class="items">
    <thead>
      <tr>
        <th id="yw0_c0">title</th><th id="yw0_c1">category.name</th><th id="yw0_c2">content</th><th id="yw0_c3">create_time</th><th id="yw0_c4">authorName</th>
      </tr>
    </thead>
    <tbody>
      <tr class="odd"><td>アルファ</td><td>コメディ</td><td><div>content</div></td><td>Mar 7, 2012</td><td>アルファのauthor</td></tr>
      <tr class="even"><td>ベータ</td><td>アクション</td><td><div>content</div></td><td>Mar 7, 2012</td><td>ベータのauthor</td></tr>
      <tr class="odd"><td>チャーリー</td><td>アニメ</td><td><div>content</div></td><td>Mar 7, 2012</td><td>チャーリーのauthor</td></tr>
      <tr class="even"><td>デルタ</td><td>ホラー</td><td><div>content</div></td><td>Mar 7, 2012</td><td>デルタのauthor</td></tr>
    </tbody>
  </table>
  <div class="keys" style="display:none" title="/test/"><span>1</span><span>2</span><span>3</span><span>4</span></div>
</div>

ソート&ページネーション

これでソート、ページネーションできた。 リレーションフィールドに対してはできなかったが、今回は(略)

<?php
        $dataProvider = new CArrayDataProvider($dt, array(
            'sort'=>array(                       // ソート設定
                'attributes'=>array('title', 'category.name', 'content', 'create_time', 'authorName'),
            ),
            'pagination'=>array(                // ページネーション設定
                'pageSize'=>10,
            ),
        ));

モデルを使わないCListViewサンプル

モデルを使わずzii.widgets.CListViewを使ってみる。
CListViewで何が得られるのか検証したかったのですが、モデルを用意するのが面倒だったので書きました。

サンプルコード

protected/controllers/SiteController.php

<?php
    public function actionIndex()
    {
        $data = array(
            (object)array('id'=>1, 'title'=>'アルファ',   'create_time'=>time()),
            (object)array('id'=>2, 'title'=>'ベータ',     'create_time'=>time()),
            (object)array('id'=>3, 'title'=>'チャーリー', 'create_time'=>time()),
            (object)array('id'=>4, 'title'=>'デルタ',     'create_time'=>time()),
        );

        $dataProvider = new CArrayDataProvider($data);

        $this->widget('zii.widgets.CListView', array(
            'dataProvider'=>$dataProvider,
            'itemView'=>'_post',   // refers to the partial view named '_post'
            'sortableAttributes'=>array(
                'title',
                'create_time'=>'Post Time',
            ),
        ));
    }

protected/views/site/_post.php

<div class="post">
    <span class="id"><?php echo CHtml::encode($data->id); ?></span>
    <span class="title"><?php echo CHtml::encode($data->title); ?></span>
</div>

得られたHTML

<div id="yw1" class="list-view">
    <div class="summary">Displaying 1-4 of 4 result(s).</div>
    <div class="sorter">
        Sort by: 
        <ul>
        <li>title</li>
        <li>Post Time</li>
        </ul>
    </div>
    <div class="items">
        <div class="post"><span class="id">1</span><span class="title">アルファ</span></div>
        <div class="post"><span class="id">2</span><span class="title">ベータ</span></div>
        <div class="post"><span class="id">3</span><span class="title">チャーリー</span></div>
        <div class="post"><span class="id">4</span><span class="title">デルタ</span></div>
    </div>
    <div class="keys" style="display:none" title="/yiiprac4/"><span>1</span><span>2</span><span>3</span><span>4</span></div>
</div>

DataProviderを通して渡したデータが_post.phpへ$dataとして渡されます。

ページネーション、ソートはできませんが、_post.phpCListView.templateプロパティ、CListView.summaryTextプロパティなどをカスタマイズしていけば何が得られるのかわかってくると思います。

ソート&ページネーション - 追記

DataProviderを次のようにしてやるとソート、ページネーションしてくれます。すてき。
SQLの order,limit に依存しているから Arrayはできないと思い込んでいた。

<?php
$dataProvider = new CArrayDataProvider($data, array(
    'sort'=>array(                       // ソート設定
        'attributes'=>array('title', 'create_time',),
    ),
    'pagination'=>array(                // ページネーション設定
        'pageSize'=>10,
    ),
));