2013年12月29日日曜日

node + ejs + Socket.io のサンプル

まずい。。
一年間、一回も更新してない!!!
つまり、、
趣味の時間がほぼ皆無ということの現れです。
業務と子育てに追われた一年でしたね。。。。。
危なく年をこすところだった。あぶない。なぶあい。
話を変えて。


今月初旬に 社内でちょっとしたプレゼンをする事になったんだけど
場所の都合上、プロジェクターが設置できないっぽいという情報を耳にしました。
なのでスマホをプロジェクタがわりにしたらええんでないの?かつ、
知らない人(私も含まれるけど)向けにwebsocketの紹介デモをするのにもってこいじゃないか。
と最近手を動かしてなかったので勉強兼ねて。
nodejsとwebsocket(socket.io)使ってスライドのデモを。

サンプル引っ張ってきてちょっとだけ弄って。
画面の制御だけをくっつけた登壇者タイミングで
制御するだけの簡単なデモを作ってみました。

環境と概要

SenchaUGで頂いたさくらのクラウドを使いました。
ありがとうございます。さくらの方。
ありがとうございます。SenchaUG。

あのあとSenchaTouch使って社員旅行のパンフサイト作ったり
今回の様にnode突っ込んだりと遊ばせて頂いております。
ホントは社員みんなに触ってみてもらいたかったので
ユーザごとのフォルダに画像だけ放り込めば
メニューが生成されて複数人で使えるようにしたかったんだけどね。。

そんな時間はどこにもありませんでした。
ルーティングやらテンプレートなどの
設定などはもうなにをどうしていいか
わからなかったのでググって express + ejs という組み合わせにしました。
当時、会社のお昼休みしか遊ぶ時間がなかったので
  • 機能はシンプル(と言うかそんなに作れません)
  • ひとまず動けばイイ(・∀・)イイ!!
です。

参考サイト

  • http://gihyo.jp/dev/serial/01/nodejs
  • http://snippets.takahironakamori.jp/2013/01/nodejs_install/
大変お世話になりました。

大きな流れ

  1. ざっくり機能をまとめる
  2. nodeのインストール
  3. expressのインストール
  4. foreverのインストール
  5. socket.ioのインストール
  6. クライアントサイドの実装
  7. サーバサイドの実装

1:ざっくり機能をまとめる

まず、最初に述べたとおり
時間がない。(ほんとになかった)
一方的な通信で良い。
ざっくり同期とれりゃいい。

URL

聴取者はスマートフォンなのでURL入力をあまりさせたくないので
http://さくらのクラウドのIP:3000/
にした。
登壇者は
http://さくらのクラウドのIP:3000/speaker
にした。

スライド

最初、パワーポイントをhtmlとして吐き出してそれを操作させようと思ったんだけど、
オブジェクトがうまく吐き出せない部分があった際に、
画像としてフォルダ単位で吐き出せば綺麗に吐き出せたから、ああ、それでいいじゃん。と。

登壇者

右矢印、左矢印で自身のイメージを変更。
現在の表示インデックスをブロードキャスト。

聴衆者

送信されてきたインデックスからパスを生成してsrcを書き換え。

登壇者、聴衆者共通

初期接続時、指定フォルダに格納されたファイルリストとURL生成用の
パス情報を取得する必要がある。
ファイルリストは配列で。
最初、登壇者と聴衆者、全く同じ機構を使って登壇者が押したボタンを
ブロードキャストして聴衆者側のハンドラでボタンの押下を
シミュレートするようにしたんだけど
そもそもどっち押したかなんて必要ないわけだし。
今見えているインデックスだけ通知して
聴衆者が自主的に取得すればいいだけだからね。
だから今見てるインデックスだけ通知するように。

2:nodeのインストール

http://snippets.takahironakamori.jp/2013/01/nodejs_install/
こちらを参考に入れました。
本当にありがとうございました。

3:expressのインストールと動作確認

今回のサンプルはejsを利用するため、
-e オプションです。(jadeは見た目で拒絶しちゃいました)
npm install -g express
npm install -g express-generator
↑※こちら、Ver4.0.0以降?必要。expressコマンド自体はepxressと分離したみたい。。
今回作成するのはslideappとしたので
作成するフォルダで
[hogehoge@fugafuga noderoot]$ express -e slideapp

create : slideapp
create : slideapp/package.json
create : slideapp/app.js
create : slideapp/public
create : slideapp/public/javascripts
create : slideapp/public/images
create : slideapp/public/stylesheets
create : slideapp/public/stylesheets/style.css
create : slideapp/routes
create : slideapp/routes/index.js
create : slideapp/routes/user.js
create : slideapp/views
create : slideapp/views/index.ejs

install dependencies:
  $ cd slideapp && npm install

run the app:
  $ node app
後半に書いてあるように
必要なパッケージをインストールして
起動します。
[hogehoge@fugafuga noderoot]$ cd slideapp
[hogehoge@fugafuga slideapp]$ npm install
npm http GET https://registry.npmjs.org/express/3.4.5
npm http GET https://registry.npmjs.org/ejs
npm http 304 https://registry.npmjs.org/ejs
npm http 200 https://registry.npmjs.org/express/3.4.5
npm http GET https://registry.npmjs.org/express/-/express-3.4.5.tgz
npm http 200 https://registry.npmjs.org/express/-/express-3.4.5.tgz
npm http GET https://registry.npmjs.org/connect/2.11.1
npm http GET https://registry.npmjs.org/commander/1.3.2
npm http GET https://registry.npmjs.org/range-parser/0.0.4
npm http GET https://registry.npmjs.org/mkdirp/0.3.5
npm http GET https://registry.npmjs.org/cookie/0.1.0
npm http GET https://registry.npmjs.org/buffer-crc32/0.2.1
npm http GET https://registry.npmjs.org/fresh/0.2.0
npm http GET https://registry.npmjs.org/methods/0.1.0
npm http GET https://registry.npmjs.org/send/0.1.4
npm http GET https://registry.npmjs.org/cookie-signature/1.0.1
npm http GET https://registry.npmjs.org/debug
npm http 304 https://registry.npmjs.org/cookie/0.1.0
npm http 304 https://registry.npmjs.org/range-parser/0.0.4
npm http 304 https://registry.npmjs.org/buffer-crc32/0.2.1
npm http 304 https://registry.npmjs.org/fresh/0.2.0
npm http 304 https://registry.npmjs.org/mkdirp/0.3.5
npm http 304 https://registry.npmjs.org/methods/0.1.0
npm http 304 https://registry.npmjs.org/send/0.1.4
npm http 304 https://registry.npmjs.org/cookie-signature/1.0.1
npm http 200 https://registry.npmjs.org/debug
npm http 200 https://registry.npmjs.org/connect/2.11.1
npm http GET https://registry.npmjs.org/connect/-/connect-2.11.1.tgz
npm http 200 https://registry.npmjs.org/commander/1.3.2
npm http GET https://registry.npmjs.org/commander/-/commander-1.3.2.tgz
npm http 200 https://registry.npmjs.org/connect/-/connect-2.11.1.tgz
npm http 200 https://registry.npmjs.org/commander/-/commander-1.3.2.tgz
npm http GET https://registry.npmjs.org/mime
npm http GET https://registry.npmjs.org/keypress
npm http GET https://registry.npmjs.org/qs/0.6.5
npm http GET https://registry.npmjs.org/bytes/0.2.1
npm http GET https://registry.npmjs.org/pause/0.0.1
npm http GET https://registry.npmjs.org/uid2/0.0.3
npm http GET https://registry.npmjs.org/raw-body/1.1.1
npm http GET https://registry.npmjs.org/negotiator/0.3.0
npm http GET https://registry.npmjs.org/multiparty/2.2.0
npm http 304 https://registry.npmjs.org/mime
npm http 304 https://registry.npmjs.org/uid2/0.0.3
npm http 200 https://registry.npmjs.org/qs/0.6.5
npm http GET https://registry.npmjs.org/qs/-/qs-0.6.5.tgz
npm http 304 https://registry.npmjs.org/pause/0.0.1
npm http 304 https://registry.npmjs.org/negotiator/0.3.0
npm http 304 https://registry.npmjs.org/multiparty/2.2.0
npm http 200 https://registry.npmjs.org/raw-body/1.1.1
npm http GET https://registry.npmjs.org/raw-body/-/raw-body-1.1.1.tgz
npm http 304 https://registry.npmjs.org/keypress
npm http 200 https://registry.npmjs.org/qs/-/qs-0.6.5.tgz
npm http 304 https://registry.npmjs.org/bytes/0.2.1
npm http 200 https://registry.npmjs.org/raw-body/-/raw-body-1.1.1.tgz
npm http GET https://registry.npmjs.org/readable-stream
npm http GET https://registry.npmjs.org/stream-counter
npm http 304 https://registry.npmjs.org/readable-stream
npm http 304 https://registry.npmjs.org/stream-counter
npm http GET https://registry.npmjs.org/core-util-is
npm http GET https://registry.npmjs.org/debuglog/0.0.2
npm http 304 https://registry.npmjs.org/core-util-is
npm http 304 https://registry.npmjs.org/debuglog/0.0.2
ejs@0.8.5 node_modules/ejs

express@3.4.5 node_modules/express
├── methods@0.1.0
├── range-parser@0.0.4
├── cookie-signature@1.0.1
├── fresh@0.2.0
├── debug@0.7.4
├── buffer-crc32@0.2.1
├── cookie@0.1.0
├── mkdirp@0.3.5
├── send@0.1.4 (mime@1.2.11)
├── commander@1.3.2 (keypress@0.1.0)
└── connect@2.11.1 (uid2@0.0.3, pause@0.0.1, qs@0.6.5, bytes@0.2.1, raw-body@1.1.1, negotiator@0.3.0, multiparty@2.2.0)
こんな具合になるので、実行して確認してみます。
[hogehoge@fugafuga slideapp]$ node app.js
Express server listening on port 3000
ブラウザで確認します。


コンソールにも出力されています。
GET / 200 10ms - 206b
GET /stylesheets/style.css 200 7ms - 110b
これでひとまず、express+ejsの環境は出来ました。

3.1:フォルダ構成

ちなみにexpressにおけるフォルダ構成はこんな具合でした。
▾ slideapp/
  ▾ node_modules/         //<-----npmでインストールされた者達。
    ▸ .bin/
    ▸ ejs/
    ▸ express/
  ▾ public/               //<-----ちなみにここがwebルートになるらしいよ。
    ▸ images/
    ▸ javascripts/
    ▸ stylesheets/
  ▾ routes/
      index.js            //<-----このjs内でviewとのバインド実装をしたらいいみたい。
      user.js
  ▾ views/
      index.ejs           //<-----ビューのテンプレート
    app.js                //<-----この中でメインロジック書いちゃいます。
                          //<-----URLと対応するroutesフォルダ配下の呼び出しルーティング設定はこちら。
                          (ホントはexportsとかで別ファイルで実装したほうが良さそうなんでしょうけど)
    package.json

4:foreverのインストール

次に永続化
npm install -g forever
としてインストール。 一番簡単な使い方
forever の手っ取り早い使い方。
forever start xxxx.js (開始)
forever stop xxxx.js (終了)
forever list (現在実行中のリスト)
さっき起動していたnodeをCtrl+Cで止めて、
ブラウザで動いていないことを確認。
今度はforeverで起動してみます。
[hogehoge@fugafuga slideapp]$ forever start app.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: app.js
[hogehoge@fugafuga slideapp]$ 
ブラウザで確認します。


5:socket.ioのインストール

さて、これでようやくnodeによるコンテンツ出力までできたんですが、
まだwebsocketの環境が整っていません。
socket.ioを使います。
[hogehoge@fugafuga slideapp]$ npm install socket.io
npm http GET https://registry.npmjs.org/socket.io
npm http 200 https://registry.npmjs.org/socket.io
npm http GET https://registry.npmjs.org/socket.io-client/0.9.16
npm http GET https://registry.npmjs.org/policyfile/0.0.4
npm http GET https://registry.npmjs.org/redis/0.7.3
npm http GET https://registry.npmjs.org/base64id/0.1.0
npm http 304 https://registry.npmjs.org/base64id/0.1.0
npm http 304 https://registry.npmjs.org/socket.io-client/0.9.16
npm http 304 https://registry.npmjs.org/policyfile/0.0.4
npm http 200 https://registry.npmjs.org/redis/0.7.3
npm http GET https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http 200 https://registry.npmjs.org/redis/-/redis-0.7.3.tgz
npm http GET https://registry.npmjs.org/uglify-js/1.2.5
npm http GET https://registry.npmjs.org/ws
npm http GET https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http 304 https://registry.npmjs.org/uglify-js/1.2.5
npm http 304 https://registry.npmjs.org/active-x-obfuscator/0.0.1
npm http 304 https://registry.npmjs.org/ws
npm http 304 https://registry.npmjs.org/xmlhttprequest/1.4.2
npm http GET https://registry.npmjs.org/zeparser/0.0.5
npm http GET https://registry.npmjs.org/commander
npm http GET https://registry.npmjs.org/nan
npm http GET https://registry.npmjs.org/tinycolor
npm http GET https://registry.npmjs.org/options
npm http 304 https://registry.npmjs.org/tinycolor
npm http 200 https://registry.npmjs.org/commander
npm http 304 https://registry.npmjs.org/zeparser/0.0.5
npm http 304 https://registry.npmjs.org/nan
npm http 304 https://registry.npmjs.org/options

> ws@0.4.31 install /var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws
> (node-gyp rebuild 2> builderror.log) || (exit 0)

make: ディレクトリ `/var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/build' に入ります
  CXX(target) Release/obj.target/bufferutil/src/bufferutil.o
  SOLINK_MODULE(target) Release/obj.target/bufferutil.node
  SOLINK_MODULE(target) Release/obj.target/bufferutil.node: Finished
  COPY Release/bufferutil.node
  CXX(target) Release/obj.target/validation/src/validation.o
  SOLINK_MODULE(target) Release/obj.target/validation.node
  SOLINK_MODULE(target) Release/obj.target/validation.node: Finished
  COPY Release/validation.node
make: ディレクトリ `/var/www/html/noderoot/slideapp/node_modules/socket.io/node_modules/socket.io-client/node_modules/ws/build' から出ます
socket.io@0.9.16 node_modules/socket.io
├── base64id@0.1.0
├── policyfile@0.0.4
├── redis@0.7.3
└── socket.io-client@0.9.16 (xmlhttprequest@1.4.2, uglify-js@1.2.5, active-x-obfuscator@0.0.1, ws@0.4.31)

6:クライアントサイドの実装

操作パネルつきのやつを作ってそれを登壇者
操作パネルだけ引っこ抜いたものを聴衆者にしちゃいます。
あと、スライドの制御用とソケットの制御用って2つの
JSファイルに分けちゃいました。。
▾ slideapp/
  ▸ node_modules/
  ▾ public/
    ▸ images/
    ▾ javascripts/
        ClientApp.js            //<-----エントリポイントと本体
        jquery-1.10.2.min.js    //<-----ちょっと操作するので必要(ExtCore使えよっとか突っ込まれそうだけど)
        SlideController.js      //<-----スライド制御用
        socket.io.js            //<-----socket.ioのクライアント向けのフォルダから引っ張ってきた。
    ▸ slide/
    ▸ stylesheets/
  ▸ routes/
  ▾ views/
      audience.ejs     //<-----聴衆者側テンプレート
      speaker.ejs      //<-----登壇者側テンプレート
    app.js

6.1:登壇者側のテンプレート(audience.ejs)

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>SpeakerView</title>
        <script type="text/javascript" charset="utf-8" src="/javascripts/jquery-1.10.2.min.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/socket.io.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/SlideController.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/ClientApp.js"></script>
        <link rel="stylesheet" type="text/css" href="/stylesheets/slide.css"/>
    </head>
    <body>
        <div class="wrapper">
            <div class="content">
                <p class="slide"><img id="slide" src=""></img></p>
            </div>
            <div class="footer">
                <div class="backbtn">
                    <img id="backbtnimg" src="/images/seek-back.png" ></img>
                </div>
                <div class="forwardtn">
                    <img id="forwardbtnimg" src="/images/seek-forward.png" ></img>
                </div>
            </div>
        </div>
    </body>
</html>

6.2:聴衆者側のテンプレート(speaker.ejs)

フッター部分ぶっこ抜いた状態のものです。いいんです。みてもらうだけですから。
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>SpeakerView</title>
        <script type="text/javascript" charset="utf-8" src="/javascripts/jquery-1.10.2.min.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/socket.io.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/SlideController.js"></script>
        <script type="text/javascript" charset="utf-8" src="/javascripts/ClientApp.js"></script>
        <link rel="stylesheet" type="text/css" href="/stylesheets/slide.css"/>
    </head>
    <body>
        <div class="wrapper">
            <div class="content">
                <p class="slide"><img id="slide" src=""></img></p>
            </div>
        </div>
    </body>
</html>

6.3:スライドの制御(SlideController.js)

  • 初期化
  • パスの生成
  • ボタン制御
  • 画像差し替え
スライド制御なんですが、
インデックスだけもらってパスを生成して自分で取得すりゃいいでしょ。
ということにしたんでファイルパス生成情報だけは初期化時に渡す必要があります。
var SlideController = function (){};
SlideController.prototype = {};
(function () {
    var me = this;
    me._pageindex = 0;

    /**
     *     me._slidepath スライドパス生成
     */
    me._slidepath = function (){
        var me = this,
            path = '';
        if ($.isArray(me._slide.slide)
            && me._pageindex >= 0
            && me._pageindex < me._slide.slide.length) {

            path = me._slide.path + me._slide.slide[me._pageindex];

        }
        return path;
    }

    /**
     *     me.init 初期化
     * @param viewid スライド用DOMエレメントID
     * @param slideinfo
     * @return this
     */
    me.init = function (viewid,slideinfo) {
        var me = this;
        me._slide = slideinfo;
        me._viewid = viewid;
        return me;
    }

    /**
     *     me.reset リセット
     * @return this
     */
    me.reset = function () {
        var me = this;
        me._pageindex = 0;
        return me;
    }

    /**
     *     me.forward 進む
     * @return this
     */
    me.forward = function () {
        var me = this;
        ((me._pageindex + 1) <= (me._slide.slide.length-1)) ? me._pageindex++ : 0;
        return me;
    }

    /**
     *     me.back 戻る
     * @return this
     */
    me.back = function () {
        var me = this;
        me._pageindex = (me._pageindex-1 < 0) ? 0 : me._pageindex-1;
        return me;
    }

    /**
     *     me.getPageIndex 現在のページインデックス取得
     * @return _pageindex
     */
    me.getPageIndex = function() {
        var me = this;
        return me._pageindex;
    }

    /**
     *     me.disp スライド表示
     * @return this
     */
    me.disp = function(page) {
        var me = this;
        if ($.isNumeric(page)) {
            me._pageindex = page;
        }
        $('#' + me._viewid).attr('src',me._slidepath());
        return me;
    }

}).apply(SlideController.prototype);

6.4:エントリポイントと本体(ClientApp.js)

  • 初期化メッセージハンドラ
  • 表示メッセージハンドラ
  • ボタン押下時の制御
固定情報でテスト実装しておいて
最後にサーバと結合させて確認。
コネクト時にスライド情報わたしゃいいんだけど
ホントは他の人向けにスライド情報を切り替えたかったので
初期化メッセージとかにして別の口にした。
(コネクト時に引数を渡す方法を調べる時間とやる気すらなかったのは内緒。)
// エントリポイント
$(document).ready(function () {

    // スライドコントローラ
    var slide = new SlideController();

    // サーバに接続する
    var socket = io.connect('http://133.242.235.218:3000')

    // サーバに接続時
    socket.on('connect', function() {
        // ローカル確認用。
        /*slide.init('slide',{
            path: './slide/hoge/',
            slide: [
                'スライド1.jpg',
                'スライド2.jpg',
                'スライド3.jpg'
            ]
        });*/
        console.log('connect!!');
    });

    /**
     * スライド情報初期化
     */
    socket.on('init', function(slideinfo) {

        console.log('スライド情報初期化');
        slide.init('slide',slideinfo);
        slide.reset().disp();

    });

    /**
     * スライド表示指示
     */
    socket.on('show', function(page) {

        //console.log('ここでスライドのページを変更');
        //console.log(msg);
        slide.disp(page);

    });

    // メッセージを送る
    /**
     *     varsendmessage
     * @param     varmsg
     */
    var sendmessage = function (msg) {

        socket.send(msg);

    }
    /**
     * 戻るボタン
     */
    $('#backbtnimg').bind('click',function(){

        console.log('back押下');
        slide.back().disp();
        sendmessage(slide.getPageIndex());

    });

    /**
     * 進むボタン
     */
    $('#forwardbtnimg').bind('click',function(){

        console.log('forward押下');
        slide.forward().disp();
        sendmessage(slide.getPageIndex());

    });

});

7:サーバサイドの実装

expressと合わせる際の引っかかりどころは
createServerして出来たサーバをsocket.ioに設定する部分。
これやってないと404が返却されてきてハマりました。
今ググるとサクッと一発で出てきました。
当時どうやってググっていたんでしょうか。
▾ slideapp/
  ▸ node_modules/
  ▸ public/
  ▾ routes/
      audience.js  <-------このファイルです。
      speaker.js   <-------このファイルです。
  ▸ views/
    app.js      <-------このファイルです。

7.1サーバサイド本体(app.js)

  • クライアントからの接続準備
  • 接続時初期処理
  • メッセージ処理
サーバサイドは初期化時に特定フォルダ内のファイルリスト
の返却とメッセージの受け渡しのみです。
/**
 * Module dependencies.
 */

var express = require('express');
var fs = require("fs");
var io = require('socket.io');

//※ココ ルータ実装読込
var speaker = require('./routes/speaker');
var audience = require('./routes/audience');

var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

//※ココ routing URLとViewのヒモづけ。
app.get('/speaker', speaker.slide);
app.get('/', audience.slide);

// サーバ生成(メソッドチェーンせずに、一旦変数に確保) ★接続準備
var server = http.createServer(app);
// リスン開始
server.listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

// ソケット生成
var socketio = io.listen(server);
// コールバックbind
socketio.sockets.on('connection', function (socket) {

    //★メッセージ処理 showメッセージ メッセージをそのままshowとしてブロードキャスト
    socket.on('message',function(data) {
        // 表示イベント発行
        socket.broadcast.emit('show', data);
    });

    //★初期処理処理 接続してきた相手に初期化イベント発行(パスとファイルリスト送信)
    //http://nodejs.org/docs/v0.3.1/api/fs.html#fs.readdir
    fs.readdir('./public/slide/hogehoge/',function(err,files) {

        // 初期イベント発行
        socket.emit('init',{
            path: './slide/hogehoge/',
            slide: files
        });

    })

});

7.2 View(audience.js)

ejsをレンダリングします。
exports.slide = function(req, res){
  res.render('audience', { title: 'Audience' });
};

7.3 View(speaker.js)

ejsをレンダリングします。
exports.slide = function(req, res){
  res.render('speaker', { title: 'Speaker' });
};

感想

みんなが使えるように
フォルダ名リストでメニュー画面生成させて
フォルダ配下に画像突っ込めばそれっぽく使えるように。
と思ったのですが平日むちゃくちゃ忙しくて
実装する暇が全くなく。、間に合わず。
当日ぶっつけリハーサル無しで使ってみたんですが、
0.5〜1秒ほど遅延することがありました。(しょぼショボ)
セカンダリサーフェスとして裏に読み込み用にimgタグつくって
事前読込とかしておくといいかもしません。
ですが、まぁみんなのスマホに私のスライドが
一斉に出力されたときのちょっと「ぉぉ」的な驚きの反応を
見れただけでも、まぁ、うれしかったです。。
ちょっとした時間で構築してちょっと驚かせる
(websocket知らない人限定だけど)
ことができるのもプログラムのおもしろさだな。
と再確認。

2012年12月7日金曜日

BanchaとBanchaScaffoldを利用した簡単なCRUD実装のまとめ

BanchaとBanchaScaffoldを利用した簡単なCRUD実装のまとめ



この記事は、Sencha Advent Calendar 2012
の7日目の記事です。
皆さんも是非、是非、参加してください。後半、まだまだ参加枠ありますよ!!そこのあなた!!



数カ月前にBanchaを試した時の備忘録を残してあったんですが、

一つのテーブルに対するCRUD実装を通して--
Scaffoldを利用するまでをまとめて紹介してみます。

ほとんど過去の備忘録の焼き直しですけど、、 


Banchaってなんだろうか



  • PHPのフレームワークであるCakePHP2.0に対応したExtJs用のプラグイン

  • ExtDirect用のモデル(Proxy設定含む)を生成してくれる

というのが主なところだと思います。 

ExtDirectについては@martini3ozさんのブログでわかりやすい
解説がされているのでそちらを参照して下さい。
他にExtDirectに対応するPHPフレームワークだと株式会社ゼノフィが
作成しているxFrameworkPXがあります。
Directについて理解を深めたいのであればそちらを利用し、コードを
眺めてみると仕組みを深く理解できると思います。

サーバサイドはCake2.Xで組みたい。

フロントエンドはExtJs作りこみたい。

ExtJsDirectを気軽に使いたい。

そんな方にはオススメできるかもしれません。 

BanchaScaffoldってなんだろうか


ExtJsにおけるデータの入出力の基本的な機能として、Grid、Formがあります。

ともに、ExtDirectを利用する際には、Model(Proxy含む)やStoreを設定したり、

サーバサイドのメソッド名を設定する必要があります。

このあたりを吸収して、「ある程度」の雛形を提供してくれるのが

BanchaScaffoldです。 

Grid、Formをオーバーライドして特定のパラメータに

反応するように作成されています。

ですので構造さえ知ってしまえば細かな変更などができると思うので

便利な機能になるんじゃないかと思っています。


Banchaの情報


http://banchaproject.org/resources.html

このページがすべてのリソースが集約されててお勧めかもです。 

CRUD実装の大きな流れ


このお話の大きな流れは、

1:Cake2.Xの準備

2:ExtJsの準備

3:MySQLの準備

4:Banchaの準備

5:Bakeの実行

6:Scaffoldの利用

7:動作確認 

になります。

前提


MAMPでUserDir配下で試してます。

今回は最新版を試している時間がないので、以前試したことのある

* Bancha1.0.0

* BanchaScaffold0.5.6

* Cake2.1.5

* ExtJs4.1.1a

の組み合わせでお話をすすめます。 

今日(2012/12/7)現在、Banchaは1.2.3がBanchaScaffoldは0.6.0が最新のようです。  
CakeのDispacharの実装が2.1系と2.2系で異なるため、BanchaとCakeの組み合わせには  
注意が必要です。


1:Cake2.0の準備





ユーザディレクトリの配置


私の場合、ユーザ配下のUserDirがユーザディレクトリのルートになっているので

その下にadventcalendarとでも作っておきましょう。 

[ユーザ]/UserDir/adventcalendar  

で、ルートを public_html としてあるので、それも作っておきます。 

[ユーザ]/UserDir/adventcalendar/public_html  

Cake2.XのDL


2.1.5をDLしてきます。

過去分なので、githubからタグされたバージョンで持ってきちゃいました。

こちらから

解凍して public_html 配下にまるっとコピーしておきます。 

▾ public_html/
  ▸ app/
  ▸ lib/
  ▸ plugins/
  ▸ vendors/
    .gitignore
    .htaccess
    .travis.yml
    build.properties
    build.xml
    index.php
    README

ユーザディレクトリ使ってるのでCakeの階層にしたがってRewriteBaseしてあげます。

このあたりはご利用になっている環境にあわせて適宜に設定してみて下さい。

過去の備忘録 

saltとseedの設定もしておきましょね。


2:ExtJsの準備





ExtJs4.1.1aのDL


ExtJsをDLしてきます。

解凍して適宜に配置します。 

ExtJSのスタートアップはゼノフィの技術情報ページや  
@martini3ozさんのページ、  
本家のチュートリアルを参照することをおすすめします。  

今回は public_html/app/webroot 配下に ext として配置しました。 



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
    ▸ View/
    ▾ webroot/
      ▸ css/
      ▸ ext/    <--ここ
      ▸ files/
      ▸ img/
      ▸ js/
        .htaccess
        favicon.ico
        index.php
        test.php
      .htaccess
      index.php
  ▸ lib/
  ▸ plugins/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~



設置用のindex.htmlの配置


MVC構成にするわけじゃなくて単なる表示確認です。

とりあえずパスが通ってることだけ確認しておきます。 



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
    ▸ View/
    ▾ webroot/
      ▸ css/
      ▸ ext/
        ~~~~~~~~~~~~~~~
        ~~~~~~~~~~~~~~~
      ▸ files/
      ▸ img/
      ▸ js/
        .htaccess
        bancha.php
        favicon.ico
        index.html  <--ここ
        index.php
        ~~~~~~~~~~~~~~~
        ~~~~~~~~~~~~~~~



404が出てないことだけ確認しておきましょ。

設置用のindex.htmlの内容


index.html  
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title id="page-title">adventcalendar</title>
        <link rel="stylesheet" type="text/css" href="./ext/resources/css/ext-all.css"/>
        <script type="text/javascript" charset="utf-8" src="./ext/ext-all.js"></script>
    </head>
    <body>
        <p>hoge</p>
    </body>
</html>


3:MySQLの準備





DB、テーブル作成


次にDBを作ってMySQLにサンプル用のテーブルを作ってみましょう。 

DB名:adventcalendar
CREATE TABLE `calender` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL,
  `acount` varchar(255) NOT NULL,
  `comment` text NOT NULL,
  `created` date NOT NULL,
  `modified` date NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

こんな具合でしょうか。 

Cakeへの設定


Cakeに利用するDB情報を設定します。

/app/Config/.database.php.defaultがあるので

/app/Config/.database.php

とコピーしてから、中身を書き換えます。 

public $default = array(
    'datasource' => 'Database/Mysql',
    'persistent' => false,
    'host' => 'localhost',
    'login' => '*****',
    'password' => '*****',
    'database' => 'adventcalendar',
    'encoding' => 'utf8',
);



▾ public_html/
  ▾ app/
    ▾ Config/
      ▸ Schema/
        acl.ini.php
        acl.php
        bootstrap.php
        core.php
        database.php  <-- ここ
        database.php.default




4:Banchaの準備





Bancha1.0.0をDLしてきます。

過去分なので、githubからタグされたバージョンで持ってきます。

こちらから。 

Banchaの設置


解凍して public_html/app/Plugin/Bancha に配置します。 



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    略
    ~~~~~~~~~~~~~~~
    ▸ Model/
    ▾ Plugin/
      ▾ Bancha/  <--ここ
        ▸ _app/
        ▸ Config/
        ~~~~~~~~~~~~~~~
        略
        ~~~~~~~~~~~~~~~
    ▸ Test/
    ▸ tmp/
    ~~~~~~~~~~~~~~~
    略
    ~~~~~~~~~~~~~~~



プラグインの設定


プラグインの設定はこちらを参考に行います。 

/public_html/app/Config/bootstrap.phpに 

CakePlugin::load(array('Bancha' => array('routes' => true, 'bootstrap' => true)));

/publichtml/app/Plugin/Bancha/app/webroot/bancha.php



/public_html/app/webroot/bancha.php

にコピー。 

public_html/app/Config/core.phpに 

/**
 * Configure the cache for Banchas Remote API.
 */
Cache::config('_bancha_api_', array(
    'engine' => $engine,
    'prefix' => $prefix . 'bancha_api_',
    'path' => CACHE . 'models' . DS,
    'serialize' => ($engine === 'File'),
    'duration' => $duration
));

を追記。 

確認


設定がうまく行っているか確認します。 

http://localhost/~adventcalendar/Bancha/setup-check.html

こんな具合になればOKです。 

bancha疎通確認

5:Bakeの実行





ここまで来るとあとは実装が残るだけとなります。

Bakeというバッチを使用してCakePHPのModel,Contollerを作成します。 


長くなってしまうので省略しますが、ヤルことは

こちらとほぼ一緒です。 

コンソールからappフォルダ配下に移動してbakeを実行します。 

cd UserDir/public_html/app
./Console/cake bake

DBの接続情報を作る


(cake設定時に作成しているのであればここでは作らなくてもOK)

Modelを作る[M]


(作ったあとに、モデルに下記[<--ここ]を追加して下さい。これによりモデルが公開されることになります。)
Calendar.php
<?php
App::uses('AppModel', 'Model');
/**
 * Calendar Model
 *
 */
class Calendar extends AppModel {

    /**
     * Bancha behaviour
     */
    public $actsAs = array('Bancha.BanchaRemotable');  <-- ここ
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~

Controllerを作る[C]


(Would you like to create some basic class methods  
(index(), add(), view(), edit())? (y/n) 
と聞かれた時に「y」として下さい。)  
(templateの選択を問われますので「bancha」を選択して下さい)



▾ public_html/
  ▾ app/
    ▸ Config/
    ▸ Console/
    ▾ Controller/
      ▸ Component/
        AppController.php
        CalendarsController.php  <--ここ
        ContollersController.php
        PagesController.php
    ▸ Lib/
    ▸ Locale/
    ▾ Model/
      ▸ Behavior/
      ▸ Datasource/
        AppModel.php
        Calendar.php  <--ここ
    ▸ Plugin/
    ▸ Test/
    ▸ tmp/
    ▸ Vendor/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~



ここまででサーバサイドの設定などの準備は完了です。


6:Scaffoldの利用





Scaffoldの準備


ScaffoldをDLしてきます(0.5.6)。

webroot配下に設置しました。 



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
    ▸ View/
    ▾ webroot/
      ▾ bancha/ <-- ここの配下のこと
          bancha-scaffold-debug.js*
          bancha-scaffold-production.js*
          bancha-scaffold.js*
      ▸ css/
      ▸ ext/
        ~~~~~~~~~~~~~~~
        ~~~~~~~~~~~~~~~



Extjsの準備


MVC構成としてのフォルダ配置をしておきます。

フォルダ構成の話とかだと、、手っ取り早いのは

ゼノフィの技術者ブログか 

ExtJsのサンプル:

public_html/app/webroot/ext/examples/MVC/pandora/ 

を持ってきてもいいかと思います。ちょっとここは省略。



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
    ▾ webroot/
      ▸ css/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
      ▾ js/
        ▾ app/  <--ここの配下のことです。
          ▾ controller/
          ▾ model/
          ▾ store/
          ▸ view/
            app.js
          empty
        .htaccess



index.htmlへの準備


Banchaを利用するため


  • Bancha.jsを読み込みます

  • bancha-api.jsを読み込みます

BanchaScaffoldを利用するため


  • bancha-scaffold.jsを読みます



<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title id="page-title">adventcalendar</title>
        <link rel="stylesheet" type="text/css" href="./ext/resources/css/ext-all.css"/>
        <script type="text/javascript" charset="utf-8" src="./ext/ext-all.js"></script>
        <!-- include Bancha and the remote API -->
        <script type="text/javascript" charset="utf-8" src="Bancha/js/Bancha.js"></script>      <--ここ
        <script type="text/javascript" charset="utf-8" src="bancha-api.js"></script>            <--ここ
        <!-- include Bancha and the remote API -->
        <script type="text/javascript" charset="utf-8" src="bancha/bancha-scaffold.js"></script><--ここ
        <script type="text/javascript" charset="utf-8" src="bancha-api.js"></script>
        <script type="text/javascript" src="./js/app/app.js"></script>
    </head>
    <body>
    </body>
</html>



いよいよScaffoldを利用します


Calendar用のGridを一つ作成します。 

Calendargrid.js

Ext.define('ADV.view.Calendargrid', {
    extend : 'Ext.grid.Panel',
    alias  : 'widget.calendargrid',
    scaffold :'Bancha.model.Calendar'  <-- ここがみそ。
});

Viewportに設置します。

Viewport.js

Ext.define('ADV.view.Viewport', {
    extend: 'Ext.container.Viewport',
    requires: [
        'ADV.view.Calendargrid',
    ],
    initComponent: function() {
        this.items = [{
            xtype:'calendargrid'
        }];
        this.callParent();
    }
});

CSSファイル作って削除用ボタンのパスを通しておきます。

style.css

.icon-add{
    background-image: url(../icons/add.png) !important;
}
.icon-delete{
    background-image: url(../icons/delete.png) !important;
}
.icon-save{
    background-image: url(../icons/disk.png) !important;
}
.icon-reset{
    background-image: url(../icons/arrow_undo.png) !important;
}

index.html

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <title id="page-title">adventcalendar</title>
        <link rel="stylesheet" type="text/css" href="./ext/resources/css/ext-all.css"/>
        <link rel="stylesheet" type="text/css" href="./js/resources/css/style.css"/>  <--ここ
        ~~~~~~~~~~~~~~~
        ~~~~~~~~~~~~~~~

あと、Grid内のactioncolumnの画像はパス指定になっているので
それも変更します。(Installationに載ってました。。)

app.js

Ext.Loader.setConfig({
    enabled: true,
    paths: {
        'Ext': './ext/src',
        'ADV': './js/app'
    }
});
Bancha.onModelReady([
    // ここにモデル名指定
    'Calendar'
        ],function(){
    // {{{ Ext.application
    Ext.application({
        name: 'ADV',
        appFolder: './js/app',
        autoCreateViewport: true,
        launch: function() {
            alert('app launch!!');
        }
    });
});
Bancha.scaffold.Grid.destroyButtonConfig.items[0].icon =
                  './js/resources/icons/delete.png';



▾ public_html/
  ▾ app/
    ▸ Config/
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~
    ▸ View/
    ▾ webroot/
      ▸ bancha/
        ~~~~~~~~~~~~~~~
        ~~~~~~~~~~~~~~~
      ▾ js/
        ▾ app/
          ▸ controller/
          ▸ model/
          ▸ store/
          ▾ view/
              Calendargrid.js   <-- ここ
              Viewport.js       <-- ここ
            app.js              <-- ここ
        ▾ resources/
          ▾ css/
              style.css         <-- ここ
          ▾ icons/              <-- ここ
              add.png           
              arrow_undo.png
              delete.png
              disk.png
          empty
        .htaccess
        bancha.php
        favicon.ico
        index.html              <-- ここ
    ~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~




7:確認





こんな感じになると思います。

実行画面


画面はまだ簡素ですが、

これでサーバとの通信ができることになります。

とっても単純なCRUD実装ですが、

独自のカスタマイズができるように

基底クラスを作成できれば使いやすくなりますね。 


scaffoldはformにも対応しているので

さっと作りたいときなどは重宝するのではないでしょうか。 


あとは慣れてきたら

APIドキュメント見ながらソースと照らし合わせながら

遊んでみると面白いと思います。。

http://scaffold.banchaproject.org/

http://scaffold.banchaproject.org/docs/# 



駆け足でしたが

こんな具合でBanchaはお手軽に導入できます。

っということで@martini3ozさんからのフリがあったのでつないでみました。 


このBanchaは先の@marini3ozさんの記事、Proxyについてを

読んでからみるとなかなかおもしろいと思います。 


ほんとは、このあとBanchaScaffoldをいじってカスタマイズする記事を

書きたかったのですが、今、高熱出してまして続きが書けませんでした。。

もうむりっす。。 ごめんなさい。


さてさて、残すは青さんかな(笑 

2012年9月18日火曜日

bancha scaffoldで独自のボタンを追加するときのscope設定(this)

Scaffoldのbuttonsに独自のボタンを設定するとき、 scope:'scaffold-scope-me'を指定。 するとthisはgetPanel(),getForm()を有するオブジェクトを返すから、 そっからfireEventしてContorllerで引っ掛ける。
buttons:[{
    text: 'search',
    scope: 'scaffold-scope-me',
    handler : function(){
        var me = this;
        me.getPanel().fireEvent('search');
    }
}],
Scaffoldで検索用のフォームとか、独自のボタンをくっつけるときのこと。

2012年9月8日土曜日

markdown2impressを触ってみた。

社内でちょっとしたことをやるのでパワポ使ってもいいんですがせっかくだし、Markdown紹介がてら。。なぁ。。。

とおもって少しだけ変わったことでもしようかと。

markdownをimpressjs化する方法が こちらや、 こちらで紹介されていたのでこれをやってみようかと思います。 参考にさせていただきました。ありがとうございます!

両サイトを参考にさせてもらいつつ、試してみます。

環境

perlって入ってるんだっけか?

まずはそこなんだけど、ゴリゴリ入れます。

$sudo port install perl5

環境入れます。

git://github.com/yoshiki/markdown2impress.git

をクローンします。それだけ。

$git clone git://github.com/yoshiki/markdown2impress.github

実行して環境が整ってないことで怒られる。

$ ./bin/markdown2impress.pl README.md                                  12-09-08
Can't locate Data/Section/Simple.pm in @INC (@INC contains: /opt/local/lib/perl5/site_perl/5.12.4/darwin-thread-multi-2level /opt/local/lib/perl5/site_perl/5.12.4 /opt/local/lib/perl5/vendor_perl/5.12.4/darwin-thread-multi-2level /opt/local/lib/perl5/vendor_perl/5.12.4 /opt/local/lib/perl5/5.12.4/darwin-thread-multi-2level /opt/local/lib/perl5/5.12.4 /opt/local/lib/perl5/site_perl /opt/local/lib/perl5/vendor_perl/5.12.3 /opt/local/lib/perl5/vendor_perl .) at ./bin/markdown2impress.pl line 6.
BEGIN failed--compilation aborted at ./bin/markdown2impress.pl line 6.

いやーんな感じです。

が、 ホント助かります。CPANって言うアーカイブからインストールするんですね。。 超助かりますし、しらんこと多すぎだし。そもそもPerl環境すらわからんし。。 お世話になりました。こちら

実行しつつ、怒られたものをいれてきます。

$ sudo cpan Data/Section/Simple.pm 
$ sudo cpan Text/Markdown.pm
$ sudo cpan Text/Xslate.pm
$ sudo cpan Path/Class.pm

実行した結果。

$ ./bin/markdown2impress.pl README.md                                  12-09-08
froggugugugu@sasakiMBP:~/markdown2impress
$ ls                                                                   12-09-08
README.md   bin     css     index.html  js

見てみた。

sugeeee.

これはこれからも使わせて頂きます。 たのしいわ。

2012年8月27日月曜日

bancha scaffoldを触ってみた

Scaffoldを

Banchaをググってたらフォーラムに到着して
出会いました。
見てみたら、マーケットにありますね。。 早速ソースをDLしてざっと眺めるとどうやら
Banchaの仕様に適した(サンプルをデフォルトで実装してくれる様な)
コンフィグビルダ的なプロジェクトでした。 ざっくり言うとExt.grid.Panelをオーバライドして scaffoldコンフィグに反応するようにしてあって BanchaにあったGridコンフィグ、Formコンフィグを作ってくれる 仕組みです。 独自の実装はbeforeBuildなり、afterBuildなりで調整実装します。 早速お試しを。

設置

どこに設置するのが良いのかわからなかったけど、 ひとまずextと同階層に設置することしました。
▾ banchajs/ <--(これ)
    bancha-scaffold-debug.js
    bancha-scaffold-production.js
    bancha-scaffold.js
▸ css/
▸ ext/
▸ files/
▸ img/
▸ js/
  .DS_Store
  .htaccess
  bancha.php*
  favicon.ico
  index.html
  index.php
  test.php

ロード

index.htmlに追加します。しばらくはコード見つつ使うと思うのでデバグ版です。
<script type="text/javascript" charset="utf-8" src="./banchajs/bancha-scaffold-debug.js"></script>

サンプルを改造(View)

ビルドコンフィグ内で、デフォルトの設定をしてくれているので なんもいじらんのであれば、、
// {{{ 'BE.view.Usergrid'
/**
 * @class BE.view.Usergrid
 * desctription
 */
Ext.define('BE.view.Usergrid', {
    // {{{ extend

    extend : 'Ext.grid.Panel',

    // }}}
    // {{{ alias

    alias : 'widget.usergrid',

    scaffold :'Bancha.model.User'

    // }}}
});
// }}}
これだけでOK。そう、これだけでOKです。 scaffoldにBanchaが提供するモデル名を設定します。
※このscaffold、オブジェクト指定でscaffold:{target:'Bancha.model.User'}として 実装することも可能ですが、その際はModelManagerにすでに登録されていることが前提となるので 何らかの問題で引っかかったときはこのあたりを見てみるといいかもです。
このパネルのinitConponentなりでBancha.getModel('User')とかして ModelManagerに登録しておくひつようがありますね。。

Store(サンプルを改造)

実はストアはBanchaのScaffoldがModelを利用して生成してくれるので Storeは必要ありません。削除しちゃいます。(わかりやすくコメントアウトとしておきます)

Controller(サンプルを改造)

各ボタンの実装もハンドラで直接結びつけちゃっているので この例だとコントローラも必要なくなっちゃいます。 (通常は必要になるとは思うのですけど)

実行してみる(画像が出ない)

画像が出ない。。。 そう、内部で設定される画像の設定、パスが通ってません。 icon-add、icon-save、icon-reset、icon-deleteをCSSに定義しておきます。
.icon-add {
    background-image: url(../icons/add.png) ! important;
}
.icon-delete: {
    background-image: url('../icons/delete.png') ! important;
}
.icon-save {
    background-image: url('../icons/disk.png') ! important; 
}
.icon-reset {
    background-image: url('../icons/arrow_undo.png') ! important; 
}
あと、Grid内のactioncolumnの画像はパス指定になっているので それを変更しなくてはなりません。。 なんかとりあえずの対応になってしまうけど、、
afterBuild : function (gridconfig) {

    // 削除ボタンコンフィグのreconfig
    var i;
    for (i = 0; i< gridconfig.columns.length; i++ ){
        if (gridconfig.columns[i].xtype == 'actioncolumn'){
            Ext.apply(gridconfig.columns[i].items[0],{icon:'js/src/resources/icons/delete.png'});
        }
    }
}
ひとまずxtypeで引っ掛けてパス変えちゃいました。
内部で固定設定されているメッセー文言とか、、
そとに口を引っ張りだして加工できるようにしたいですね。

userテーブルのBanchaScaffoldでの表示

現状況

▾ public_html/
  ▾ app/
    ▸ Config/
    ▸ Console/
    ▸ Controller/
    ▸ Lib/
    ▸ Locale/
    ▸ Model/
    ▸ Plugin/
    ▸ Test/
    ▸ tmp/
    ▸ Vendor/
    ▸ View/
    ▾ webroot/
      ▾ banchajs/
          bancha-scaffold-debug.js
          bancha-scaffold-production.js
          bancha-scaffold.js
      ▸ css/
      ▸ ext/
      ▸ files/
      ▸ img/
      ▾ js/
        ▾ src/
          ▾ app/
            ▾ controller/
                Main.js
                Usergrid.js
            ▾ model/
            ▾ store/
                user.js
            ▾ view/
                Usergrid.js
                Viewport.js
          ▾ resources/
            ▾ css/
                style.css
            ▾ icons/
                add.png*
                arrow_undo.png*
                delete.png*
                disk.png*
                image_add.png*
                user_edit.png*
            app.js
          empty
        .DS_Store
        .htaccess
        bancha.php*
        favicon.ico
        index.html
        index.php
        test.php
      .DS_Store
      .htaccess
      index.php
  ▸ lib/
  ▸ plugins/
  ▸ vendors/
    .gitignore
    .htaccess
    .travis.yml
    build.properties
    build.xml
    index.php
    README

2012年8月19日日曜日

banchaを触ってみた4

疎通させてみる

Bancha使ってみたくて
cakeをざっと流し読みしたけど、
まだ疎通できてないので疎通させてみようと思います。
お題はBanchaのサンプルをMVCに再配置、と、
テスト的にコンポーネントからイベント発火させて
Mainコントローラでディスパッチするようにしてみました。
A.View(何らかの操作)

Aコントローラ(ビューをコンポーネントクエリで引っ掛けて)
Ext既存イベントの処理→A.fireEventで独自イベント発行

Mainコントローラ(ビューをコンポーネントクエリで引っ掛けて)
独自インベントのディスパッチ
AコントローラをgetControllerで取得してAコントローラ.実装処理を呼び出し
なかんじで複数コントローラ時な感じを想定して見ました。

アプリの作成

ここからはExtJS4の話になります。
xframeworkであればwebapp下にsrcとか掘ってアプリを設置するんですが、
Cakeで言うところだと、、、
webroot/js/srcとかになるのでしょうかね。
あとは株式会社ゼノフィさんの技術ページ(code:x)か@martini3ozさんの
ページを見ながら実践開発ガイドのクラスローダ部分を読み返せばスタートアップ設置はOKです。
大掛かりな設置をすると大変なので
ビューポートにGridのみ入れて
Banchaのサンプルを切り出しながら再整形して
CRUDの確認だけしてみます。

example.htmlについて

やることは
  • extのロード
  • Banchaの準備
ということ。
<head>
    <title>Bancha Example</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <meta http-equiv="Content-Style-Type" content="text/css" /> 
    <meta http-equiv="Content-Script-Type" content="text/javascript" /> 

    <link rel="stylesheet" type="text/css" href="./ext/resources/css/ext-all.css" />

    <script type="text/javascript" src="./ext/ext-all-debug.js"></script>
    <script type="text/javascript" src="./ext/locale/ext-lang-ja.js"></script>

    <!-- include Bancha and the remote API -->
    <script type="text/javascript" src="./Bancha/js/Bancha.js"></script>
    <!-- for the github version use /Bancha/js/Bancha.js -->
    <script type="text/javascript" src="./bancha-api/models/all.js"></script>

    <!-- ExampleApp の設置 -->
    <script type="text/javascript" src="./js/src/app.js"></script>

</head> 
<body> 
</body> 
</html>
こんな感じになるのでしょうか。 404帰ってきてないこと確認しておきます。

app.jsとviwportの設置

  • addProviderの関係上、先行してExt.OnReadyが走る必要があり、
    これはBancha.OnModelReady内でExt.OnRadyしている。  
ので、OnModelReadyのコールバックでExt.applicationしてやることに
してみました。
設置については、、
* snippetsでひな形配置して
* namespace決めて
* app名決めて
* launchにalertとか設置して動作確認。
* 最初はcontrollersプロパティをコメントアウトして表示される
のを確認してもいいかもしれないです(ブランク開くと表示すら躓くのでひとつづ表示させながら)
で、
こんなかんじになると思います。

  • app.js
アプリケーションについてはローダーの設定とか重要なところになります。
これは実践開発ガイドの77ページ目のローダの説明を見ると理解が深まります。
Ext.ns('BE');

// Ext.Loader有効化
Ext.Loader.setConfig({
    enabled: true,
    paths: {
        'Ext': './ext/src',
        'BE': './js/src'
    }
});

//Bancha 初期化
Bancha.onModelReady('User',function(){

    //alert('hoge');

    // アプリケーション設定
    Ext.application({

        // Viewport自動生成
        autoCreateViewport: true,

        // アプリケーション名
        name: 'BE',

        // アプリケーションフォルダパス
        appFolder: './js/src/app',

        // 使用コントローラー定義
        controllers: [
        'Main',
        'Usergrid'
        ],

        // アプリケーション起動時イベントハンドラ
        launch: function() {

            //alert('fuga');

        }

    });
});
サーバサイドのモデルが増えたら、、
onModelReadyの第一パラメタも増やしていきます。
Bancha.onModelReady(['User','hoge1','hoge2'],function(){
みたいにね。

  • viewport.js
ビューポートはCenterにグリッドのみ設置。xtypeで指定します。
// {{{ BE.view.Viewport

Ext.define('BE.view.Viewport', {

    // {{{ requires

    requires: [
        'BE.view.Usergrid'
    ],

    // }}}
    // {{{ extend

    extend: 'Ext.container.Viewport',

    // }}}
    // {{{ layout

    layout: {
        type: 'border',
        padding: 5
    },

    // }}}
    // {{{ items

    items: [{

        region: 'center',
        //html: 'hoge'
        xtype: 'usergrid'

    }]

    // }}}

});

// }}}

Controllerの設置

上記のcontrollersをコメントインして(最初はコメントアウトしてhogeだけ表示させてた)Main,Usergridのコントローラを作成。
* MainControllerの設置をします。
* UsergridControllerの設置をします。
* これもスニペットで作成。設置のみ。ほとんどコメントアウト状態。以下、例。

  • Main.js
メインコントローラはコンポーネントからの独自イベントをディスパッチするように しておきます。
// {{{ 'BE.controller.Main',
/**
 * @class BE.controller.Main
 * メインコントローラ
 */
Ext.define('BE.controller.Main', {
    // {{{ extend

    extend : 'Ext.app.Controller',

    // }}}
    // {{{ refs インスタンス化されたViewへの参照提供 get([R]efname)
    /*
    refs: [{
        selector: 'xtype..',ref: 'localname'
    }],
    */
    /*
    // }}}
    // {{{ views クラスへの参照提供 get(Xxxx)View

    views:[
        'Views'
    ],

    // }}}
    // {{{ models クラスへの参照提供 get(Xxxx)Model

    models:[
        'Models'
    ],

    // }}}
    // {{{ stores インスタンスへの参照提供 get(Xxxx)Store

    stores: [
        'Store'
    ],

    // }}}*/
    // {{{ init

    //アプリケーション起動時にコールされる特別なメソッド
    //ApplicationのLaunch前にコールされる
    init: function() {

        //alert('hoge');
        var me = this;
        me.control(me.bindItem);

    },
    // {{{ bindItem
    bindItem: {

        // 作成
        'usergrid' : {
            create: function(){
                var me = this;
                alert('create dispatch');
                me.getController('Usergrid').onCreate();
            },
            reset: function(){
                var me = this;
                alert('reset dispatch');
                me.getController('Usergrid').onReset();
            },
            save: function(){
                var me = this;
                alert('save dispatch');
                me.getController('Usergrid').onSave();
            },
            del: function(rowIndex){
                var me = this;
                alert('save dispatch');
                me.getController('Usergrid').onDelete(rowIndex);
            }

            /*
            hoge: function(){
                var me = this;
                me.getController('Hoge').onHoge();
            },*/
        }

    }
/*
    // }}}
    // }}}
    // {{{ onXXXX

    onXXXX: function() {
        console.log('hoge');
    }
*/
    // }}}

});
// }}}

  • Usergrid.js
refとか汎用の名前にしたり、onXXとして実装と分離して抽象クラスとして
沈み込ませてそれを継承させたらすっきりするかなぁ。とおもってます。
// {{{ 'BE.controller.Usergrid',
/**
 * @class BE.controller.Usergrid
 * ユーザグリッドコントローラ
 */
Ext.define('BE.controller.Usergrid', {
    // {{{ extend

    extend : 'Ext.app.Controller',

    // }}}
    // {{{ refs インスタンス化されたViewへの参照提供 get([R]efname)

    refs: [{
        selector: 'usergrid',ref: 'crudgrid'
    }],

    // }}}
    // {{{ views クラスへの参照提供 get(Xxxx)View

    views:[
    ],

    // }}}
    // {{{ models クラスへの参照提供 get(Xxxx)Model
    // }}}
    // {{{ stores インスタンスへの参照提供 get(Xxxx)Store

    stores: [
        'User'
    ],

    // }}}
    // {{{ init

    //アプリケーション起動時にコールされる特別なメソッド
    //ApplicationのLaunch前にコールされる
    init: function() {

        var me = this;
        me.control(me.bindItem);

    },

    // }}}
    // {{{ bindItem
    bindItem: {

        // 作成
        'usergrid button[action=create]' : {
            click: function(){
                var me = this;
                alert('create');
                me.getCrudgrid().fireEvent('create');
            }
        },
        // リセット
        'usergrid button[action=reset]' : {
            click: function(){
                var me = this;
                alert('reset');
                me.getCrudgrid().fireEvent('reset');
            }
        },
        // 保存
        'usergrid button[action=save]' : {
            click: function(){
                var me = this;
                alert('save');
                me.getCrudgrid().fireEvent('save');
            }
        },
        // 削除
        'usergrid actioncolumn' : {
            click: function(view,cell,row,col,e){
                var me = this;
                alert('delete');
                me.getCrudgrid().fireEvent('del',row);
            }
        }

    },
    // }}}
    // {{{
    onCreate: function() {
        var me = this;
        me.create();
    },
    onSave: function() {
        var me = this;
        me.save();
    },
    onDelete: function(rowIndex) {
        var me = this;
        me.del(rowIndex);
    },
    onReset: function() {
        var me = this;
        me.reset();
    },
    // }}}
    // {{{
    create: function() { // scope is a config object
        var me = this,
            grid = me.getCrudgrid(),
            edit = grid.getPlugin('cellplugin'),
            store = grid.getStore(),
            model = store.getProxy().getModel(),
            rec;

        // Cancel any active editing.
        edit.cancelEdit();

        // create new entry
        rec = Ext.create(Ext.ClassManager.getName(model),{});

        // add entry
        store.insert(0, rec);

        // start editing
        edit.startEditByPosition({
            row: 0,
            column: 0
        });
    },
    save: function() { // scope is the store
        var me = this,
            valid = true,
            msg = "",
            name,
            grid = me.getCrudgrid(),
            store = grid.getStore();

        // check if all changes are valid
        store.each(function(el) {
            if(!el.isValid()) {
                valid = false;
                name = el.get('name') || el.get('title') || (el.phantom ? "New entry" : el.getId());
                msg += "<br><br><b>"+name+":</b>";
                el.validate().each(function(error) {
                    msg += "<br>&nbsp;&nbsp;&nbsp;"+error.field+" "+error.message;
                });
            }
        });

        if(!valid) {
            Ext.MessageBox.show({
                title: 'Invalid Data',
                msg: '<div style="text-align:left; padding-left:50px;">There are errors in your data:'+msg+"</div>",
                icon: Ext.MessageBox.ERROR,
                buttons: Ext.Msg.OK
            });
        } else {
            // commit create and update
            store.sync();
        }
    },
    del: function (rowIndex) {
        var me = this,
            grid = me.getCrudgrid(),
            store = grid.getStore();
        rec = store.getAt(rowIndex),
        name = Ext.getClassName(rec);

        // instantly remove vom ui
        store.remove(rec);

        // sync to server
        // for before-ExtJS 4.1 the callbacks will be ignored,
        // since they were added in 4.1
        store.sync({
            success: function(record,operation) {

                Ext.MessageBox.show({
                    title: name + ' record deleted',
                    msg: name + ' record was successfully deleted.',
                    icon: Ext.MessageBox.INFO,
                    buttons: Ext.Msg.OK
                });
            },
            failure: function(record,operation) {

                // since it couldn't be deleted, add again
                store.add(rec);

                // inform user
                Ext.MessageBox.show({
                    title: name + ' record could not be deleted',
                    msg: operation.getError() || (name + ' record could not be deleted.'),
                    icon: Ext.MessageBox.ERROR,
                    buttons: Ext.Msg.OK
                });
            }
        }); //eo store sync
    },
    reset: function() { // scope is the store
        // reject all changes
        var me = this,
            grid = me.getCrudgrid(),
            store = grid.getStore();

        store.each(function(rec) {
            if (rec.modified) {
                rec.reject();
            }
            if(rec.phantom) {
                store.remove(rec);
            }
        });
    }
});
// }}}

storeの設置

ストアの設置です。 で、いままでProxyの設定はStoreでやってたんですけど、、 Banchaが提供するModelにはすでにCakeにあわせたProxyの設定が 盛り込まれたModelを返却するのでストアはBanchaの提供するモデルを 設置するのみです。
// {{{ 'BE.store.User',
/**
 * @class BE.store.User
 * desctription
 */
Ext.define('BE.store.User', {
    // {{{ extend

    extend : 'Ext.data.Store',

    // }}}
    // {{{ model

    model: Bancha.getModel('User'),
    autoLoad: true

    // }}}

});
// }}}

modelの設置

モデルは要らないっす。 ModelはBanchaが提供してくれますので。 Bancha.getModel(‘モデル名’); で取得可能です。
または、Bancha.model.モデル名ですかね。。。

これで全部載せたかな。。。

all.jsからの返却

rpc用のメソッド一覧等の情報がちゃんとできているか見てみます。 all.jsのレスポスンスをJavaScriptBeautifierで整形してみました。 よさそうです。
Ext.ns('Bancha');
Bancha.REMOTE_API = {
    "url": "\/~banchatest\/bancha.php",
    "namespace": "Bancha.RemoteStubs",
    "type": "remoting",
    "metadata": {
        "User": {
            "idProperty": "id",
            "fields": [{
                "name": "id",
                "type": "int"
            }, {
                "name": "username",
                "type": "string"
            }, {
                "name": "password",
                "type": "string"
            }, {
                "name": "first_name",
                "type": "string"
            }, {
                "name": "last_name",
                "type": "string"
            }, {
                "name": "created",
                "type": "date",
                "dateFormat": "Y-m-d H:i:s"
            }, {
                "name": "modified",
                "type": "date",
                "dateFormat": "Y-m-d H:i:s"
            }],
            "validations": [],
            "associations": [],
            "sorters": []
            },
        "_UID": "5030537f74c0a675541393"
    },
    "actions": {
        "User": [{
            "name": "getAll",
            "len": 0
        }, {
            "name": "read",
            "len": 1
        }, {
            "name": "create",
            "len": 1
        }, {
            "name": "update",
            "len": 1
        }, {
            "name": "destroy",
            "len": 1
        }, {
            "name": "submit",
            "len": 1,
            "formHandler": true
        }],
        "Bancha": [{
            "name": "loadMetaData",
            "len": 1
        }]
        }
}
※注目すべきはxframeworkでもハマった
"formHandler": true
これ。これは.formのAPIに設定する用のハンドラにはこれをつけます。 xframeworkの時はドキュメントコメントに記載すると フレームワークがパースしてこのフラグを立ててくれてました。 超ハマったのでよく覚えてます。

実行してみる。





まとめ

で、、 プロトタイプを作成しよう!となると、
  1. UserDirの設置
  2. cake2.1.5の設置
  3. htaccessの変更
  4. Bancha0.9.5の設置(cake配下のPlugin下)
  5. bake実施(モデルとコントローラ)
  6. bakeで生成したモデルに追記
    public $actsAs = array(‘Bancha.BanchaRemotable’);
  7. Extjsの設置(cake配下app/webroot/js/配下)
  8. ExtJsMVCの設置
こんなかんじかな。
cake→banchaと夜な夜な帰宅後にやったわりには理解は思ったより早くてすっきりしてきた。 これを業務にフィードバックできればいいんだけど、
あとは、banchascaffoldだね。

感想

久々のMVCの設置、スニペットがあるのと無いのでは雲泥の差だし、
何より先の技術サイトのように一気通貫で説明があると助かるなぁ。。
と思いこのエントリも自分用に再度まとめ直したし、サンプルも作り替えた。
疲れた。。。

2012年8月5日日曜日

banchaを触ってみた3

概要の概要

出来上がったコントローラ見ると、 リクエスト情報の内、isBaanchaとかをみてSaveAndXXXとか 呼び出している。。

のはいいのだけど、

最初のおおまかな流れがつかめない。 気になるのがRPC用のメソッド一覧の取得契機。

ほんの少しだけ見てみた。 もうほんとこまかなロジックはすっ飛ばして 「どこでやってんのかしらね。。」 程度です。

いつものaddProviderはどこ?

まず、 Plugin/Bancha/webroot/js/Bancha.js

initメソッドで

Ext.direct.Manager.addProvider()

しているところがありました。

で、こいつに渡しているのがメソッド一覧のオブジェクトになるから、 上を追うとgetRemoteApiがある これはthis.remoteapi(Bancha.REMOTEAPI)となる。 このBancha.REMOTEAPIを事前にリクエストしておくひつようがあるわけで。

initはonModelReadyでExt.OnReadyをコールし、そのなかでinitとされている。

つまり

Bancha.onModelReady => Ext.OnReady => init => addProvider

となることはわかった。

Bancha.REMOTE_APIはどこで生成?

上記で事前に取得しておくBancha.REMOTEAPIについてざっと見てみる。 そもそもこのBancha.REMOTEAPIという命名はWikiにもあるように Config設定にある(grep結果)

/UserDir/banchatest/public_html/app/Plugin/Bancha/Config/bootstrap.php|23 col 30| Configure::write('Bancha.Api.remoteApiNamespace','Bancha.REMOTE_API');

で、この設定が利用されているのは

/app/Plugin/Bancha/Controller/BanchaController.php|86 col 78| $this->response->body(sprintf("Ext.ns('Bancha');\n%s=%s", Configure::read('Bancha.Api.remoteApiNamespace'), json_encode($api)));

のこの部分。

で/app/Plugin/Bancha/Lib/Bancha/BanchaApi.php でモデル一覧取得して、 モデル名を引数にコントローラへアクセスしてCrud用のAction名を取得している。

このあたりはCake特有の命名規則でルーティングしてクラス名をつくりだしたりしているので 深追いせずに次行っちゃいます。

つまり、

モデル名一覧 => モデル名からコントローラ => コントローラのCrud用メソッド一覧。

それを連想配列でredirect。

Ext.ns('Bancha'); Bancha.REMOTEAPI = jsonencodeした上記連想配列。

をredirect->bodyしているわけですね。

概要だけでも少し追うと安心できました。

そのBancha.REMOTE_APIはいつのリクエストで生成?

で、 このBancha.REMOTE_APIは、じゃぁ、いつのリクエストで生成されて ロードされているか。となるんだけど、、、 これは、bancha-api.jsを呼んで、 Banchaのルーティング設定でBanchaControllerのindexアクションが呼び出される 設定になってるっぽい(routes.php)。。(これはちょっと迷子になった。)

hoge.html内で

<!-- include Bancha and the remote API -->
<script type="text/javascript" src="Bancha/js/Bancha.js"></script>

※ここでBancha利用用のBanchaクラス群ロード


<script type="text/javascript" src="bancha-api.js"></script>

※これでサーバサイドでルーティングされてBanchaControllerのindexアクションコール
  返却でurl(RPC時のルーティングするurl)やメソッド一覧のJSON返却

このあとは各App用JSでOnModelReadyを実装してそのなかでaddProvider
ということはこのコールバック内に今までのエントリポイントとなる
メソッドを設置することになるでしょうかね。。

次は疎通。疎通~。

現状のフォルダ構成

▾ banchatest/
  ▾ public_html/
    ▾ app/
      ▸ Config/
      ▸ Console/
      ▸ Controller/
      ▸ Lib/
      ▸ Locale/
      ▸ Model/
      ▾ Plugin/
        ▾ Bancha/
          ▸ _app/
          ▾ Config/
              bancha-dispatcher-bootstrap.php
              bootstrap.php
              routes.php・・・・・・・・ここ
          ▸ Console/
          ▾ Controller/
              BanchaAppController.php
              BanchaController.php・・・・・・・・ここ
          ▾ Lib/
            ▾ Bancha/
              ▸ Exception/
              ▸ ExceptionHandler/
              ▸ Network/
              ▸ Routing/
                BanchaApi.php・・・・・・・・ここ
          ▸ Model/
          ▸ Test/
          ▾ webroot/
            ▾ js/
                Bancha.js*・・・・・・・・ここ
              setup-check.html
            .gitignore
            gpl-v3.txt
            license.txt
            README.md
            UpdateFromBeta3.txt
            VERSION.properties*
          empty*
      ▸ Test/
      ▸ tmp/
      ▸ Vendor/
      ▸ View/
      ▸ webroot/
        .htaccess*
        index.php*
    ▸ lib/
    ▾ plugins/
        empty*
    ▸ vendors/
      .gitignore*
      .htaccess*
      .travis.yml*
      build.properties*
      build.xml*
      index.php*
      README*

banchaを触ってみた2


bakeでBancha用のモデルとコントローラの作成

bakeで自動生成します。 まず、カレントディレクトリをappにして

cd UserDir/public_html/app

./Console/cake bake

を実行。

DB設定

Welcome to CakePHP v2.2.0 Console
---------------------------------------------------------------
App : app
Path: /Volumes/DataDrive/UserDir/banchatest2/public_html/app/
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> D
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:  
[default] > 
Datasource: (Mysql/Postgres/Sqlite/Sqlserver) 
[Mysql] > 
Persistent Connection? (y/n) 
[n] > 
Database Host:  
[localhost] > 
Port?  
[n] > 
User:  
[root] > web
Password:  
> ************************ 
Database Name:  
[cake] > tasks
Table Prefix?  
[n] > 
Table encoding?  
[n] > UTF8

---------------------------------------------------------------
The following database configuration will be created:
---------------------------------------------------------------
Name:         default
Datasource:       Mysql
Persistent:   false
Host:         localhost
User:         web
Pass:         ******
Database:     tasks
Encoding:     UTF8
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y
Do you wish to add another database configuration?  
[n] > n

Creating file /Users/froggugugugu/UserDir/banchatest/public_html/app/Config/database.php
Wrote `/Users/froggugugugu/UserDir/banchatest/public_html/app/Config/database.php`

次にモデル。

Welcome to CakePHP v2.2.0 Console
---------------------------------------------------------------
App : app
Path: /Users/froggugugugu/UserDir/cakephpTest/public_html/app/
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> M
---------------------------------------------------------------
Bake Model
Path: /Users/froggugugugu/UserDir/cakephpTest/public_html/app/Model/
---------------------------------------------------------------
Possible Models based on your current database:
1. TblPlan
2. TblProject
3. TblUser
Enter a number from the list above,
type in the name of another model, or 'q' to exit  
[q] > 3
What is the primaryKey?  
[Id] > 

.....略..................

.....途中でBancha用のテンプレート選択..................

Associations:
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y
---------------------------------------------------------------
You have more than one set of templates installed.
Please choose the template set you wish to use:
---------------------------------------------------------------
1. bancha
2. default
Which bake theme would you like to use? (1/2) 
[1] > 1

Baking model class for TblUser...

Creating file /Users/froggugugugu/UserDir/banchatest/public_html/app/Model/TblUser.php
Wrote `/Users/froggugugugu/UserDir/banchatest/public_html/app/Model/TblUser.php`

で、モデル出来上がり。

次にコントローラ

What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> C
---------------------------------------------------------------
Bake Controller
Path: /Users/froggugugugu/UserDir/banchatest/public_html/app/Controller/
---------------------------------------------------------------
Possible Controllers based on your current database:
---------------------------------------------------------------
 1. TblPlans
 2. TblProjects
 3. TblUsers
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 3
---------------------------------------------------------------
Baking TblUsersController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n) 
[y] > 
Would you like to use dynamic scaffolding? (y/n) 
[n] > 
Would you like to create some basic class methods 
(index(), add(), view(), edit())? (y/n) 
[n] > y
Would you like to create the basic class methods for admin routing? (y/n) 
[n] > 
Would you like this controller to use other helpers
besides HtmlHelper and FormHelper? (y/n) 
[n] > 
Would you like this controller to use any components? (y/n) 
[n] > 
Would you like to use Session flash messages? (y/n) 
[y] > 

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:
    TblUsers
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y

Baking controller class for TblUsers...

Creating file /Users/froggugugugu/UserDir/banchatest/public_html/app/Controller/TblUsersController.php
Wrote `/Users/froggugugugu/UserDir/banchatest/public_html/app/Controller/TblUsersController.php`

ひとまず完了

さっそく出来上がったものを見て見ます。

banchaを触ってみた


banchaを使ってみる

banchaのサイトは

こちらから

インストールから

まずはCakeの設置。

(bancha:0.9.5はcake2.1.X(現在は2.1.5)に対応しています。
Dispacherの実装が2.2.Xだとことなるため、BanchaのDispatcherの実装が合いません::つまづきました。。)

設置といってもUserDirに配置して

Config/database.php

を適宜に設定。 ユーザディレクトリ利用しているのであれば、

public_html下の.htaccessに

RewriteBase    /~banchatest/

public_html/app下の.htaccessに

RewriteBase    /~banchatest/app/

public_html/app/webrot下の.htaccessに

RewriteBase    /~banchatest/app/webroot/

を追記してひとまず完了。

Banchaの設置

ダウンロードしてきたフォルダをそのまま

public_html/app/Plugin/Bancha

として設置。

bootstrap.phpに追記

public_html/app/Config/bootstrap.phpに

CakePlugin::load(array('Bancha' => array('routes' => true, 'bootstrap' => true))); 

を追記。

bancha.phpファイルコピー

/Users/froggugugugu/UserDir/banchatest/public_html/app/Plugin/Bancha/_app/webroot/bancha.php

/Users/froggugugugu/UserDir/banchatest/public_html/app/webroot/bancha.php

にコピー。

シンボリックリンクってのもあるんだろうけど、、、

$ sudo ln -s /Users/froggugugugu/UserDir/banchatest/public_html/app/Plugin/Bancha/_app/webroot/bancha.php /Users/froggugugugu/UserDir/banchatest/public_html/app/webroot/bancha.php

これだと内部のファイルパス取得部分でPlugin配下を参照するようになってる?? みたいだったのでもうやめてファイルコピーで対応。

public_html/app/Config/core.php

末尾に

/**
 * Configure the cache for Banchas Remote API.
 */
Cache::config('_bancha_api_', array(
    'engine' => $engine,
    'prefix' => $prefix . 'bancha_api_',
    'path' => CACHE . 'models' . DS,
    'serialize' => ($engine === 'File'),
    'duration' => $duration
));

を追記。

設置できたかの確認。

http://localhost/~banchatest/Bancha/setup-check.html

OK..

Cakeでつかう(Bakeでひな形作成)

Bachaでクライアントサイドに公開する方法として モデルの保有するメソッドの公開とコントローラが保有するメソッドの 公開があるんだけど、一般的にはモデル公開。 このあたりはxFrameworkPXでもやったことあるので理解は楽。

モデルの公開方法

bakeで用意します。 こちら

cakeを触ってみた2

前回の続き。

CakePHP2.0

こちらををベースに写経していきます。

Viewについて

ビューはctpという拡張子で
Viewフォルダ配下にアプリケーション名のフォルダを作成して
その配下に、アクション名のctpファイルを作ればいいんだろう。

でデータのバインドは
コントローラ側で
$this->set(‘変数名’,変数);
という具合でできる。

ビュー側では
そいつを<?php echo $変数名 ?>とかで出力する。

次。
モデルに行きます。

DB

MySQLでDB作ります。
よくあるtbl_Userとかのユーザテーブルにしました。
CakeではXXXdatasっていうテーブル名にするみたい。
でもマスタはmstリソース、イベント系はtblとかに
する事が多いのでここはtbl_Userとしておきます。

と思いきや、
cakeの特有の命名規約があるみたい。
テーブルにはsがついた複数形として
モデルはsがつかない。
というルールにしておけばよさそう。。

tbl_Users

とすれば

TblUser

というモデル名となります。
※このあとでfind(‘list’);ってやった時にフィールド名にデフォルトのモデル名が利用されてSQLエラーが
発生したけど、エラー情報からオブジェクトにaliasを設定すればよさそうなことがわかり、対応。

  • controller = 機能要件に沿ったアプリケーション名として
  • model ≒ DBテーブル名 ≒ 実世界のデータ・モデル名に近い命名

DB接続情報

/app/Config/.database.php.defaultがあるので  
/app/Config/.database.php
としてコピーして利用します。 で、$testは今のところ利用しないので削除しちゃいます。 ひとまず勉強のためにやってますが、これはあとでbakeで対話式に 設定可能。

modelについて

DBの説明でもあったように命名に癖がある。

クラス定義

Controllerクラスの定義はまず、

App::uses('AppController','Controller');  

として利用できるように定義、
AppControllerクラスを継承して定義するみたい。

これも結局あとでbakeで生成されるけど、いちどはやっとかんとね。

ひとまず、写経してどのフォルダに何入れるか。。
ってのと命名規則のさわりだけはOKとした。

時間ないのでCakeについてはここまで。

次はBancha。