2013年5月29日水曜日

[eclipse]Subversionでのマージの方法

SVNマージの仕方

この手順でばっちりです。

2013年5月24日金曜日

[CakePHP]ブラウザを閉じでもログイン状態を保持する方法

このぺージを参考にさせて頂く。

自動ログイン2

ただし、自分のサイトの場合はもっとシンプルにした。

・passportsテーブルは作らず、Usersテーブルにpassportカラムを追加
 →テーブル作る必要ない。

・有効期限カラムは必要ないと思う。
 →Cookie自体に有効期限があるから、わざわざDBの値とチェックする必要ない。

・__passportDeleteや__passportWriteのメソッドは作らない。
 →Auth->login()やAuth->logout()の直後に書けばよいだけ。

・cookieのチェックはapp_controllerのbeforeFilterにて行う。
 →どのぺージにランディングされてもチェックしてくれるから。


UsersControllerのlogin()アクションあたり。当然この前に、$this->expire(cookieの有効期限)の設定やpassportカラムへSecurity::generateAuthKey()などを使った値の保存はしておく。
ちなみに、上記のサイトだと、「$this->Cookie->write('User', $cookie, true,"+ ".$this->expires);  」となっていたが、"+"があるとうまく動作しなかったのではずした。Cakeのバージョン違いだろうか。

$this->Auth->login($user);
if($this->Auth->_loggedIn){
    $pass = array('Passport'=>$this->Auth->user('passport'));
    $this->Cookie->write('pass', $pass, true,$this->expires);
}


以下のように、もしログインされていなかったら→Cookieにパスポートがあるか確認。
というのをどのぺージでもするように、app_controllerのbeforeFilterで以下をやる。

if(!$this->Auth->_loggedIn){
    $cookiePassport=$this->Cookie->Read('pass');
    if(!empty($cookiePassport)){
        $cookieUser=$this->User->find('first',array('conditions'=>array(
            'User.passport'=>$cookiePassport['Passport']
        )));
        if(!empty($cookieUser)){
            $this->Auth->login($cookieUser);
        }
    }
}

[CakePHP]'Security.cipherSeedには数字を設定しなきゃだめ。

$this->Cookie->write()メソッドを使用した際に以下のエラーが画面に表示された。
Security.cipherSeedにアルファベットが入っていたため。

srand() expects parameter 1 to be long

2013年5月16日木曜日

[AWS]RDSのmult-AZは無料使用枠に収まらない

ということなので、無料で使いたい人は、multi-AZは使用しないようにしましょう。

750 時間の Amazon RDS Single-AZ マイクロ DB インスタンスの使用(MySQL、Oracle BYOL または SQL Server(SQL Server Express Edition を実行中)の実行用) - DB インスタンスを毎月継続的に実行するのに十分な時間

http://aws.amazon.com/jp/free/

また、AWSを使用する人は、価格が従量制であるが故、こまめにAccount Activityで値段を確認したほうがいいです。

2013年5月15日水曜日

[AWS]Route53とoutlook.comで独自ドメインメールを使う

outlook.comでは、google Apps等とは違い、独自ドメインでのメール利用を無料で提供できるので、使用した。
なお、同ドメインのWEBサーバはAWS上に配置しているので、DNSはRoute53を使用することにした。

※事前に独自ドメインを取得して下さい。


基本的には以下サイトに沿って進める。

1.【Google Apps(無償版)難民救済企画】Outlook.comで独自ドメインメールを無料運用してみたよ(Gmailへの転送もOK)

ただし、ムームーDNSの設定は行わずに、Route53で設定を行う。
以下サイトの<メールの設定>のみ行う。他の作業はしなくてよい。
2.Amazon Route53をWindows Live独自ドメイン用のDNSサーバーとして利用する ~基本設定~

この設定が終わったら、1のサイトの「ムームーDNS」設定後の作業から行えば、メールの送信が行えるようになる。

2013年5月8日水曜日

[AWS]CakePHP+Elastic Beanstalk+Route53+ムームードメインで独自ドメインを使用する方法

ムームードメインのDNSサーバを使用することもでるが、AWS内のサービスで完結できるものはしておいたほうが、よさそうなので、Route53を使うことにした。
あと、Route53だとwwwなしの短いURLアクセスできるようにできるとのこと。(ここここ参照)

1.事前作業

・ムームードメインでの独自ドメインの取得
・Elasctic Beanstalkでの環境構築

2.Route53での設定

参考URL1と2に書いてありますが、以下の流れ。

①Hosted Zoneの作成(Create Hosted Zoneボタンから)

②Record Setの作成(以下の2レコードを作成)
  ・www無し(Nameに何も入れない)
  ・www有り(Nameに'www'と入力)
※Alias Targetは対象環境のELBのA Recordを指定

③4つのネームサーバーを控えておく。
  Record SetsのType列が'NS'である行の'Value列'に入った4つのサーバ名

これでRoute53側の作業は終わり

3.ムームードメインでの設定(レジストラのネームサーバー情報を更新する)

①コンパネ→ドメイン操作→ネームサーバ設定変更というように遷移する。

②「取得したドメインで使用する」を選択し、Route53で控えた4つのネームサーバを入力し保存する。

 ※注意点としてRoute53でコピーした段階ではネームサーバ名の最後にドットが付くが、本手順で   入力する際にはそのドットを削除すること。そうじゃないと、エラーとなって保存されない。

   ex aaaa.awsdns-99.net. →  aaa.awsdns-99.net

③ 15分程度待つと、あなたの独自ドメインにwww有りと無しの両方でアクセスできるようになる。

 4.CakePHPの設定

ここからは参考URL5,6,7参照のこと。
 この段階では、www有りと無しの両方のURLでアクセスすることができる。しかし、SEO的にはどちらかに統一したほうがよいとされている。(こことかで

個人的には短いほうがすきなので、www無しに統一する。

ということで、CakePHP内の.htaccessをいじる。

CakePHPのディレクトリ内には.htaccessがたくさんあるが、どれをいじればよいのかは、どうやら環境によって違うようだ。

参考URL7によると、DocumentRootが設定されている値に依存するようだ。

自分の場合は、[cakephpが入るフォルダ]/app/webroot/直下の.htaccessをいじったら、挙動が変化したので、これをいじっていった。 [cakephpが入るフォルダ]直下の.htaccessをいくらいじっても挙動は変わらなかった。
以下が変更後の状態。(当然mydomainやmydomain.jpはあなたのものに置き換えてね)
3~4行目は、URLにwwwがある場合は、 無しにする部分。
5~6行目は、 ElasticBeansTalkにデフォルトで付与されるURLにアクセスされた場合も、独自ドメインのURLに変更するようにした。
この設定の良いところは、本番環境以外では動作しないところ。つまり、本番環境と開発環境で同じソースを使用できる。開発環境では、www有りとか無しとか考えなくていいからね。

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
    RewriteRule ^(.*)$  http://%1/$1 [R=301,L]
    RewriteCond %{HTTP_HOST} ^mydomain\.elasticbeanstalk\.com$ [NC]
    RewriteRule ^(.*)$  http://mydomain.jp/$1 [R=301,L]
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule> 

<豆知識>
'%1'はどうやら直近のRewriteCondで正規表現パターン(丸括弧でかこまれた部分)にヒットしたものが入るっぽい。
'$1'はおそらくRewriteRuleの一つ目の正規表現パターンにマッチした部分がはいるっぽい。'$2'は二目の正規表現パターンにマッチしたもの。
Apacheって奥深そう!

[参考URL]
1.Using Custom Domains with AWS Elastic Beanstalk
2.How to Create Alias Resource Record Sets
3.営業でも簡単!Route 53の基本設定
4.Elastic Beanstalkで立ち上げたアプリにRoute53経由で独自ドメイン(サブドメイン無し)を割り振る
5.How to make a 301 redirect for incomming requests from non-www to www in CakePHP
6.Generic non-www to www (and vice versa) 301 redirect using .htaccess
7.Redirect www to non-www

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で管理画面を表示できるようになる。



2013年3月26日火曜日

[Twitter Bootstrap]typeaheadでの日本語入力問題への対策

[Twitter Bootstrap]typeaheadの項目をajaxで取得するコード例

上記の記事にて、日本語入力に問題ないと書きましたが嘘でした。

現状、問題を二つ発見しました。一応対処できたのでシェアします。

1.日本語を入力し、文字変換確定のEnterを押すと、その間に表示されていたtypeahedの候補を選択したことになってしまう。(Safari5.1.7で発生。chrome25.0.1364.172 mは問題なかった。)


【問題内容】

 A.キーボード入力開始
     ↓
 B.一つのキーを押すごとに候補が表示されていく。一番上の候補が選択状態となる。
     ↓
  C.文字変換確定のためにEnterキーを押す。
     ↓
 D.Bで表示されていた候補の一番上のものが選択され、テキストボックスに入ってしまう。

例として私の開発環境での事例を紹介する。
「ファ」と入力した段階(まだ候補確定のEnterキーは押していない)では、typeaheadの候補としては、「ファッション」、「メンズファッション」が表示され、一番上の「ファッション」が選択れた状態だった。
そこで、「ファ」を確定させるためEnterキーを押すと、「ファッション」とテキストボックスに入ってしまうのだ。「ファ」と入力したいだけなのに。

【対処方法】

1.Bで表示される候補のうちどれも選択されないようにする。

  このサイトを参考にさせていただいた。
 Bootstrap Typeaheadの日本語入力対応
 このサイトに書いてあるbootstrap.jsのある部分をコメントアウトする。
 ただし、私の場合、これだけだと足りなかった。
 確かに、Bで一つも選択状態になっていない候補が表示されるのがだ、CでEnterを押すと、テキストボックスが空になってしまう。おそらく、0文字の候補を選択してEnterを押したように解釈されてしまうのだろう。そこで以下の対処も行った。

2.オプションのupdaterを選択した候補が0文字だったらqueryを返すようなロジックにする。

下記の10~16行目を追加した。 これで、Enterを押しても空欄とならず、入力しようとしていた文字列が入ってくるようになった。
※7~9行目のsorterが気になる方は「[Twitter Bootstrap]typeaheadがせっかくサーバ側でソートした配列をソートし直しちゃう件への対処 」をご覧下さい。

$("#tagSelectInput").typeahead({
    source: function(query, process) {
        $.post($("#tagSelectInput").attr("data-url"), { 'data[Tag][content]': query}, function(data) {
            process(JSON.parse(data));
        });
    },
    sorter:function(items){
        return items;
    },
    updater:function(item){//選択してない状態でenter押すと、空欄になってしまうので。
        if(item.length==0){
            return this.query;
        }else{
            return item;
        }
    }
});

2.日本語入力にて文字列入力中も変換確定のEnter押下時もtypeahedの候補が表示されない。(FireFox19.0.2で発生。safari、chromeはOK)


【対処方法】

bootstrap.jsのEnterを押した時のイベントの部分に、明示的に候補を探してくるコマンドを入れた。(1950行目あたり。)
以下でいうと12~15行目あたりを修正した。もともとここに、this.lookup()はなかった。FireFoxのためにここにこれを入れてしまうと、正常に動いているブラウザでは一回余計に検索をかけにサーバ側にアクセスしてしまうかなと心配したが、なぜかアクセス回数は増えずに済んだ。 もし増えたら、ブラウザの種類を判定してFireFoxだけ行うようにしようと思っていたが。

 , keyup: function (e) {
    switch(e.keyCode) {
      case 40: // down arrow
      case 38: // up arrow
      case 16: // shift
      case 17: // ctrl
      case 18: // alt
        break

      case 9: // tab
      case 13: // enter
        if (!this.shown){
            this.lookup();
            return;
        }
        this.select()
        break

      case 27: // escape
        if (!this.shown) return
        this.hide()
        break

      default:
        this.lookup()
    }

    e.stopPropagation()
    e.preventDefault()
}
上記の対処で違和感なく動くようになっています。ついでにIE8でみたけど問題なかったです。
ちなみにbootstrapはbootstrap-transition.js v2.2.2です。

以下と合わせて読めば、Bootstrapのtypeahedをajaxで日本語入力で使うのにお役に立つかと思います。

[Twitter Bootstrap]typeaheadの項目をajaxで取得するコード例
[Twitter Bootstrap]typeaheadがせっかくサーバ側でソートした配列をソートし直しちゃう件への対処 


【その他参考ページ】
Getting More From Twitter Bootstrap’s Typeahead Library
How to Use JSON Objects With Twitter Bootstrap Typeahead

2013年3月25日月曜日

[Twitter Bootstrap]typeaheadがせっかくサーバ側でソートした配列をソートし直しちゃう件への対処

[Twitter Bootstrap]typeaheadの項目をajaxで取得するコード例

上記の記事で紹介したajaxでの候補の取得方法だが、このままだと自分の場合ちょっとした問題がある。
サーバ側でどのように候補を検索するかというと、たいていSQLのセレクト文で抽出するはず。その際、order by句を使って、順序付けを普通はすると思う。

このようにせっかく順序付けをしてもブラウザ側でtypeaheadがご丁寧にソートしなおしてしまうのだ。

例えば、「ファ」と入力した段階だと、typeahedがサーバ側に「ファ」を送信。
サーバ側で、SQLで検索。select文にorder by句をつけて。
それで、「メンズファッション」「ファッション」という順番でjson形式のデータを返す。
これは、サーバ側のSQLのorder byでついた順序だ。

しかし、ブラウザ側でそのjsonをうけとったら、ご丁寧にもソートしなおしてくれて、「ファッション」「メンズファッション」という順番にしてしまう。
これは、マッチした部分が文字列の頭のほうにあるものが上に来るロジックらしい。

これを抑止するために以下のようにsorterオプションを、単純に受け取った配列をそのまま返すようなロジックで上書いたら、サーバ側でつけた順番どおりに「メンズファッション」「ファッション」と表示されるようになった。


$("#tagSelectInput").typeahead({
   source: function(query, process) {
      $.post($("#tagSelectInput").attr("data-url"), { 'data[Tag][content]': query}, function(data) {
         process(JSON.parse(data));
      });
   },
   sorter:function(items){
      return items;
   }
});

[CakePHP]find()ってlist'だとcount(*)できないので'all'を使う。

$result=$this->Tag->ThemesTag->find('list',array(
   'fields'=>array(
      'count(*) AS count',
      'Tag.content'
   ),
   'conditions'=>array(
      'Tag.content like'=>"%".$this->data['Tag']['content']."%",
      'Theme.status'=>1
   ),
   'limit'=>10,
   'group'=>'ThemesTag.tag_id',
   'order'=>'count desc,ThemesTag.id asc',
   'recursive'=>2
));

こんな感じでlistを指定して、count(*)を使用すると、以下のようなSQLが発行されてしまう。
テーブル名.count(*)ってひどいな。。。

SELECT ThemesTag.count(*) AS count, `Tag`.`content` FROM `themes_tags` AS `ThemesTag` LEFT JOIN `themes` AS `Theme` ON (`ThemesTag`.`theme_id` = `Theme`.`id`) LEFT JOIN `tags` AS `Tag` ON (`ThemesTag`.`tag_id` = `Tag`.`id`)  WHERE `Tag`.`content` like &#039;%a%&#039; AND `Theme`.`status` = 1  GROUP BY `ThemesTag`.`tag_id`  ORDER BY `count` desc, `ThemesTag`.`id` asc  LIMIT 10
なので、仕方がないので、allを使用し、後でリストっぽく整形することで対処した。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

[Twitter Bootstrap]typeaheadの項目をajaxで取得するコード例

Twitter Bootstrapのtypeaheadだけど、ajaxでアイテムをサーバ側で検索して取得するのが普通だと思う。
だけど、本家のサイトだとajaxを使用したサンプルが全く無いので、かなり厳しい。

そこで探していたら、英語だけどコード例を載せている人がいました。
Getting More From Twitter Bootstrap’s Typeahead Library

自分は一番上に出ているサンプルを少し修正して実現しきました。
("q"は自分がサーバ側で指定した名前に変更。limitはサーバ側で行うので削除)

サーバ側では、検索した結果を連想ではない普通の配列に入れて、json形式で返してあげればOKです。

また、心配していた日本語での動作ですが、特に修正せずに問題ない挙動です。

思った以上に楽だった!

【追記2013/3/26】
日本語入力に関して色々と問題がみつかったので、以下の記事で対処しています。ご覧下さい。
[Twitter Bootstrap]typeaheadでの日本語入力問題への対策

2013年3月21日木曜日

[VMware]ロングモードに対応していないエラーが出たら

VMWareで「ソフトウェアの仮想化は、このプラットフォームでのロング モードには対応していません。ロング モードを無効にしています。ロング モードのサポートがないと、仮想マシンは 64 ビット コードを実行できません。詳細については、http://vmware.com/info?id=152 を参照してください。」のようなエラーが出たら、BIOSを開いて、「Intel Virtualization Technology」という項目をenabledにし、一度電源を落として起動すると、エラーが出なくなりました。(再起動だけだと反映されませんでした。)

[CakePHP]Windows環境だとbaseurl直後に名前つきパラメターが使用できない

以下のようにベースURLの直後に名前つきパラメターをいれてアクセスすると、403 forbiddenが出ちゃってたんだけど、どうやらWindows環境だけ発生するようです。

http://baseurl.com/page:2

ここを参照(英語)
CakePHP routes named parameters in base path (/)

http://baseurl.com/C:aa.exe
まぁ、みたいなアクセスを防止するためっぽいっすね。

Windows版Apacheの仕様っぽいので、どうすることもできなさそう。
VMwareでCentOS環境でも作るか。

2013年3月19日火曜日

[jQuery]resizeイベントが2回発動される問題への対策

$(window).resize(function)なんですが、なぜかchromeだと1度のリサイズでイベントが二回発動されていしまいます。firefoxだとおきませんでした。

ここにのっている対処が一番シンプルそうです。実際うまくいきました。

JQuery: How to call RESIZE event only once it's FINISHED resizing?

コードはここにあるのを、発動される関数を自分のもに変えればいいと思います。

http://jsfiddle.net/Zevan/c9UE5/5/

2013年3月12日火曜日

[HTML5]自由にカスタム属性を定義する。

data-~~の形にすれば、自分で自由な属性を定義していいとのこと。
これは便利だね。

HTML5では、data-* の書式でカスタム属性 ( Custom Data Attribute )を定義できるらしい

使い道としては、jsによる入力のチェックとかで、何か制限数をブラウザ側に伝えたい時とかかな。

2013年3月10日日曜日

[CakePHP]テスト周りの勉強会@Co-Edo

Co-Edoさんにて3/9(土)に行われた「(CakePHPとか)PHPのテストについての勉強会」に参加してきました。

やはり、勉強会に参加すると一人で勉強していると気付けないことに色々気づけて有意義ですね。
なんでもCo-Edoさんを運営される方々にはCakePHPユーザが多いとのことなので、Co-edoさんに行くときは少し嬉しくなります。

勉強会はプレゼン形式でした。下記の「CakePHP2実践入門」(祝増刷!!)の著者陣を始め、Cakeに精通したそうそうたる方々のプレゼンは非常に勉強になりました。




以下がプログラムです。
・テストの基本 & CakePHP2でTDD/BDD @sizuhiko さん 40min
・「8時間耐久PHPUnitの教室」から「PHPUnitの基本」清水 紘己 さん ※ Thanks, @yando さん 40min
・CakePHP(+BDD Plugin)+Jenkins @kaz_29 さん 25min
・xDebugについて 小林 慧一 さん 25min

Twitterで#coedoで検索すると、3/9あたりのタイムラインにプレゼン資料が色々出てきます。

seleniumもPHPUnitもBDDもJenkinsも知らなかったので、勉強になりました。
xdebugは使ってはいたけど、サーバ上のソースのデバッグができたり、変数を書き換えて実行できたり等知りませんでした。さっそく試してみたいと思います。

PHPunitやseleniumの導入はもうちょっと余裕が出てきたらやりたいですね!
でも、PHPUnitってCakePHP2.0から対応なんだ。1.3だとだめなのかな。。。

2013年3月5日火曜日

[アフィリエイト]Amazon Product Advertising API のデータ構造がアイテムによって微妙に違う件

Amazonの Product Advertising APIを使って商品情報を取得し、商品画像のURLを取得しようとしているんだけど、取得したXML内のアイテムによってデータの構造が微妙に違うのでメモ。赤い方は、ImageSets要素内に入らないと画像URLが無い。青い方は、Item要素直下にImageの要素がある。前者は珍しいパターン。


2013年2月28日木曜日

[JavaScript]JSのlive関数のように動的に取得した要素にstyleを反映させるには

AJAXなどで動的に取得したHTML要素にstyleを反映させるにはどうするか。
最初からcssファイルに記述しておけばいいんだけど、値自体を動的に決めたい場合はどうするか。例えば、windowサイズに合わせて何かのサイズを決めたい時とか。

そんなときはhead内に動的にstyle要素を追加すればよいということがわかった。。
このサイトが参考になります。

jQueryのlive関数のように、後から追加されたエレメントに対して事前にスタイルを設定する方法

2013年2月12日火曜日

[Bootstrap]modal時に背景のスクロールを止める方法

https://github.com/jschr/bootstrap-modal/

このプラグインを導入すればいけますね。
ajaxとかも楽にできそうな感じなので今後使っていきたいと思います。

[Bootstrap]簡単にmodalにajaxでコンテンツを取得するスクリプト

http://twitter.github.com/bootstrap/javascript.html#modals 

ここで紹介されている方法だと、html内にdiv要素で枠組み準備しておかなかったりと、面倒くさいので以下のスクリプトを使うことにした。非常に簡単。

Dynamic (AJAX) loaded Bootstrap Modal (Bootstrap 2.1)




2013年2月11日月曜日

CakePHPでTwitter Bootstrapのpaginationを楽に表示するコード

pagination element for CakePHP on twitter bootstrap

このコードでいけました。超絶便利。
ただ、誰かのコメントにあるようソースの2行目の$modules$modulusに修正してね。

・使い方はこのファイルをelementsディレクトリに格納。
・view側で表示したい部分で以下のように呼び出す。
 ※カレントモデルと違うモデルでpaginationを行う場合は以下のようにモデル名を第二引数で指定してあげる。

<?php echo $this->element('pagination',array('model'=>'Theme')); ?> 

ちなみに、elementへ変数を渡せることを本件で知りました。
マニュアルには以下で載っています。

http://book.cakephp.org/1.3/ja/The-Manual/Developing-with-CakePHP/Views.html#id4

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

2013年2月9日土曜日

[JavaScript]画面ロード時にレイアウトがガタッっと一瞬崩れないようにするため

レイアウトに影響を与えるJavaScriptが外部ファイル化されていると、読み込み完了まで時間がかかるので、一瞬ガタっとレイアウトが崩れるように見えることがある。
今回の場合、それは、例えば以下のようなonload時に実行されるレイアウトに影響を与えるコマンドが、外部ファイルにあるためだった。

if($(window).width()>=980){
     $("#itemList").css("padding-top",$(".hero-unit").height()+50+"px");//hero-unitの大きさに合わせてpadding-topを変更
 }
なので、こちらをHTMLファイル内のbodyの閉じタグ直前の方に移した。
すると、ほとんど気にならないほど画面表示が速くなった。

2013年2月7日木曜日

[jQuery]flatheightsとheightLine.jsの使い勝手比較

ブロックレベル要素の高さをそろえてくれるjsライブラリについて使い勝手を比較する。

・jquery.flatheights.js
【メリット】
 対象の要素にクラス名を付与せずに使用できる。

【デメリット】
 呼び出すjsを書く必要がある。
 要素にpaddingやmarginが指定してある場合、高さがおかしくなる。
  http://d.hatena.ne.jp/AyeBee_TY/20120710/1341926286

heightLine.js
【メリット】
 要素にpaddingやmarginが指定してあっても正しく高さを調整してくれる。
 呼び出すスクリプトを書く必要がない。あるクラスを指定するだけで動作する。
http://blog.webcreativepark.net/2007/07/26-010338.html

【デメリット】
 クラス名をつけてあげなければ動かない


結局paddingとかで高さがおかしくなるのが嫌なので後者を使うことにした。


 
 

2013年2月1日金曜日

[CakePHP]どのviewでも共通して使うjsやcssをdefault.ctpで指定する

どのviewからも呼び出すjsファイルやcssファイルはdefault.ctp等のlayout側で指定してしまった方がコードの量が減らせます。

その指定の仕方。

default.ctp
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title><?php echo $title_for_layout?></title>
<?php
    echo $html->css(array(
        '/bootstrap/css/bootstrap.min',
        '/bootstrap/css/bootstrap-responsive.min',
        'style'
        ),
        'stylesheet',
        array('inline'=>true)
    );
    echo $html->script(array(
        'jquery'
        ),
        array('inline'=>true)
    );
    echo $scripts_for_layout;
?>
</head> 
 
通常、view側で$html->css()や$html->script()を使用して指定したものが、レイアウト側の$scripts_for_layoutで出力されます。ですが、ここでは、どのviewからも呼び出すものは7~19行目のようにdefault.ctp側で指定しています。注意点は二つ。

・echoなどの出力系コマンドで出力する。(7,15行目)


 →view側で指定する場合は、 "echo $scripts_for_layout;"で出力されるのでecho等は必要なかった。

・inlineをtrueとする。(13、18行目)


 →ここで指定したものは$scripts_for_layoutではかせるのではなく、「その場」で吐かせるため。
   (inlineをfalseにするのは$scripts_for_layoutの中に入れ込むということ。ただし、layout側でfalseを指定した場合、$scripts_for_layoutに入ってこない。)

あと、これは作り方によりますが、基本的には上のコードのように"echo $scripts_for_layout;"の直前で出力させることになるかと思います。ベーシックなjs、cssなので、先に読み込ませるべきものが多いと思います。

[bootstrap]responsive時のnavbarのサブメニューが展開されない

Twitter Bootstrapにてsubmenuを表示するために下記の部分をクリックしてもなぜかサブメニューが表示されなかった。




このサンプルサイトを見たところ正常に動いていたので、ソースを見比べたところ下記の部分にcollapseというクラスが足りていなかった。

<div class="nav-collapse collapse">

下記のTwitterの説明ページの「Responsive navbar」のサンプルソースにもcollapseが書いてあるので、僕がどこかから変なソースを持ってきてしまったんだと思います。

http://twitter.github.com/bootstrap/components.html#navbar

[jQuery]CakePHPでjquery.infinitescroll.jsを使用した場合にスクロールを正常に終わらせるには

http://www.infinite-scroll.com/

上記で配布されているjquery.infinitescroll.jsであるが、スクロールが終わる条件は、私の知る限る二つある。

1.オプションのnextSelectorで指定されたリンクで指定された、次ページへのリクエストに対し404エラーが返される。
 →こちらの場合、オプションのfinishedMsgで指定された文言が最後に表示される。

2.オプションのmaxPageで最終ページを指定する。
→こちらの場合、finishedMsgはなぜか表示されない。

2の方法は、総ページ数をmaxPageに入れてしまえばいいので楽チンなのだが、やはり終わった時のメッセージは欲しい。
1の方法に関して。
当方、サーバサイドはCakePHPを使用している。CakePHPはデフォルトでは、最終ページを超えた数字が指定されても、最終ページの内容を出力し404エラーは出力しない。
なので、最終ページのnextSelectorで指定されたaタグに存在しないURLを指定したのだが、どうもうまくいかない。相変わらず最終ページの内容が出力される。
なぜかと思いfirebugで観察していたところ、どうやら、ベースになるURLは最初のページを表示したタイミングで作られ、incrementした数字をお尻に付加しているようだった。つまり、2ページ目以降のnextSelectorのaタグは見られていない。

なので、CakePHPで最終ページを超えた数字を指定された場合は404となるように細工することにした。その方法は以下で紹介している。

[CakePHP]pagination時に存在しないpageの場合は404を出すには

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip


[CakePHP]pagination時に存在しないpageの場合は404を出すには

CakePHP1.3でのpaginationでは、urlの最後に"page:2"などのようにpageを指定する。
(2ページ目を表示させたい場合)
実際に存在するpage数より大きな数字を指定した場合、どうなるかというと、最終ページの内容が表示されてしまう。

発行されたSQLを見たところ、select文の最後に"LIMIT 20 10"のように指定し、目的のページのデータを取得する仕様のようだ。"LIMIT 20 10"の場合は、21行目から10件取得する時に付与される場合だ。つまり、1ページにつき10件の表示で3ページ目を表示する場合だ。
最終ページが3ページなのに4ページ目を表示しようとURLに"page:4"と付与しリクエストを行い、発行されたSQLを見たところ、"LIMIT 20 10"のままだった。これが原因のようだ。
なぜこのようになってしまうかはわからない。

最終ページより大きな数字を指定した場合でも、最終ページの内容を表示するのではなく、404などのエラーを出力させたいこともあると思う。そんな時は以下のコードをcontroller側に記述する。

if(!empty($this->params['named']['page'])){
    if($this->params['paging']['Theme']['pageCount']<$this->params['named']['page']){
        $this->cakeError('error404');
    }
}

$this->params['paging']['Theme']['pageCount']はページの総数、$this->params['named']['page']はリクエストがあったページ、が格納されている。後者は、ページ指定無しの場合(1ページ目を表示)は格納されないので、!emptyで確認している。

※CakePHPのバージョンはcakephp-cakephp-1.3.15-9-gacd25c3.zip

【追記2013/2/10】
上記の'Theme'は$this->paginate()で指定したモデル名を指定する。
ちなみに、どうやら上記のコードは$this->paginate()コマンドよりも後にやらないと正常に動かない。おそらく、paramsに入るこれらの値はpaginate()コマンドにより格納されるもののため。たぶん。

2013年1月30日水曜日

[CakePHP]$html->cssや$html->scriptでデフォルトのディレクトリにないファイルを参照させたい時

スラッシュ有りでwebrootからのパスを書けば良いということですね。

例えば以下のような書き方。

$this->Html->css('/bootstrap/css/bootstrap.min','stylesheet',array('inline'=>false));