ブロックの仕組み

提供: Baked Wiki
移動: 案内検索

目次

ブロックディレクトリ

ブロックはCakePHPのプラグイン機能によって実現しています。ブロックディレクトリそのものがCakePHPのプラグインになっています。ブロックディレクトリの設置先は「app/Plugin」ディレクトリで、ディレクトリ名は「Block」の接頭辞が必要です。

下記はブロックディレクトリのツリー構造です。

  • BlockName ブロックディレクトリ
    • Config 設定ディレクトリ
      • bootstrap.php 設定ファイル
    • Controller コントローラディレクトリ
      • BlockNameApiController.php APIコントローラ
    • Model モデルディレクトリ
      • BlockName.php モデルファイル
    • View ビューディレクトリ
      • Elements 部品ビューのディレクトリ
        • block.ctp 表示用ビュー
        • editor.ctp 編集用ビュー
    • webroot ブロックルートディレクトリ
      • css CSSディレクトリ
        • block.css 表示中に読み込まれるcssファイル
        • editor.css 編集中に表示するcssファイル
      • js JSディレクトリ
        • block.js 表示中に読み込まれるjsファイル
        • editor.js 編集中に読み込まれるjsファイル

設定ファイル

ブロックディレクトリには必ず設定ファイルを作成してください。パスは「BlockName/Config/bootstrap.php」です。

設定ファイルでは、そのブロックプラグインの情報を設定します。

 <?php
 Configure::write('Blocks.BlockName(ブロックプラグインの名前)', array(
   'name' => '適当なブロックの名前',
   'icon' => 'icon-picture', // アイコン名
 ));

BlockNameはブロックディレクトリの名前と一致させてください。iconキーに設定する値は、下記URLから選ぶことができます。

http://fortawesome.github.io/Font-Awesome/icons/

ファイルの自動読み込み

Bakedは、下記のファイルを適当なタイミングで探して読み込みます。

パス タイミング 用途
BlockName/webroot/css/block.css サイト表示中 表示用ビュー(BlockName/View/Elements/block.ctp)に適用する
BlockName/webroot/css/editor.css サイト編集中 編集用ビュー(BlockName/View/Elements/editor.ctp)に適用する
BlockName/webroot/js/block.js サイト表示中 ブロックの動作に必要なコードを書く
BlockName/webroot/js/editor.js サイト編集中 ブロックの編集に必要なコードを書く


ファイルの明示的読み込み

別途読み込ませたいファイルがあるときは、設定ファイルに下記のように設定することもできます。

 // 表示中
 Baked::add('CSS', array(
   '/BlockName/show/show_1.css',
   '/BlockName/show/show_2.css',
 ));
 // 編集中
 Baked::add('CSS_EDITTING', array(
   '/BlockName/editor/editor_1.css',
   '/BlockName/editor/editor_2.css',
 ));

この設定により、サイト表示中は「BlockName/webroot/css/show/show_1.css」「BlockName/webroot/css/show/show_2.css」が読み込まれ、さらにサイトの編集中は「BlockName/webroot/css/editor/editor_1.css」「BlockName/webroot/css/editor/editor_2.css」読み込まれます。

また、同じような方法で、JSを読み込むことも可能です。

 // 表示中
 Baked::add('JS', array(
   '/BlockName/show/show.js',
 ));
 // 編集中
 Baked::add('JS', array(
   '/BlockName/editor/editor.js',
 ));

これでサイト表示中は「BlockName/webroot/js/show/show.js」が、さらに編集中は「BlockName/webroot/js/editor/editor.js」が読み込まれます。

モデル

モデルは必ず必要なファイルで、付けるべき名前も決まっています。 BlockName/Model/以下に、{プラグイン名}.phpというファイル名で、プラグイン名と同じ名前のクラスを作成してください。 例えば、プラグイン名をBlockRssFeedという名前にする場合、下記のようになります。

 // app/Plugin/BlockRssFeed/Model/BlockRssFeed.php
 
 <?php
 App::uses('BlockAppModel', 'Model');
 
 class BlockRssFeed extends BlockAppModel
 {
   public $name = 'BlockRssFeed';
   public $useTable = FALSE;
 }

モデルに最低限必要なコードは上記だけです。2行目でBlockAppModelクラスを読み込んでいます。作成するモデルは必ずBlockAppModelのサブクラスでなければなりません。

ブロックが作成されると、Bakedはブロックが自由に読み書きできるデータの保存場所を用意します。この保存場所には一意のIDが付与されます。ブロックが削除されると、Bakedはブロックと一緒にデータの保存場所を削除します。モデルでやる仕事は、データの更新と削除です。

メソッド

モデルに定義されるメソッド(具体的にはBlockAppModelで定義されているメソッド)には下記のようなものがあります。

updateData($id, $data)

 public function updateData($id, $data)

1つ目のパラメータにはブロックのID(以後「ブロックID」)を、2つ目には保存したいデータ配列を渡します。 データはBakedによってjson形式にエンコードされ保存されます。 成功した場合はtrue、失敗した場合はExceptionクラスのインスタンスが返ります。

下記のように使用します。

 $this->updateData(1, array(
   'title' => 'Hello',
   'text' => 'World!'
 ));

getData($id)

次はデータの取得です。

 public function getData($id)

パラメータにはブロックIDを指定します。保存されていたjson形式のデータ配列がデコードされ返ってきます。ブロックIDが存在しない場合は、falseが返ります。

バリデーション

データを保存する前にはバリデーションを実行しなければいけません。 バリデーションの実行はBakedが担当するので、モデルではバリデーションの設定だけを行います。 バリデーションの設定は、モデルクラスに$validという名前の変数を定義して行います。

 class BlockName
 {
   public $name = 'BlockName';
   public $useTable = FALSE;
   public $valid = array(
     'add' => array(
       'title' => 'required | minLen[10] | maxLen[100]',
       'email' => 'required | email'
       'type' => 'inList(type1,type2)'
     ),
     'update' => array(
     ),
   );
 }

addキーはブロックの新規追加時に使用され、updateキーはブロックの更新時に使用されます。addキーの配列は自動的にupdateキーに結合されます。add/updateキーの中は配列で、その中のキーはupdateDataメソッドで渡すデータの配列のキーと対応しています。値にはそれぞれバリデーションルールが設定され、'|'で区切られています。

Bakedはデータを作成/更新する際、このバリデーションルールを確認し、クリアした場合はデータ保存し、エラーがあった場合は適宜エラーを返します。 下記は設定できるバリデーションルールの一覧です。

ルール 説明 使用例
required 必須入力。配列にキーがない場合はエラー
notEmpty 必須必須。配列にキーがない場合はクリア
alphaNumeric 半角英数字
between 半角文字。字数に範囲制限 between[1,10] 1文字以上かつ10文字以下
blank 値無し
cc クレジットカード番号
date 日付形式
datetime 日付時間形式
decimal 小数点第n位までの半角数字 decimal[2] 小数点第2位まで
email メールアドレス
equalTo 入力値の一致 equalTo[hello] 入力値が"hello"である
extension 拡張子の一致 extension(png,gif,jpg,jpeg) 拡張子がpng,gif,jpg,jpegのいずれか
ip IPアドレス形式
minLen 最低文字数 minLen[5] 5文字以上
maxLen 最大文字数 minLen[10] 10文字以内
money 金額として正しい値
numeric 半角数字
phone 電話番号
postal 郵便番号
zip 郵便番号
range n1より大きくn2より小さい半角数字 range(0,10) 0より大きく10より小さい
url URL
inList リストに存在する値 inList(1,2,3) 1,2,3のいずれか
time 時:分 形式

$valid[update]には自動的に$valid[add]が結合されますが、その際に$valid[add]に含まれる'required'ルールは全て'notEmpty'に置換されます。

コールバックメソッド

モデルにはBakedから自動的に呼ばれるコールバックメソッドがあります。最もよく使うのは下記の2つです。

initialData()

 public function initialData()
 {
   return array(
     'title' => 'Yahoo! トピックストップ',
     'url' => 'http://rss.dailynews.yahoo.co.jp/fc/rss.xml',
     'limit' => 5,
   );
 }

上記のメソッドはブロックが新規作成された時に呼び出されます。返り値はブロックの初期データとなります。

willDelete($blockId)

ブロックが削除される時に呼び出されるメソッドです。

 public function willDelete($blockId)
 {
   return TRUE;
 }

引数にはブロックのIDが渡されます。このメソッド内では独自に保存されたデータ(例えばフォトギャラリーのブロックでは画像など)を削除します。このメソッドでfalseを返すと、ブロックの削除はキャンセルされます。全てのデータをupdateDataメソッドで保存している場合は、willDeleteメソッドを定義する必要がありません。

コントローラ

コントローラの仕事は、ブロックの編集データを受取り、モデルを通して保存することです。コントローラの雛形は下記の通りです。

 <?php
 /**
  * app/Plugin/BlockName/Controller/BlockNameApiController.php
  */
 
 App::uses('BlockAppController', 'Controller');
 
 class BlockNameApiController extends BlockAppController
 {
   public $uses = array('BlockName.BlockName');
 }

コントローラクラスはBlockAppControllerクラスのサブクラスである必要があります。コントローラのファイル名とクラス名は、必ずプラグイン名を接頭辞にしてください。1つ気をつけなければならないことは、「{プラグイン名}Controller」という名前は使えないことです。必ず「{プラグイン名}ApiController」や「{プラグイン名}UpdateController」など、適当な名前を付けてください。

クラスの中に、$usesというプロパティがあります。このプロパティには使用するモデル名を配列で渡します。上記では'BlockName.BlockName'となっています。「.」の前はプラグイン名で、後ろがモデル名です。モデル名はプラグイン名と同じにするべきなので、このような表記になっています。 $usesに渡したモデルは、コントローラのメソッドから下記のように使用することができます。

 class BlockNameApiController extends BlockAppController
 {
   public $uses = array('BlockName.BlockName');
 
   public function sample()
   {
     $this->BlockName->methodName(); // メソッドをコール
   }
 }

コントローラには自由にメソッドを作成できます。それらのメソッドはブラウザからコールでき、ビューやJSファイル等からのリクエストを受け取ります。仮にコントローラ名を「BlockNameApiController」とし、下記のようなメソッドを作成した場合、http://yourbaked.com/plugin/block_name/block_name_api/hellp というパスでアクセスすることができ、「Hello World!」と表示されるはずです。

 public function hello()
 {
   echo "Hello world!";
   exit;
 }

セキュリティ

データを更新するコントローラに、管理者ではないリクエストや、悪意ある攻撃(例えばXSRFなど)によるリクエストがくる可能性もあります。Bakedではそれらを防ぐメソッドを用意しています。「tokenFilter」と「staffFilter」です。

tokenFilterメソッドは、リクエストに正常なトークンが含まれているかを調べ、不正だと判断した場合はエラーを出力し終了します。トークンはサイトにアクセスしたユーザー全てのセッションに自動で保存されています。また、後述しますが、リクエストに適宜トークンを仕込む方法もBakedが提供します。

staffFilterメソッドは、リクエストユーザーが管理者であるかどうかを調べ、不正だと判断した場合はエラーを出力し終了します。 下記のように使います。

 public function hello()
 {
   $this->tokenFilter();
   $this->staffFilter();
 
   echo "Hello world!";
   exit;
 }

エラー出力をjsonで行う場合は、「tokenFilterApi」「staffFilterApi」メソッドを使用してください。

 public function hello()
 {
   $this->tokenFilterApi();
   $this->staffFilterApi();
 
   $this->Api->ok(array(
     'text' => 'Hello world!'
   ));
 }

レスポンス

Bakedではコントローラと通信するためのJavascriptクラス(以後「Baked JSクラス」)を用意しています。ビューからコントローラにリクエストするときは、Baked JSクラスにデータを渡します。

Baked JSクラスでは、コントローラからjson形式のレスポンスを期待しています。そのため、コントローラからjsonを出力するためのクラス「ApiComponent」が用意されています。このクラスのインスタンスは、コントローラの$Apiプロパティに保存されています。

下記のように使います。

 public function hello()
 {
   /**
    * 成功時にコール
    * 下記jsonが出力されexitします
    *
    * {
    *   result: "OK",
    *   key1: "val1",
    *   key2: "val2"
    * }
    */
   $this->Api->ok(array(
     'key1' => 'val1',
     'key2' => 'val2',
   ));
 
   /**
    * エラー時にコール
    * 下記jsonが出力されexitします
    *
    * {
    *   result: "NG",
    *   message: "エラーメッセージ"
    * }
    */
   $this->Api->ng('エラーメッセージ');
 }

ngメソッドを使うと、Baked JSクラスはエラーメッセージをユーザーに表示し、適切にエラーを処理します。

リクエストデータ

コントローラでは下記の方法でリクエストデータを取得できます。

 public function hello()
 {
   $this->request->data; // POSTデータの配列
   $this->request->query; // GETデータの配列
 }

サンプル

通常、ブロックのデータを更新するメソッドは下記のような処理になります。

 public function update()
 {
   $this->tokenFilterApi();
   $this->staffFilterApi();
 
   $data = array(
     'text' => @$this->request->data['text'],
   );
   $r = $this->BlockName->updateData(@$this->request->data['block_id'], $data);
   if ($r !== TRUE) $this->Api->ng($r->getMessage());
 
   $this->Api->ok(array(
     'html' => $this->_htmlBlock($this->request->data['block_id']),
   ));
 }

ブロックIDと新しいデータを受取り、それらをモデルのupdateDataメソッドに渡しています。エラーならApiComponentクラスのngメソッドにエラーメッセージを渡します。更新が成功したら、ApiComponentクラスのokメソッドに更新したブロックのhtmlタグを渡しています。_htmlBlockメソッドにブロックIDを渡すと、ブロックのHTMLタグを取得できます。

ビュー

ブロックビュー

ブロックのビューは2つあります。その1つがブロックビューです。ブロックビューではブロックのコンテンツを表示します。パスはBlockName/View/Elements/block.ctpです。

ブロックビューでは$block変数が使えます。

 // var_dump($block)
 array(
   'Block' => array(
     'id' => string '183' // ブロックID
     'package' => 'BlockHeading', // プラグイン名
     'sheet' => 'main', // シート名
     'order' => '0', // シート内での順番
     'data' => array( // ブロックのデータ
       'h' => '1',
       'text' => '見出しのテキスト',
     ),
     'created' => '2013-08-04 00:03:50, // 作成日時
     'modified' => '2013-08-21 04:20:02', // 更新日時
   )
 );

この変数を使って、ブロックのコンテンツを表示します。例えば、下記は見出しブロック(BlockHeading)のブロックビューです。

 <?php echo sprintf('<h%s>%s</h%s>', $block['Block']['data']['h'], $block['Block']['data']['text'], $block['Block']['data']['h']) ?>

編集ビュー

編集ビューはサイトの編集時のみ表示されます。ブロックのコンテンツを編集するためのビューです。 下記は見出しブロック(BlockHeading)の編集ビューです。

<?php
echo $this->Form->create('Block', array(
  'default' => FALSE,
  'data-block-editor-heading',
));
?>

<ul class="bk-editor-boxes">
  <li>
    <div class="bk-title"><?php echo __d('BlockHeading', 'サイズ') ?></div>
    <?php
    echo $this->Form->input('Block.h', array(
      'value' => $block['Block']['data']['h'],
      'options' => BlockHeading::$H,
      'class' => 'bk-block-input-h',
      'label' => FALSE,
    ));
    ?>
  </li>
  <li>
    <div class="bk-title"><?php echo __d('BlockHeading', 'テキスト') ?></div>
    <?php
    echo $this->Form->input('Block.text', array(
      'value' => $block['Block']['data']['text'],
      'class' => 'bk-block-input-text',
      'label' => FALSE,
    ));
    ?>
  </li>
</ul>

<div class="spacer1"></div>
  
<button type="submit" class="button button-primary button-pill button-small"><?php echo __d('BlockHeading', '保存') ?></button>

<?php
echo $this->Form->end();

ここではCakePHP標準のFormヘルパーを使っていますが、formタグやinputタグを直接書いても問題ありません。

bk-editor-boxesクラスの付いたulタグは、適切に編集項目を表示するのに適しています。下記のように使用してください。

<ul class="bk-editor-boxes">
  <li>
    <div class="bk-title">項目名</div>
    <!-- inputタグなど -->
  </li>
</ul>

BakedではFontAwesomeを使い、簡単にアイコンを表示することができます。

アイコンのリスト:http://fortawesome.github.io/Font-Awesome/icons/

アイコンの表示方法:http://fortawesome.github.io/Font-Awesome/examples/

inputタグなどの代わりに、アイコンボタンを設置することもできます。

<ul class="bk-editor-boxes">
  <li>
    <div class="bk-title">項目名</div>
    <a href="javascript:;"><i class="icon-align-left icon-2x"></i></a>
    <a href="javascript:;"><i class="icon-align-right icon-2x"></i></a>

    <!-- アイコンを選択状態にするには aタグのクラスにactiveを加える -->
    <a href="javascript:;" class="active"><i class="icon-align-right icon-2x"></i></a>
  </li>
</ul>

ブロックの更新は、編集ビューのformタグやaタグのイベントを拾い、後述するBaked JSクラスを通してコントローラにリクエストし、ブロックのデータを保存するという流れです。

リソース

BlockName/webrootディレクトリは、ブラウザから自由にアクセスできる公開ディレクトリです。例えばBlockName/webroot/css/style.cssには/block_name/css/style.cssというパスでアクセスできます。

webrootディレクトリ以下にCSSやJSファイルや画像などのリソースファイルを設置します。

#ファイルの自動読み込みでも言及しましたが、サイトの表示中には「css/block.css」「js/block.js」が、さらに編集中には「css/editor.css」「js/editor.js」が読み込まれます。これらのファイルは作成しなくても構いません。必要なときだけ用意してください。

Baked JSクラス

Bakedには便利なJavascriptクラスがあります。Bakedというクラスで、bakedという変数にインスタンスが保持されています。ブロックの動作(特に編集)では、このクラスを使用することになります。

メソッド

以下、使えるメソッドをリストアップします。

メソッド 返り値 説明
closeAllEditor() void 全てのブロックの編集エリア(editor.ctp)を閉じる
domBlockById(blockId) jQuery Dom ブロックIDを渡すと、表示中のページに存在するブロックのhtmlタグのjQuery DOMが返される
loadBlock(blockId, callbacks) void 第1引数でブロックIDを渡すと、サーバからブロックのhtmlタグを取得し、成功したらcallbacks.okメソッドをコールする
showBox(html) void htmlをポップアップで表示する
params($dom) Object formのjQuery Domを渡すと、その中からinput要素をkey=value形式のオブジェクトにして返す
post(url, options) void 第1引数のURLにpost送信する。最終的なリクエストURLは、引数のURLにBakedのトップページのURLが接頭に付く。そのため、Bakedのインストールパスを意識せず、plugin/block_name/block_name_api/helloなどのパスを渡せば良い。第2引数はjQuery.ajaxの引数とほぼ同様。ただし、optionsのdataTypeが"json(デフォルト)"の場合、successメソッドはコールされず、代わりに次にような挙動になる。リクエストが成功したときoptions.okがコールされる。リクエストが失敗したときoptions.ngがコールされる。(ここでいうリクエストの成功/失敗は、具体的にはコントローラでApiComponent::ok()メソッドかApiComponent::ngメソッドのどちらでjsonを出力したかで判断される)
reload() void 表示中のページをリロードする
reloadDynamic() void data-bk-dynamic属性のあるタグを全て更新する

この中でも使うメソッドはそれほど多くないかと思います。中でも最も重要なメソッドはpostメソッドでしょう。コントローラとの通信はこのメソッドを通して行ってください。Bakedの不正アクセス対策、リクエストURLの自動補完、エラーハンドリング、コールバック処理を利用できます。

使用例

下記はBaked JSクラスの使用例です。

// 現在のブロックのjQuery DOMを保持します。
var $block = baked.domBlockById(blockId);

var newText = '新しいテキスト';

// コントローラのブロック更新メソッドにリクエストします。
baked.post('plugin/block_name/block_name_api/update', {
  data: {
    text: newText
  },
  ok: function(r){
    // 開いている編集エリアと閉じます。
    baked.closeAllEditor();

    // レスポンスに含まれていた新しいブロックのタグを既存のものと入れ替えます。
    $block.find('.bk-your-block-content-class').replaceWith(r.html);
  },
  ng: function(r){
    // 更新が失敗したようです。エラーの表示はBakedが担当しますが、他に何かやることがあればここで実行します。
  }
});