標準で組み込まれたフィールドの他に、プラグインによってカスタムフィールドを拡張することができます。
ここでは、カスタムフィールドの開発の手順と、プロジェクトへの組み込み方について説明します。
このドキュメントは、 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 の関数名を指定します。