Code

2014年9月27日土曜日

モバイルでは viewport 設定

 先週、作ってるサイトがバグ見つかった。問題は non-responsive のページは一画面に全部表示されなかったことです。期待としては responsive ページはそのまま表示、non-responsive ページは縮小して、一画面に収まれるようになることです。
 調べてみたら、viewport の設定に問題があった。今のプロジェクトでは、responsive ページとnon-reesponsive ページは C# の属性を標識して、render engine で違う viewport を設定します。
 viewport と言えば、携帯のピクセルは実際ページのどのぐらい当てるかの設定です。例えば、viewport の width を 800 に設定したら、携帯のスクリーンは 800 pixel となります。もしサイトの横サイズは 800 だったら、ちょうど一画面に収まれます。1024 に設定したら、800 pixel サイズのサイトはちょっと小さく表示されます。
 一般的には responsive サイト、またはそのサイズのままで表示したい場合、 width を device-width を設定すれば良いです。これで、ディバイスの横幅をそのまま使われます。また、サイトを小さくしたい場合、init-scale を小さくすれば良いです。それに、minmum-scale, maximum-scale でサイトの拡大と縮小倍率はコントロールできます。user-scalable でユーザがサイトピンチで拡大できるかどうか設定できます(yes or no).

 今回の問題は width を 800 に設定したら、initial-scale も 1.0 に設定したからです。これで、width は正しく 800 になっても、サイトのイニシャルスケールは 1.0 つまり、サイトはどのぐらい大きいか、もうそのまま表示されます。つまり、width の設定が上書きされました。今後注意しましょう。

 ちなみに、古いバージョンの iPhone または Android のデフォルトブラウザーでは、width を指定した後、scale を指定すると、viewport が効かないようになりますので、ご注意ください。つまり、content="width=1024, user-scalable=no" は動きますが、content="width=1024, maximum-scalable=2" は動かないです。

 後、Android と iOS の古いバージョンでは、viewport を正しく解釈できないことが発見しました。多分 CSS の影響だと思いますが。。。
 最新の Chrome (Android) をダウンロードして、チェックしてよいです。iOS なら、できるだけ、iOS 7を使いましょう。iOS 6 の Safari はまだまだバグがあります。特に、transform に関して、正しく表示できない時があります。

 それでは。

2014年9月19日金曜日

リターンキーでフォーム Submit

 昨日、変なバグが見つかった:ある input text にフォーカスを押して、リターンキーをクリックしたら、画面が更新されました。。。何でだろうと思った。。。
 実際見てみると、input は form タグの中にあるので、フォームの action は "#" と書かれています。# だと、自分のアドレスに submit されますので。。。でも input は text type だし、submit タイプではないから、何で submit がトリッガーしただろう??
 調べてみたら、もし Form の中に input text が一つだけある場合、リターンキーを押すだけで、 form が submit されます。これはブラウザーの固有動作です。しかも IE 5 からあります。
 また、IE から変な動作を継承したな〜と思いました。
 直す方法としては form の action を削除するか、display : none の input text をもう一個追加するです。
 将来のブラウザーはこれを直さないだろう。メモしておこう。

 それでは。

2014年9月8日月曜日

Angularjs $timeout 調べ

 DI (Dependency Injection) は Javaや、ASP.NET などバックエンドには人気ですけど、AngularJS はそれを JavaScript に応用できるようにしました。具体的に言うと、モジュールをロードするときや、関数を呼び出す場合、$injector の invoke 関数で、呼び出す対象の引数を全部パースして、providerと instance キャッシュの中に探して行く、もし同じ名前の対象があったら、それを使います。
 さらに、Ajax call を実行するとき、$http と $httpBackend を利用することで、$browser も内部で持つことで、すべての操作をライブラリ内部で intercept できるようになります。
 この中に、面白いのは $timeout というものです。実際の timeout 関数は既にあるのに、何で $timeout を実装するのか?
 理由としては、二つあります。まずは Unit test を書くとき、自分で timeout をトリッガーできるようになります。実際のコードを見ると、promise を使っていますが、promise の中には、window.timeout 関数が使われています。これで timeout を同じ機能が実行できるようになります。さらに、もし外部で同じ promise を resolve したら、同じ関数が呼ばれますので、timeout 発生時と同じ動作になります。これはかなりいいことです。もう timeout を待つ必要がなくなります。
 もう一つは Angular Scope の $digest に関係あります。$timeout の中に $rootScope.apply() が呼ばれています。これを理解するには JavaScript 実行のサイクルを理解する必要があります。以前では setTimeout(function() {}, 0) を呼び出すことで、今までの JavaScript コールスタックをすべてクリアした後に、コードを実行する方法があります。$timeout も同じです。AngularJS に自分のサイクル、いわゆる $digest がありますの、この関数ですべての watcher を oldValue, newValue を計算して、listener をその後呼び出します。一つのサイクルを終わらないと、Angular は対象の変更が察知できないことがあります。例えば、ng-include でテンプレートの path を変更したい場合、関数の中に
    /* templateUrl の値は abc とします。*/
    templateUrl = "";
    /* その他の操作*/
    templateUrl = "abc"; // テンプレートをリロードしたい
をやると、$digest はこの関数終わった後に実行されますので、templateUrl は変更してないように見えます。つまり、変数の変更をAngularJS に察知させる為に、一つの $digest サイクルの終わりを待つ必要があります。このとき、$timeout は利用すれば、ちゃんと意図通りできます。
     templateUrl = "";
     /* 他の操作 */
     $timeout(function() {templateUrl = "abc"})
これで、最初の templateUrl がクリアされて、$digest の実行で、ng-include directive がクリアされます。その後、次の $digest サイクルで、templateUrl がまた元に戻したので、すべての expression がもう一度更新されます。
 もちろん、自分で setTimeout を実行して、scope.apply() を呼び出しても、同じ機能ができますの、そのショートカットとして、$timeout を使えば、簡単になります。
 
 まぁ、dirty check にはいろいろ不利なところもありますが、Knockout.js の set 関数を比べると、自分は Angular のほう好きです。
 最近、このライブラリも人気だそうです:
    durandal
   このライブラリは require.js を使っていますので、Angular でできない遅延ロードもできます。聞いたことですが、テンプレートのほうも Angular よりちょっとだけ優れています。
 まぁ、これらは Angular 2.0 で改善される予定なので、少し待ってもいいような気がしますけど。
 
 それでは。