ラベル Development? の投稿を表示しています。 すべての投稿を表示
ラベル Development? の投稿を表示しています。 すべての投稿を表示

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。

2012年8月1日水曜日

cakeを触ってみた

先日、cakeを試してみようとおもってて、、
でもExtJSとの絡みを考えるとDirect使えんしなぁ
と呟いてたら@martini3ozさんがBanchaってのがそれっぽいよ。
と教えてくれました。
見てみます!と返信したもののCake自体が
チンプンカンプンなので入門の入門を触ってみました。

CakePHP2.0

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

ダウンロード~動かすまで

  1. DL
    2012/7/6現在の最新版のstableである2.2.0を使用してみます。
    こちらのTOP右側のリンクからDL可能。

※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
前のエントリでも書きましたが、Bancha0.9.5は2.2.Xシリーズには
対応しておらんようです。
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※

  1. 設置
    UserDir有効のためUsrDir/cakephpTest/public_html配下にcakephpTestとして配置してみた。
    で、このままだとnotfoundになるのでpublick_html配下の.htaccessに

    RewriteBase /~cakephpTest/

必然的にその配下の関連する.htaccessも書きかえました。

public_html/app配下の.htaccess

RewriteBase /~cakephpTest/app/  

public_html/app/webroot配下の.htaccess

RewriteBase /~cakephpTest/app/webroot/  

を追記。

  1. 確認

    localhost:8080/~cakephpTest/

アクセスして確認。
* DBの設定
* セキュリティソルト、セキュリティシードはあと。で設定。

Controllerについて

コントローラは/app/Controller配下に作る。
コントローラは(アプリケーション)ごとに設置と理解。
で、コントローラ名は(アプリケーション名:パスカルケースにして)+Controller.phpとするらしい。
ex)hogehogeというアプリケーション→HogehogeController.php
コントローラ(アプリケーション)は複数のアクションをまとめたもの?として理解。

ファイル命名規則は、パスカルケースで、

XxxxxxController.php  

として作成すること。
クラスの命名規則も同様に。

クラス定義

Controllerクラスの定義はまず、

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

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

アクション定義

publicメソッドがアクションとして認識されるようですね。
メソッド≒アクション。と。
基本、アクションが1URLに割り当てられる見たい。

アクション間遷移

  • リダイレクト

    public function index()
    {
    $this->autoRender = false;
    $this->redirect(‘./other’);/そのコントローラ(アプリケーション)内のotherメソッド(アクション)にリダイレクト/
    }

  • フォワード
    public function index()
    {
    $this->autoRender = false;
    $this->setAction(‘other’);/そのコントローラ(アプリケーション)内のotherメソッド(アクション)にフォワード(URLの変更は発生せず)/
    }

  • URLパラメタによる値渡し

    $this->redirect(‘./other/hoge/fuga/piyo’);

Modelの利用

モデル名がコントローラ名と一致した際に自動的に利用可能になるようです。
ただ、明示的に設定することも可能で、
var $uses = array(‘Recipe’, ‘User’);
のように$usesプロパティに指定することでインスタンス化されるようです。


お試しコード

<?php  

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

// {{{ sampleController  
/**  
 * sampleController Class  
 *  
 * sample  
 *  
 * @category   CategoryName  
 * @package    PackageName  
 * @copyright  2011-2011  
 * @license    http://www.opensource.org/licenses/mit-license.html MIT License  
 * @version    Release: sample  
 */  
class SampleController  
extends AppController  
{  
    // {{{ const  
    // }}}  
        // {{{ props  
    // }}}  
    // {{{ __construct  
    /**  
     * コンストラクタ  
     *  
     * @param type argname discription  
     **/  
//    function __construct($argname)  
//    {  
//        # code...  
//    }  
//    // }}}  
//    // {{{ __destruct  
//    /**  
//     * デストラクタ  
//     **/  
//    function __destruct()  
//    {  
//        # code...  
//    }  
    // }}}  
    // {{{ index  
    /**  
    *  
    *  
    * @param type $argname discription  
    * @return type discription  
    */  
    public function index()  
    {  
        $this->autoRender = false;  
        //var_dump('index');  
        //$this->redirect('./other/hoge/fuga/piyo');  
        //$this->setAction('other','hoge','fuga','piyo');  
        $params = array(  
            'hoge' => array(  
                'hoge1' => 'hogehoge1',  
                'hoge2' => 'hogehoge2',  
                'hoge3' => 'hogehoge3',  
            ),  
            'fuga' => array(  
                'fuga1' => 'fugafuga1',  
                'fuga2' => 'fugafuga2',  
                'fuga3' => 'fugafuga3',  
            )  

        );  
        //$this->setAction('other','hoge','fuga','piyo');  
        $this->setAction('other',$params);  
    }  

    //public function other($a,$b,$c){  
    public function other($params){  
        $this->autoRender = false;  
        //var_dump('other');  
        //var_dump($a);  
        //var_dump($b);  
        //var_dump($c);  
        var_dump($params);  
    }  

    // }}}  
}  
// }}}  

2012年7月29日日曜日

Bancha0.9.5 はCake2.1.5を食べるときにのむ。2.2.Xではない。

warningが出てるparseParamsでおきる+うまくいかない。Viewが無いとか言ってうまくいかない。。

(Cakeのバージョンがちがうよこれ。:後日談)


(後日追記):
CakeのDispatcherの実装がかなりかわってて
Banchaがそれにあわせて実装してあったからそもそも
うまく動きませんでした。よ。。。っていうオチです。

2.1.X系はDispatcherでinvoke呼び出しているからBanchaのinvokeが
呼び出されてバンザイなんだけど、

2.2.X系は_invoke呼び出されないし、それ以前にいろんなところで
関数のIF違うわなんだで動きません。

以下はリーディングした際のログ。
結局どうこういう以前のバージョン確認しろよお前。という
どうしようもなく目も当てられないミスを犯したのですが。。


疎通させている時の話

うまくいきません。うーまーくーいーかーなーいーよー。
帰宅後の僅かな時間しか取れない自分にとってほんのちょっとの疎通障害は
もうかなりきついのであーだこーだ言う前に急がば回れということで
ざっとソースをみてみます。。

warning発生箇所はBanchaSingleDispatchのparseParamsで第一引数はCakeRequestじゃなきゃダメだ。
みたいなこと言われてるっぽい。

perseParamのWarningは。。。

$dispatcher->dispatch($request, new CakeResponse(array('charset' => Configure::read('App.encoding'))), array('return' => true)), // second argument is expected to be overwritten by BanchaSingleDisptacher::_invoke

この第三引数のreturn=>trueが重要。
これは最後の方にBanchaSingleDispatchの_invokeで番茶用にカスタマイズしたロジックを
通らなくなってしまう。。
このarray(‘return’ => true)をrequest->param[‘return’]として追加する機構が
perseParamで行なっている

$request->addParams($additionalParams);

となります。。
さて、、、
ダンプしてみるとCakeEventがわたってきてる。
(これが例外のWarningの原因)

perseParamを追って見ることにする

grepしてみるとCakeのDispatcherのimplementedEventsに

return array('Dispatcher.beforeDispatch' => 'parseParams');

のようにあって、イベント名とメソッド名を結びつけているような実装がある。
もう一つはその設定のイベントを生成してコールしている部分ぽい実装として
BanchaDispatch::dispatch内の実装がある。
このBanchaDispatch::dispatchはbancha.phpから
呼び出されるBanchaの最初の関数(今後追加するブログのBanchaを使うところでも言っているけど)。

BanchaDispatch::dispatch内はというと、、

BanchaSingleDispach::がインスタンス化されて、そのBachaSingleDispatchのdispatchをしている。
BanchaSingleDispachはCakeのDispacherを継承しているので実際に呼ばれるdispacherはcakeのDispatcherになる。
基底Dispachでイベントが生成される(Dispatcher.beforeDispatch)。

(BanchaDispatch)  
public function dispatch(CakeRequest $request, CakeResponse $response, $additionalParams = array()) {  
    $beforeEvent = new CakeEvent('Dispatcher.beforeDispatch', $this, compact('request', 'response', 'additionalParams'));  

こんな具合で。。
ここで
CakeEventを生成してて、
ここに引き渡すのはそれぞれ、CakeEventクラスのメンバname、subject、dataに格納されるようです。。

CakeEvent->data = array($requestと$responseと$additionalParamsの配列)になる。。  

$beforeEventはEventManagerによってディスパッチされています。
イベント名で紐付けて呼び出されるみたい。。

(Dispach)  
$this->getEventManager()->dispatch($beforeEvent);  

EventManagerを掘り下げてみる。

このgetEventManager()はCakeEventManagerをインスタンス化して、
CakeEventManager->attach($this)になる。。。
この際の$thisはBanchaSingleDispatchになる。。。
BanchaSingleDispatch->Dispache->CakeEventListenerの継承関係だから。
attachのなかのif制御でinstanceof CakeEventListenerのになる
で、

(CakeEventManager->attach内)  
public function attach($callable, $eventKey = null, $options = array()) {  
   .....  
   $this->_attachSubscriber($callable);  

これが呼び出される。。callableはBanchaSingleDispatch。

attachSubscriberが呼び出される

この_attachSubscriber…は

(CakeEventManager->_attachSubscriber)  
protected function _attachSubscriber(CakeEventListener $subscriber) {  
    foreach ($subscriber->implementedEvents() as $eventKey => $function) {  
        $options = array();  
        $method = $function;  
        if (is_array($function) && isset($function['callable'])) {  
            list($method, $options) = $this->_extractCallable($function, $subscriber);  
        } elseif (is_array($function) && is_numeric(key($function))) {  
            foreach ($function as $f) {  
                list($method, $options) = $this->_extractCallable($f, $subscriber);  
                $this->attach($method, $eventKey, $options);  
            }  
            continue;  
        }  
        if (is_string($method)) {  
            $method = array($subscriber, $function);  
        }  
        $this->attach($method, $eventKey, $options);  
    }  
}  

となってて、implementedEventsは継承元のDispatcherとなるから
eventKey = ‘Dispatcher.beforeDispatch'
function = 'parseParams'
となる。

と、、
functionは文字列なので

$method = array( BanchaSingleDispatch, 'parseParams' )  

ということになる。
次にattachが呼び出されている。。。

attachが呼び出される

$this->attach($method, $eventKey, $options);  

この引数はそれぞれ、

$method = array( BanchaSingleDispatch, 'parseParams' )  
$eventKey = 'Dispatcher.beforeDispatch'  
$options = array()  

となる。。

(CakeEventManager->attach)  
   public function attach($callable, $eventKey = null, $options = array()) {  
    if (!$eventKey && !($callable instanceof CakeEventListener)) {  
        throw new InvalidArgumentException(__d('cake_dev', 'The eventKey variable is required'));  
    }  
    if ($callable instanceof CakeEventListener) {  
        $this->_attachSubscriber($callable);  
        return;  
    }  
    $options = $options + array('priority' => self::$defaultPriority, 'passParams' => false);  
    $this->_listeners[$eventKey][$options['priority']][] = array(  
        'callable' => $callable,  
        'passParams' => $options['passParams'],  
    );  
}  

listenerに登録される

ここでCakeEventManagerの_listenersに

['Dispatcher.beforeDispatch'][優先度][] = array('callable' => array(BanchaSingleDispatch,'parseParams'),'passParams'=>false)  

として登録される。
これでCakeEventMangerに登録される仕組みがわかりました。

今度は呼び出し側を追ってみます。

このdispatchの内容は
(CakeEventManager->dispatch内)

foreach ($this->listeners($event->name()) as $listener) {  
    if ($event->isStopped()) {  
        break;  
    }  
    if ($listener['passParams'] === true) {  
        $result = call_user_func_array($listener['callable'], $event->data);  
    } else {  
        $result = call_user_func($listener['callable'], $event);  
    }  
    if ($result === false) {  
        $event->stopPropagation();  
    }  
    if ($result !== null) {  
        $event->result = $result;  
    }  
    continue;  
}  

となってて、気持ちとしては'passParams === trueの制御が効いてほしいんだけど、
前述したように、_listenersにはpassParamsがfalseで登録されているから、
elseのほうに行って

$result = call_user_func($listener['callable'], $event);  

が呼び出されて、parseParamsの引数はCakeEventとしてよびだされているのではないか。

すこしだけ。Banchaを変更してみる。試しにperseParamをCakeEventを受け取る形のものを実装してみます。。

public function parseParams(CakeEvent $event) {  
    if (!empty($event->data['additionalParams'])) {  
        $event->data['request']->addParams($event->data['$additionalParams']);  
    }  
    return $request;  
}  

これだけど、そもそもCakeのDispatchの制御がそもそも違う。

Cake2.1.0だと
dispatchでthis->_invokeとコールされていて、そのさきはBanchaSingleDispatcherとなって
そのなかでreturnした結果をそのままdispatcherの戻り値として返却するからdispatcherとしての
制御ってそこで終わってた。。

Cake2.2.1だと
そもそも_invokeすら呼び出されていない氏ね。

で、ようやっとひと通りの大雑把な流れはつかめた

あれ?詰んだらしい。www
そもそもイベントリスナへ登録する時passParam =がfalseとして登録されてて、
途中どこにも更新される点がない点で詰んでる。

あれ?これはおかしくないかということで過去のソースを追ってみると
あ、あ、あ、あ、あ、あ
これならBanchaの_invokeも動くわな。。という実装。2.1.5で確認してみました。

このあたりの動きは そもそもこれはCakeの実装に依存している箇所だからね。
Cakeのバージョン2.1.0だとテストしたということ書いてあったけどね。
そりゃ2.2系はまだか。

お陰でBanchaと少し友だちになれましたww

ざんねん。
貴重な勉強時間がごっそりと削られました(まぁ、これも勉強になりましたが)。

2012年7月28日土曜日

JSDuckを触ってみた

JSDuckを利用してみる。

FaceBookで@martini3ozさんがシェアしてて
ググったら@kotsutsumiさんがTwitterで
質問に答えてて、
結果、こちらにお世話になりました。
さて何からすればいいのか。という取っ掛かりの部分、
とても助かりました。ありがとうございました。
チュートリアルを実施してみたので ちょっと残しておきます。

目指すゴール

左にメニューがあって 内容が切り替わる。と、 で、内容は都度MarkDownで作成できて任意に増やせればOK。

インストール

$ sudo gem install jsduck

ヘルプを見てみる。–helpで引いたら=fullつければ全て見れるってから全て見てみた。

$ jsduck --help=full                                                                                                                                                        12-06-18
Usage: jsduck [options] files/dirs...

-o, --output=PATH                Directory to output all this amazing documentation.
                                 This option MUST be specified (unless --stdout).
                                 Use dash '-' to write docs to STDOUT (only export).

    --ignore-global              Turns off the creation of global class.

    --external=Foo,Bar,Baz       Declares list of external classes.  These classes
                                 will then not generate warnings when used in type
                                 definitions or inherited from.

    --builtin-classes            Includes docs for JavaScript builtin classes.

    --meta-tags=PATH             Path to Ruby file or directory with custom
                                 meta-tag implementations.

    --encoding=NAME              Input encoding (defaults to UTF-8).

-v, --verbose                    This will fill up your console.

Customizing output:

    --title=TEXT                 Custom title text for the documentation.
                                 Defaults to 'Sencha Docs - Ext JS'

    --footer=TEXT                Custom footer text for the documentation.
                                 Defaults to: 'Generated with JSDuck {VERSION}.'

    --head-html=HTML             HTML to append to the <head> section of index.html.

    --body-html=HTML             HTML to append to the <body> section index.html.

    --welcome=PATH               Path to HTML file with content for welcome page.

    --guides=PATH                Path to JSON file describing the guides. The file
                                 should be in a dir containing the actual guides.
                                 A guide is a dir containing README.md, icon.png,
                                 and other images referenced by the README.md file.

    --videos=PATH                Path to JSON file describing the videos.

    --examples=PATH              Path JSON file describing the examples.

    --categories=PATH            Path to JSON file which defines categories for classes.

    --no-source                  Turns off the output of source files.

    --pretty-json                Turn on pretty-printing of JSON.

    --images=PATH                Search path for including images referenced by
                                 {@img} tag. Several paths can be specified by
                                 using the option multiple times.

    --link=TPL                   HTML template for replacing {@link}.
                                 Possible placeholders:
                                 %c - full class name (e.g. 'Ext.Panel')
                                 %m - class member name prefixed with member type
                                      (e.g. 'method-urlEncode')
                                 %# - inserts '#' if member name present
                                 %- - inserts '-' if member name present
                                 %a - anchor text for link
                                 Default is: '<a href="#!/api/%c%-%m" rel="%c%-%m" class="docClass">%a</a>'

    --img=TPL                    HTML template for replacing {@img}.
                                 Possible placeholders:
                                 %u - URL from @img tag (e.g. 'some/path.png')
                                 %a - alt text for image
                                 Default is: '<p><img src="%u" alt="%a"></p>'

    --export=TYPE                Exports docs in JSON.  TYPE is one of:
                                 * full     - full class docs.
                                 * api      - only class- and member names.
                                 * examples - extracts inline examples from classes.

    --seo                        Creates index.php that handles search engine traffic.

    --eg-iframe=PATH             An HTML file to use inside an iframe
                                 to display inline examples.

    --examples-base-url=URL      Base URL for examples with relative URL-s.

    --tests                      Creates page for testing inline examples.

    --stats                      Creates page with all kinds of statistics. Experimental!
と出力されます。

フォルダの配置を決めよう

作業用ディレクトリ作成
~/UserDir/jsducktest
としてみた。
出力用ディレクトリ指定
~/UserDir/jsducktest/release
としてみた。

テンプレートとなるhtmlを作成してみよう

index.htmlを作成して、下記を記載。
<h1>HELLO WORLD h1</h1>
<h2>HELLO WORLD h2</h2>
<h3>HELLO WORLD h3</h3>
<h4>HELLO WORLD h4</h4>
<h5>HELLO WORLD h5</h5>
<h6>HELLO WORLD h6</h6>
たったこれだけとしておく。
現在の状況
▾ jsducktest/
  ▸ release/
    .DS_Store
    index.html

※releaseはフォルダのみindex.htmlを作成してしてみる

ビルドしてみる。

  • 早速ビルドです。
    $jsduck -o ~/UserDir/jsducktest/release –welcome=~/UserDir/jsducktest/index.html
    ※パスはわざとフルパス指定にしてます。 ※作業用ディレクトリに移動してやると省略できてよいです。

できたらアクセス。

release配下のindex.htmlにブラウザからアクセスしてみます。
こんな感じになりました。

index.html(welcomeページのみ)

次に左のメニュー。

  • 作業用ディレクトリに「guides」ディレクトリを作成
    このディレクトリ配下のサブディレクトリごとにページを作るイメージです。 ですから、、、
「guides」にサブディレクトリを作成
このディレクトリ配下にREADME.md(これが本文になります)作ってあとでビルドして
リンクさせます。
ですので、subdir1,とsubdir2を作ってみました。 その中にREADME.mdを作っておきます。もちろんMarkDownで書いておきます。
現在の状況
▾ jsducktest/
  ▾ guides/
    ▾ subdir1/
        icon-lg.png
        README.md
        sample.png
    ▾ subdir2/
        README.md
  ▸ release/
    .DS_Store
    index.html
ツリーを作ります。
これはTreePanel用のオブジェクトリテラルを書いてあげるんですね。 作業用ディレクトリ直下にguides.jsonファイルを作って、、、下記のような内容で記載。
(guides.json)
[
    {
        "title": "RootNode",
        "items": [{
                "name": "subdir1",
                "title": "サブディレクトリ1",
                "description": "解説1"
            },{
                "name": "subdir2",
                "title": "サブディレクトリ2",
                "description": "解説2"
        }]
    }
]

ビルドしてみる(オプションを別ファイルにしてみる)

早速ビルドです。今度はヘルプにのっている様に、他のパラメタも。 たくさんのオプションが増えてきたので別ファイルで記述してみます。 json形式でオプションを設定して–configで設定します。
(jsduck.jsonの内容:ファイル名は任意)
{
    "--title": "jsducktest!!!",
    "--guides": "guides.json",
    "--welcome": "index.html"
    "--footer": "footer!!!",
    "--head-html": "head",
    "--body-html": "body"
}
ビルドしてみる
jsduck -o release --config=jsduck.json 

できたらアクセス。

release配下のindex.htmlにブラウザからアクセスしてみます。
こんな感じになりました。



色々と試してみました。

(jsduck.jsonファイル名は任意)
{
    "--title": "jsducktest!!!",
    "--guides": "guides.json",
    "--videos":"videos.json",
    "--categories":"categories.json",
    "--welcome": "index.html",
    "--footer": "footer!!!",
    "--head-html":"head",
    "--body-html":"body",
    "--warnings": ["-link", "-no_doc"],
    "--":[
        "src/Hoge/AbstructSample.js",
        "src/sample.js"
    ]
}

jsduck -o release --config=jsduck.json
現在の状況
▾ jsducktest/
  ▾ categories/
    ▸ subdir1/
    ▸ subdir2/
  ▾ guides/
    ▸ subdir1/
    ▸ subdir2/
  ▾ release/
    ▸ extjs/
    ▸ guides/
    ▸ output/
    ▸ resources/
    ▸ source/
      app.js
      data.js
      eg-iframe.html
      favicon.ico
      index.html
  ▾ src/
    ▸ Hoge/
      sample.js
  ▾ videos/
    ▸ subdir1/
    ▸ subdir2/
    .DS_Store
    categories.json
    guides.json
    index.html
    jsduck.json
    videos.json

できたらアクセス。

release配下のindex.htmlにブラウザからアクセスしてみます。
こんな感じになりました。




Gitにのっけました。

release配下のindex.htmlで上の出来上がりが確認できるかと思います。

https://github.com/froggugugugu/jsducktest

ビデオを見てみる

紹介されているように、ビデオを見てみます。
http://vimeo.com/33465319
4分17秒あたりから上記のカスタムパラメタとリンクする各メニュータブの説明
6分27秒あたりからソース内のマークダウン記法について あーそうなんだ。コメントもマークダウンでかけばいいのね。で{@link}とかなわけだ。

インストールについて

16分56秒あたりからインストールです。

タイトルについて

18分42秒あたりからタイトルの変更。
19分あたりからコメントブロック入れようね。
20分あたりからソース内のパラメータに沿ったDocumentの吐き出され方 alternateClassNameとかsingletonとか。 設定するとアイコンが変わったり、とか、こう出るよ。的な。
21分でコメントブロックの記載について。 Ext.define直前のドキュメントコメントはクラス説明文にあたるみたい。

propertyについて

21分50秒付近でのプロパティ直前のドキュメントコメントについては
/**
 * @property {[型]} [名前] [説明]
 */

configオプションについて

22分40秒付近ではコンフィグオプションの説明
config:{
    /**
     * @cfg
     * [説明]
     */
    hogehoge: 0.5
}
だと、0.5を判断して型を表示して、ConfigOptionカテゴリに分類して出力してくれるみたい。

functionについて

23分37秒付近ではfunctionの説明 functionの直前のコメントブロックに
/**
 * [説明]
 * @return {[型]} 説明
 */
とすることでMethodカテゴリに分類して出力してくれる。

functionについて(戻り値Object)

25分07秒付近ではfunctionの説明(Object戻り)
/**
 * [説明]
 * @return {Object}
 * @return {[型]} return.[Objectのメンバ名] 説明
 * @return {[型]} return.[Objectのメンバ名] 説明
 * ...
 */
とすることで戻りがObjectの際の出力をしてくれる。

functionについて(alias)

26分30秒付近ではfunctionの説明(alias)
/**
 * @alias クラス名#メソッド名
 */
で、内容が同一になるし、
/**
 * @alias [クラス名#メソッド名]
 * @duprecated [説明] {@link}
 */
で、重複の警告が出るみたい。 {@link [クラス名#メソッド名]}で本文内リンク。

eventについて

29分20秒付近ではeventの説明
/**
 * @event [イベント名]
 * [説明]
 * @param {[型]} [説明]
 */

/**
 * @event [イベント名] [説明]
 */
でイベントの説明を

privateクラスについて

31分00秒付近ではprivateクラスの説明 Ext.define直前のドキュメントコメントで
/**
 * @private
 */
とするとprivate クラスになります。

exampleコードの機能について

31分36秒付近では例の説明 Ext.define直前のドキュメントコメントで
/**
 * @example
 *     [code....]
 */
と,するわけですが この@exampleがなくても4space入れてるのでmarkdown形式で
コードブロックとなるわけですが、プレビュー機能等をつけるときは
この@exampleをつけます。
この際、eg-iframe.htmlを利用するので
配下にあるeg-iframe.htmlに当該jsファイルを読み込む設定を
しておきます。
ビルドするときはパラメタに–eg-iframe=[eg-iframe.htmlのパス]を設定します。

imageについて

34分00秒付近からイメージの説明 Ext.define直前のドキュメントコメントで {@img [ファイル名]} としておきます。 この際、ビルドするときはパラメタに–images=[画像が入ったフォルダパス]を設定します。

categoriesの設定について

36分00秒付近からイメージの説明 作業用ディレクトリ直下にcategories.jsonファイルを作って、、、下記のような内容で記載。
[
    {
        "name": "Jsducktest.js",
        "groups": [
            {
                "name": "subdir1",
                "classes": [
                    "class1_1",
                    "class1_2"
                ]
            },
            {
                "name": "subdir2",
                "classes": [
                    "class2_1",
                    "class2_2"
                ]
            }
        ]
    }
]
groupsで括って、 classesで括ってクラスの羅列。 この際、ビルドするときはパラメタに–categories=categories.jsonを設定します。

guidesの設定について

39分20秒付近からguidesの説明 作業用ディレクトリ直下にguides.jsonファイルを作って、、、下記のような内容で記載。
[
    {
        "title": "RootNode",
        "items": [
            {
                "name": "subdir1",
                "title": "サブディレクトリ1",
                "description": "解説1"
            },
            {
                "name": "subdir2",
                "title": "サブディレクトリ2",
                "description": "解説2"
            }
        ]
    }
]
itemsで括って、 nameにはguides配下のサブディレクトリ名と合わせる。 で、imageを変えたい場合はREADME.mdの同じ階層にicon-lg.pngを配置。 このあたり見ると良いかも。
https://github.com/senchalabs/jsduck/blob/master/lib/jsduck/guides.rb
この際、ビルドするときはパラメタに–guides=guides.jsonを設定します。

welcomeの設定について

42分22秒付近からwelcomeページの設定について 作業用ディレクトリ直下にwelcome.htmlファイルを作って、、、
この際、ビルドするときはパラメタに--welcome=welcome.htmlを設定します。

videosの設定について

43分18秒付近からvideosの設定について 作業用ディレクトリ直下にvideos.jsonファイルを作って、、、下記のような内容で記載。
[
    {
        "title": "RootNode",
        "items": [
            {
                "id": "9071202",
                "title": "サブディレクトリ1",
                "description": "解説1",
                "thumb": "http://b.vimeocdn.com/ts/439/819/439819_200.jpg"
            }
        ]
    }
]
vimeoのIDを入れるようです。。 この際、ビルドするときはパラメタに–videos=videos.jsonを設定します。

examlesの設定について

44分40秒付近からexamplesの設定について 作業用ディレクトリ直下にexamples.jsonファイルを作って、、、下記のような内容で記載。
[
    {
        "title": "RootNode",
        "items": [
            {
                "text": "例",
                "url": "[index.html]",
                "icon": "[icon]",
                "desc": "解説1"
            }
        ]
    }
]
この際、ビルドするときはパラメタに–examples=examples.jsonと設定します。

なんだよ、、まぁ、ここまで見てアレですが、

47分20秒付近から
https://github.com/senchalabs/jsduck/wiki/Guide
https://github.com/senchalabs/jsduck/wiki/Advanced-Usage
に今までの解説がありました。。。。

パラメタの設定について

コマンドラインパラメタは --configで指定できます。前述でも挙げましたが、こいつは便利ですね。

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

まずい。。 一年間、一回も更新してない!!! つまり、、 趣味の時間がほぼ皆無ということの現れです。 業務と子育てに追われた一年でしたね。。。。。 危なく年をこすところだった。あぶない。なぶあい。 話を変えて。 今月初旬に 社内でちょっとしたプレゼンをする事に...