2013年4月17日水曜日

[jQuery]名前空間とoneで動的にイベント処理設定を変更する。

jQueryでイベント処理の設定(bindやliveとかで)をしていて、ユーザの操作によってだとか、ajaxの受信結果の内容によって、動的にイベント処理の設定を変更できたらいいのに、と思うことがあります。

例えば、モーダルウィンドウでユーザが選択した結果を受けて、次にでるモーダルウィンドウのイベント設定内容を変えたりなど。
そんなときは、名前空間とone関数を使って動的にイベント設定内容を変えていきます。

名前空間とはNamespaced Eventsで説明されているよう、'click'や'hover'等のイベント名の後ろにクラス名のようにドットで名前がつけられる機能です。
これを設定していれば、その名前だけunbindすることができます。(clickイベントを全て削除しなくて済む)

one関数とは、ほぼbindと同じなのですが、そのイベントは一度呼び出されれば終わりで、二回以上発動することはありません。こちらで説明されています。one(type, [data], fn)

以下の例では、1行目でhidden.twtrmdlという名前空間にoneでイベント処理設定をしていますが、ajaxの結果を受けて12行目でその名前空間がunbindされています。つまり、ajaxの結果によって、イベントの処理設定が変わっていることになります。


$("#commentModal").one('hidden.twtrmdl', function () {
    $("#twtrCnfrm").modal();
});

$("#commentModal btn").bind('click',function(){
    $.ajax({
        success:function (data, textStatus) {
                if (data.success) {
                      $("#commentModal").modal('hide');
                }
                else {
                      $("#commentModal").unbind('hidden.twtrmdl');
                      alert(data.message);
                }
        },
        data:postData,
        dataType:"json",
        type:"post",
        url:url
    });
});

2013年4月16日火曜日

[JavaScript]Pinterestを参考にブックマークレットを作る

ここ数年流行しているPinterest等のキュレーションサイトは、簡単にコンテンツをクリップするために、ブックマークレットを提供していたりするんだけど、自分もPinterestのを参考に作ってみたので、発見したことを書きます。

※最近ではchrome拡張機能とかも提供しているみたいだね。

javascript:void((function(d){
    var e=d.createElement('script');
    e.setAttribute('type','text/javascript');
    e.setAttribute('charset','UTF-8');
    e.setAttribute('src','//assets.pinterest.com/js/pinmarklet.js?r='+Math.random()*99999999);
    d.body.appendChild(e)
})(document));

これが、pinterestのブックマークレットです。(見やすいように改行は入れました。)
ここで入手できます。

これを見ると、開いているサイトのHTMLに、無理矢理自サーバに配置したjavascriptを呼び出すscriptタグを挿入しています。

その呼び出したjavascriptは非常に長いので説明を省略しますが、クリップする画像を選ばせるようなプロンプトを出させたり等、自サイトのjavascriptを書くかのごとく書いています。

ただし、jquery等のライブラリをscriptタグ挿入で呼び出しても、変数、関数の共有ができないので使えない、とのこと。(どこかのサイトに書いてあったのだが、どこかわからなくなってしまった)
なので、生のjavascriptを書く必要があります。

CSSファイルはlinkタグ挿入により、反映することができます。なので、自分のスタイルを反映することができます。

また、このjavascriptから、window.open()等で自サイトのページを開く場合、ポップアップブロックが発生する、という問題がおこります。

この問題への対処はhtmlにonclick属性を使い、自サイトを開くということです。
このページを参照してください。ポップアップブロック回避策

なので、プロンプトはjavascriptのalertやconfirmを使うのではなく、htmlでモーダルっぽいものを作り、そのOKボタンのタグにonclick属性をつけることになります。

一連の流れは以下のようになります。(自分の場合)

1.ブックマークレット起動。→自サーバのjavascript(A.jsとする。)が呼び出される。
2.A.jsにて、自サーバのcssを呼び出すlinkタグを挿入。
3.A.jsにて、モーダルっぽいHTMLを挿入。自サーバを開くボタン(Bボタン)にはonclick属性でwindow.openを紐づけてある。
4.Bボタンを押すと、自サーバの画面が開く。そのリクエストにはそのサイトから抽出したい情報をパラメータとしてつけておく。

ここで一つ注意点、以下のポストでも書いたように、自サーバ側の設定によっては、違うドメインからの遷移により、セッションが切れるようにしている場合がある。(CakePHPではデフォルトで切る。)そうすると、ログインしていたのに、別画面で自サイトを開いたら、再度ログインしなければならなくなってしまう。なので、チェックをはずす設定をする必要があります。
[CakePHP]他のサイトを経由するとセッションが切れてしまう件への対処

[CakePHP]JavaScriptをコントローラーに吐かせる。

ブックマークレットから呼び出されるjavascriptって自サーバのURLをどうしても絶対パスで書かないといけないと思うんだけど、開発環境と本番環境で書き換えなきゃいけなかったりで、面倒臭いので、CakePHPのコントローラーからjavascriptを吐かせるようにした。

[CakePHP] スタイルシートをコントローラーで作ってみる

基本的には上記のサイトを参考にさせて頂いた。

CSSでもJSでもだいたい同じ。

ただ、header('Content-Type: text/css');ってところを当然header('Content-Type: text/javascript');にしなければいけません。

あと、データベースを使用しないモデルになるのでモデル内に

var $useTable = false;

も必要です。これが上記のサイトにのってないのはなぜかな。

2013年4月13日土曜日

[CakePHP]他のサイトを経由するとセッションが切れてしまう件への対処

自分の場合は、ブックマークレットを使用し、自分のドメイン以外のサイトから、自分のサイトの画面を開くようにしているときにこの問題が発生した。

ここで
外部サイトに接続して戻って来ると、セッション情報が失われている

説明されているよう、、session.referer_checkが有効となるため、発生するようだ。
なので、ここでで説明されているよう、
Sessions

core.phpで以下をいれ,

Configure::write('Session.save','my_session');

同ディレクトリにmy_session.phpを配置する。
中には、ある状況では、 session.referer_checkは無効になるようにした。 なので、if文などで、ある状況には以下が実行されるようにする。こんな感じで正規表現でマッチングしてもいいかもしれない。本当はアクセス先URLではなく、遷移元URLで判別できたほうがいいだろうけど。

if(preg_match("/^models\/action/",$_GET['url'])){
 ini_set('session.referer_check', '');
}

ちなみに、もし、ブックマークレットからアクセスする自サイトの画面が認証を必要とする画面の場合、CakePHPの機能で、$this->Auth->loginActionで指定したアクションに自動的にリダイレクトされる。 なので、このloginActionで指定したURLへのアクセス自にも上記のsession.referer_checkを無効となるように設定したほうがいい。 なぜなら、loginActionにリダイレクトさた時点で、session.referer_checkが発動しセッション情報が失われるので、Auth.redirectに保存されたURLがなくなってしまうからだ。

 Auth.redirectとは、認証が必要なページに未ログイン状態でアクセスすると、そのURLを保存してくれるものだ。CakePHPはログイン後にこの値を取り出しもともと開こうとしていたURLにリダイレクトしてくれるのだ。 このAuth.redirectが空だと、Cakeはwebrootにリダイレクトしてしまう。 loginActionへのsession.referer_checkを無効にしてあげれば、きちんと保持してくれる。

あとブックマークレットを作っている方は、ブックマークレットから呼び出しているjavascriptやcssをcakephpで動的に出力している場合は、これらのURLへのアクセスも許容しておく必要がある。これらのコントローラー、アクションが起動した段階で、session.referer_checkが発動してしまうからだ。通常、JSやCSSは静的なファイルでWebrootに配置しているので問題ないと思う(webrootの静的なファイルへのアクセスでは発動しない模様。)が、動的にjsやcss内のURLを書き換える等のトリッキーな使い方をしている人もいると思うので。

 ちなみに、Auth.redirectは$this->Auth->redirect()で取得できる。 なので、リダイレクトする際は以下のようにする。OAuthの場合は、コールバックURLになっているアクションで。
$this->redirect($this->Auth->redirect());
でもCakeのドキュメントには、「通常、 AuthComponent は認証が実行されるまえのコントローラ/アクションのペアを記憶しており、認証が成功したらユーザをそこにリダイレクトします」って書いてあって、まるで自動的にリダイレクトしてくれるように書いてるんですよねー。 自分はOAuthを使ってるから、自動的ではないのかなぁ。

2013年4月8日月曜日

[CakePHP]controller側でviewのメソッドを使う方法

以下のようにviewクラスからインスタンスを作って使います。
ctpファイルを作るほどでないアクションで、sql_dumpをログに残したい時等に便利です。

$view = new View($this, false);
CakeLog::write(LOG_NOTICE,$view->element('sql_dump'));
※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2013年4月5日金曜日

[jQuery]infinitescrollが正しくpage番号をincrementしてくれない件への対処

infinitescrollを使用している際、以下のような事象が発生した。

http://【mydomain】/themes/2/page:2がinfinitescrollのオプションnextSelectorに指定されている時、なぜか出力されないアイテムが表示されたり、アイテムがもっとたくさんあるはずなのに、「もうアイテムはありません」というようなメッセージが出てしまうのだ。 i

まず、サーバ側のaccessログをみると、 http://【mydomain】/themes/3/page:2というURLにアクセスしており、404エラーを返していた。なぜ、このようなURLにアクセスしてしまうのか。
infinitescrollのソースをchoromeのデバッグモードで見ていると、以下のことがわかった。
pathという2要素の配列があるのだが、 0番目に'http://【mydomain】/themes/'、1番目に'/page:2'という文字列が入っており、処理としては、このそれぞれの文字列の間にインクリメントされた数字を入れて結合しアクセス先のURLを作っているようだった。つまり、本来は 'http://【mydomain】/themes/2/page:【ページ番号】'と認識してほしいところ、'http://【mydomain】/themes/【ページ番号】/page:2'というように認識してしまったようだ。

infinitescrollのソースをよくみると、URLに/2/というようにスラッシュに直接はさまれた’2’があるとそれをページ番号として認識する作りになっていた。/suzuki2/や/2suzuki/など、他の文字列と一緒であれば、問題なかった。

これを防止するため、最初はinfinitescroll.jsに手を加えようと考えたが、pathはオプションで指定できるということがわかった。なので、以下のようにして指定した。
色々書いているが言いたいことは色が付いている行。

    path=new Array();
    path.push(location.href+'/page:');
    path.push("");
    $("#infiniteItems").infinitescroll({
            navSelector  : '#page-nav',    // selector for the paged navigation
            nextSelector : '#page-nav a',  // selector for the NEXT link (to page 2)
            itemSelector : '.rankedItem',     // selector for all items you'll retrieve
            loading: {
                finishedMsg: 'これ以上はないのじゃ',
                img: $("body").attr("data-ladingImg")
            },
            path:path
        },
        function(newElements){
            var $newElems = $( newElements ).css({ opacity: 0 });
                $newElems.each(function(i){
                    $itemInfo=$(this).children(".itemInfo");
                    $voters=$(this).children(".voters");
                    if($itemInfo.outerHeight()>$voters.outerHeight()){
                        mgnpdgSum=$voters.outerHeight()-$voters.innerHeight();
                        $voters.height($itemInfo.height()-20);
                    }
                });
            $newElems.css({ opacity: 1 });
        }
    );


[CakePHP]デフォルトのlayouto、styleのscaffoldを管理画面として使用する

簡単なWEBサービスの場合、管理画面を作るために、工数を割いていられない。
そんな時、デフォルトのscaffoldの画面はDBを管理するにはちょうどいいインターフェイスになっているので、管理画面として使用するにはうってつけだ。

Cake本家のドキュメントでも同じようなことをしているが、今回はこれを少し拡張した手順となっている。

 通常scaffoldを使用する場合、CSSスタイルシートは各controllerで指定している$this->layoutで指定しているレイアウトと、それが参照しているcssファイルが適用されることになる。
でも、Cakeのデフォルトのレイアウトとスタイルが見やすくて好きだし、考えなくてよいので楽だと思うのです。
また、管理画面にBASIC認証をかけたいとも思う。

そんなときどうするか。
CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zipだけど、他のバージョンも少し修正すればいけると思います。

1.デフォルトのレイアウト、default.ctpをコピーする。

/cake/libs/view/layouts/default.ctpをコピーして、
 /views/layouts/scaffold.ctpを作成します(ファイル名はなんでもよい) 。

2. core.phpに以下を追加。prefixとして'admin'を登録します。

Configure::write('Routing.prefixes', array('admin'));
prefixとはhttp://【mydomain】/【prefix】/【controller】/【action】のような位置で指定できるものです。
なので、これをすると、http://【mydomain】/admin/themes/indexみたいなURLでアクセスすることが可能になります。

3.app_controllerを以下のようにします。

class AppController extends Controller {
    var $scaffold = 'admin';//scaffoldのprefixに'admin'を指定
    var $components = array('Auth','Session','Security');//Basic認証のためSecurityコンポーネントを使う

    function beforeFilter(){
        $this->Security->validatePost = false;
        if(!empty($this->params['admin'])){
            if($this->params['admin']) {//ここで'admin'prefixつきのアクセスかを判別
                $this->layout = 'scaffold';//prefixが'admin'のアクセスの場合は、scaffold.ctpをlayoutとして使用する。 
                $this->paginate[$this->modelNames[0]]['limit']=20;//paginateのアイテム数はこうやって自由に変えられます。
                $this->Security->loginOptions = array(
                    'type'=>'basic',
                    'realm'=>'MyRealm'
                );
                $this->Security->loginUsers = array(
                    'ID'=>'PASSWORD'//IDとPASSWORDはお好きな文字列にする。
                );
                $this->Security->requireLogin();
            }
        }
    }
}

6行目の$this->Security->validatePost = false;をしているのは、Securityコンポーネントをロードした場合、formtagのチェックが有効になってしまうから。postのフォームをきちんとformタグが入るように生成している場合は問題ないが、自分はしていなかったので、falseにした。

4.route.phpに以下を追加

Router::connect('/admin',array('controller' => 'themes', 'action' => 'index','admin' => true));

これを追加しないと、http://【mydomain】/admin/themes/のようにアクセスしないといけないので。
http://【mydomain】/admin/のように短いurlで管理画面を表示できるようになる。