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 '%a%' 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の要素がある。前者は珍しいパターン。