Pickles 2

カスタムフィールド

標準で組み込まれたフィールドの他に、プラグインによってカスタムフィールドを拡張することができます。

ここでは、カスタムフィールドの開発の手順と、プロジェクトへの組み込み方について説明します。

このドキュメントは、 Pickles 2 v2.0.0-beta.20 から導入された、PHP版エンジン broccoli-html-editor-php を前提に説明しています。このエンジンの利用方法については GUI編集エンジンの種類と切り替え方法 を参照してください。

カスタムフィールドを開発する

カスタムフィールドの構成

カスタムフィールドは、フロントエンドとバックエンドの両スクリプトで構成されます。

バックエンドスクリプトの実装

バックエンドは、PHPで実装されます。

broccoli-html-editor パッケージに含まれるクラス \broccoliHtmlEditor\fieldBase を継承し、拡張します。 各メソッドの 実装例は fieldBase.php を参考にしてください。

<?php
namespace myidentity\myplugin;
class myCustomFieldBackend extends \broccoliHtmlEditor\fieldBase{

    private $broccoli;

    /**
     * constructor
     */
    public function __construct($broccoli){
        $this->broccoli = $broccoli;
        parent::__construct($broccoli);
    }

    /**
     * データをバインドする (Server Side)
     */
    public function bind( $fieldData, $mode, $mod ){
        $rtn = '';
        return $rtn;
    }

    /**
     * リソースを加工する (Server Side)
     */
    public function resourceProcessor( $path_orig, $path_public, $resInfo ){
        // ↓デフォルトの処理。オリジナルファイルをそのまま公開パスへ複製する。
        $result = copy( $path_orig, $path_public );
        return $result;
    }

    /**
     * GPI (Server Side)
     */
    public function gpi($options){
        return $options;
    }

}

フロントエンドスクリプトの実装

フロントエンドは、ブラウザ上で動作する JavaScript で実装されます。

broccoli-html-editor パッケージに含まれる fieldBase.js を継承し、拡張します。 各メソッドの 実装例は fieldBase.js を参考にしてください。

window.myCustomFieldFrontend = function(broccoli){
    var $ = require('jquery');

    /**
     * データを正規化する (Client Side)
     * このメソッドは、同期的に振る舞います。
     */
    this.normalizeData = function( fieldData, mode ){
        // 編集画面用にデータを初期化。
        var rtn = fieldData;
        return rtn;
    }

    /**
     * プレビュー用の簡易なHTMLを生成する (Client Side)
     * InstanceTreeViewで利用する。
     */
    this.mkPreviewHtml = function( fieldData, mod, callback ){
        var rtn = '';
        new Promise(function(rlv){rlv();})
            .then(function(){ return new Promise(function(rlv, rjt){
                // サーバーサイドの bind() に相当する処理
                rtn = fieldData;
                rlv();

            }); })
            .then(function(){ return new Promise(function(rlv, rjt){

                // console.log(rtn);
                var $rtn = $('<div>').append(rtn);
                $rtn.find('*').each(function(){
                    $(this)
                        .removeAttr('style') //スタイル削除しちゃう
                    ;
                });
                $rtn.find('style').remove(); // styleタグも削除しちゃう
                rtn = $rtn.html();

                rlv();

            }); })
            .then(function(){ return new Promise(function(rlv, rjt){
                callback( rtn );
            }); })
        ;
        return this;
    }

    /**
     * エディタUIを生成 (Client Side)
     */
    this.mkEditor = function( mod, data, elm, callback ){
        var rows = 12;
        if( mod.rows ){
            rows = mod.rows;
        }
        var $rtn = $('<div>'),
            $formElm
        ;
        $formElm = $('<textarea class="form-control">')
            .attr({
                "name": mod.name,
                "rows": rows
            })
            .val(data)
            .css({'width':'100%','height':'auto'})
        ;
        $rtn.append( $formElm );
        $(elm).html($rtn);

        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback();
        }); });
        return this;
    }

    /**
     * エディタUIにフォーカス (Client Side)
     */
    this.focus = function( elm, callback ){
        callback = callback || function(){};
        $(elm).find('textarea, input').eq(0).focus();
        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback();
        }); });
        return this;
    }

    /**
     * データを複製する (Client Side)
     */
    this.duplicateData = function( data, callback, resources ){
        callback = callback||function(){};
        data = JSON.parse( JSON.stringify( data ) );
        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback(data);
        }); });
        return this;
    }

    /**
     * データから使用するリソースのリソースIDを抽出する (Client Side)
     */
    this.extractResourceId = function( data, callback ){
        callback = callback||function(){};
        data = [];
        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback(data);
        }); });
        return this;
    }


    /**
     * エディタUIで編集した内容を検証する (Client Side)
     */
    this.validateEditorContent = function( elm, mod, callback ){
        var errorMsgs = [];
        // errorMsgs.push('エラーがあります。');
        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback( errorMsgs );
        }); });
        return this;
    }

    /**
     * エディタUIで編集した内容を保存 (Client Side)
     */
    this.saveEditorContent = function( elm, data, mod, callback, options ){
        options = options || {};
        options.message = options.message || function(msg){};//ユーザーへのメッセージテキストを送信
        var $dom = $(elm);
        var src = $dom.find('textarea').val();
        src = JSON.parse( JSON.stringify(src) );

        new Promise(function(rlv){rlv();}).then(function(){ return new Promise(function(rlv, rjt){
            callback(src);
        }); });
        return this;
    }

    /**
     * GPIを呼び出す (Cliend Side)
     */
    this.callGpi = function(options, callback){
        callback = callback || function(){};
        broccoli.gpi(
            'fieldGpi',
            {
                '__fieldId__': this.__fieldId__,
                'options': options
            },
            function(result){
                // console.log(result);
                callback(result);
            }
        );
        return this;
    }

}

カスタムフィールドをプロジェクトに組み込む

config.php の中の項目 $conf->plugins->px2dt->guieditor->custom_fields に設定します。

@$conf->plugins->px2dt->guieditor->custom_fields = array(
    'myCustomField'=>array(
        'backend'=>array(
            'class' => 'myidentity\myplugin\myCustomFieldBackend'
        ),
        'frontend'=>array(
            'dir' => '/path/to/frontendFiles/',
            'file' => array(
                'frontend.js'
            ),
            'function' => 'window.myCustomFieldFrontend'
        ),
    ),
);

連想配列 $conf->plugins->px2dt->guieditor->custom_fields のキーが、フィールド名として使われます。

上記サンプルでは myCustomField がフィールド名です。 モジュールからは {&{"input": {"type":"myCustomField"}}&} のように呼び出すことができます。

それ以下の各項目については次のような意味があります。

  • backend->class : バックエンドの PHP のクラス名を指定します。 namespace 内に実装される場合は、これも含んだ完全な名前として指定してください。
  • front->dir : フロントエンドのリソース類が格納されているディレクトリのサーバー内部パスを指定します。ツールによっては、このフォルダをドキュメントルート下にコピーして使用することがあります。
  • front->file : 自動的にロードするリソースファイル名を指定します。配列にして複数設定します。1件であれば文字列で指定することもできます。front->dir からの相対パスで検索されます。
  • front->function : フロントエンドの JavaScript の関数名を指定します。