CakePHP3で遊ぶ


独自ウィジェットに挑戦してみる

CakePHP 3にて、Formヘルパーのcontrol()メソッドを使いdatetime型の入力フィールドを生成する際に、divエレメントで括らないselectエレメントを生成させる方法を検討してみます。

datetime型のウィジェット?

CakePHP3で遊ぶ - bootstrapに合わせたFormヘルパーのtemplate -では、templateをカスタマイズして任意のdivエレメントを出力させる方法を検討しました。しかし、datetime型の入力フィールドは、複数のselectエレメントを出力するので、templateだけでは思うように入力フィールドを生成できません。
そこで、datetime型の入力フィールドをどのように生成しているのか確認してみます。
CakePHP 3では、Formヘルパーのcontrol()メソッドを使って入力フィールドを生成する場合、それぞれのエレメントに対応したウィジェットが入力フィールドを生成します。
ならば、datetime型の入力フィールドを生成しているウィジェットをカスタマイズしてみればよさそうです。

まずは、標準のウィジェットがどのようになっているのか確認してみます。

FormHelper.php
    protected $_defaultConfig = [
                :
        'templates' => [
                :
            // Widget ordering for date/time/datetime pickers.
            'dateWidget' => '{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}',
                :
            // Select element,
            'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
                :
        ]
    ];

    protected $_defaultWidgets = [
                :
        'select' => ['SelectBox'],
                :
        'datetime' => ['DateTime', 'select'],
                :
    ];

ここからわかることは、datetime型の入力フィールドを生成しているウィジェットは、DateTimeWidgetが処理をしていて、さらにSelectBoxWidgetを呼び出していることです。
SelectBoxWidgetは、selectエレメントを生成しているウィジェットです。このことからも、datetime型の入力フィールドをselectエレメントで構築していることがわかります。

独自ウィジェットに差し替える

そこで、datetime型の入力フィールドを生成させるウィジェットを新たに作成することにします。
やりたいことは、divエレメントで括らないselectエレメントを生成させることです。そこで、SelectBoxWidgetを継承したウィジェットを作成します。

View/Widget/AppSelectBoxWidget.php
<?php

namespace App\View\Widget;

use Cake\View\Form\ContextInterface;
use Cake\View\Widget\SelectBoxWidget;

class AppSelectBoxWidget extends SelectBoxWidget
{

    public function render(array $data, ContextInterface $context)
    {
        $data += [
            'name' => '',
            'empty' => false,
            'escape' => true,
            'options' => [],
            'disabled' => null,
            'val' => null,
            'templateVars' => []
        ];

        $options = $this->_renderContent($data);
        $name = $data['name'];
        unset($data['name'], $data['options'], $data['empty'], $data['val'], $data['escape']);
        if (isset($data['disabled']) && is_array($data['disabled'])) {
            unset($data['disabled']);
        }

        $template = 'selectNoDiv';
        $attrs = $this->_templates->formatAttributes($data);

        return $this->_templates->format($template, [
            'name' => $name,
            'templateVars' => $data['templateVars'],
            'attrs' => $attrs,
            'content' => implode('', $options),
        ]);
    }
}

30行目に$template = 'selectNoDiv';としています。ここで、使用するtemplateを指定しています。

次に、このウィジェットを使うようにaddWidget()します。
今回も、viewで変更することにします。

$this->Form->addWidget('appselect', ['AppSelectBox']);
$this->Form->addWidget('datetime', ['DateTime', 'appselect']);

ここで、appselectという識別名でAppSelectBoxウィジェットを追加します。
さらに、datetimeappselectに置き換えます。
以上で、datetime型の入力フィールドを生成する際にAppSelectBoxWidgetが使われるようになりました。

つぎに、AppSelectBoxWidgetで参照するselectNoDivtemplateを追加しておきます。

$this->Form->templates([
                :
    'dateWidget' =>          '<div class="col-xs-6">{{year}}&nbsp;{{month}}&nbsp;{{day}}&nbsp;{{hour}}&nbsp;{{minute}}&nbsp;{{second}}&nbsp;{{meridian}}</div>',
                :
    'select' =>              '<div class="col-xs-6"><select name="{{name}}"{{attrs}}>{{content}}</select></div>',
                :
    'selectNoDiv' =>         '<select name="{{name}}"{{attrs}}>{{content}}</select>',

  ]);

この状態で

echo $this->Form->control('field');

とすると目的の通りに、年、月、日などの入力フィールドがselectエレメントで作成されますが、個々のselectエレメントにはdivエレメントに括られていない状態で生成されるようになりました。

参考サイト

CakePHP3:Form