先週、作ってるサイトがバグ見つかった。問題は 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 に関して、正しく表示できない時があります。
それでは。
Code
2014年9月27日土曜日
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 をもう一個追加するです。
将来のブラウザーはこれを直さないだろう。メモしておこう。
それでは。
実際見てみると、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 で改善される予定なので、少し待ってもいいような気がしますけど。
それでは。
さらに、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 で改善される予定なので、少し待ってもいいような気がしますけど。
それでは。
2014年8月27日水曜日
Angular Scope 外の HTML を Angular Scope に入れる
最近、ある旧 Page を Angular を使っている新しい Page に入れたかったのですが、Angular の Bootstrap は HTML を入れる前に行われていて、Scope が使われてないことがありました。
まぁ、解決にはそんなに難しくないですが、解決のプロセスが面白いから、メモしておこう。
まず、angular.element を取得します。その後、injector を取得して、$compile を変数に保存します。
var compile = angular.element(ele).injector().get("$compile")
これで、$compileProvider で作成した $compile 関数が取得できました。
その後、HTML String を $compile に当てて、Scope を利用して、リンクした HTML を該当場所に入れます。
compile(angular.element(ele).scope())
最後に、Scope の $digest を呼び出すために、$apply() 関数を呼び出す。終わり。
Angular は自分の実行サイクルがあるので、その外で生成した HTML はそのサイクルに入らないので、これを入れるために $apply を利用します。ーーまたは $timeout を使います。(実際、$timeout の中に $rootScope.$apply() が呼び出されています。)
短くいうと、HTML String を Angular の世界に入れるため、$compile を使います。$compile 関数は Link 関数を返しますので、link はある Scope をベースに実際の Angular の世界に入る HTML String を返されます。その後、$apply で $digest を呼び出して、一回 Angular の世界をリフレッシュします。
愚痴:1.0.8 に ng-if がないので、作ろうかなと思ってますけど、、、否決されました。しばらく ng-switch を使う予定です。1.2 を使いたいな〜。
まぁ、解決にはそんなに難しくないですが、解決のプロセスが面白いから、メモしておこう。
まず、angular.element を取得します。その後、injector を取得して、$compile を変数に保存します。
var compile = angular.element(ele).injector().get("$compile")
これで、$compileProvider で作成した $compile 関数が取得できました。
その後、HTML String を $compile に当てて、Scope を利用して、リンクした HTML を該当場所に入れます。
compile(angular.element(ele).scope())
最後に、Scope の $digest を呼び出すために、$apply() 関数を呼び出す。終わり。
Angular は自分の実行サイクルがあるので、その外で生成した HTML はそのサイクルに入らないので、これを入れるために $apply を利用します。ーーまたは $timeout を使います。(実際、$timeout の中に $rootScope.$apply() が呼び出されています。)
短くいうと、HTML String を Angular の世界に入れるため、$compile を使います。$compile 関数は Link 関数を返しますので、link はある Scope をベースに実際の Angular の世界に入る HTML String を返されます。その後、$apply で $digest を呼び出して、一回 Angular の世界をリフレッシュします。
愚痴:1.0.8 に ng-if がないので、作ろうかなと思ってますけど、、、否決されました。しばらく ng-switch を使う予定です。1.2 を使いたいな〜。
2014年8月7日木曜日
iOS 7 Safari リンク 2回クリック問題
今日新しい問題を発見しました。以前はこのようなサイトを作ってなかったから、わからなかったが、iOS 7 から :hover クラスの中に display と visibility があれば、一回目の touch は hover 状態にして、2回目のタッチは実際のリンクに行きます。。。
調べてみたら、どうやら iOS 7 からいろいろ変わってきました。
ios-has-a-hover-problem
具体的に言うと、以前の Touch Device では、:hover クラスを無視しましたが、iOS 7 からは
div span {display:none}
div:hover span {display: block}
みたいな動作を見逃さないように、一回目のタッチを :hover にして、つまり span を表示させて、このとき、click イベントがトリッガーしません。2回目のタッチで、click になります。。。
これは困ったな〜
ネットでは :hover を no-touch クラスをつけて、タッチデバイスではなければ、no-touch を外すという直す方法がありますが、個人では、display:none の要素を opacity で隠したりしたほうがいいかもしれません。
まぁ、まだいろいろ試しているところ。。。また後日追記します。
それでは。
調べてみたら、どうやら iOS 7 からいろいろ変わってきました。
ios-has-a-hover-problem
具体的に言うと、以前の Touch Device では、:hover クラスを無視しましたが、iOS 7 からは
div span {display:none}
div:hover span {display: block}
みたいな動作を見逃さないように、一回目のタッチを :hover にして、つまり span を表示させて、このとき、click イベントがトリッガーしません。2回目のタッチで、click になります。。。
これは困ったな〜
ネットでは :hover を no-touch クラスをつけて、タッチデバイスではなければ、no-touch を外すという直す方法がありますが、個人では、display:none の要素を opacity で隠したりしたほうがいいかもしれません。
まぁ、まだいろいろ試しているところ。。。また後日追記します。
それでは。
2014年7月26日土曜日
cross-browser IE8 ボタン クリック問題
最近仕事が変わりましたので、IE8 も対応ブラウザになりました。。。
以前から、出て来た IE8 ボタンのクリック問題をまとめておきます。
1 まずは、radio ボタンをクリックしたら、何か動作を自動的に実行する場合、IE8 だと、クリックだけでは onchange イベントハンドラーが呼び出されない。これは、radio ボタンを focus を失うとき、onchange が trigger されます。。。まぁ、理解できますけど。。。
直し方としては、radio ボタンにもう一個の onclick イベントハンドラーをバインドします。そのイベントハンドラーに
$("radio button の selector").focus().blur()
を実行します。これで、onchange が自動的に呼ばれるようになります。
2 display:none のボタンの label をクリックしても、ボタンの状態が変わらない。。。
これは display:none だから。直す方法が二つあります。display:none をやめて、opacity を 0 に設定したり、position を absolute に設定して、top: -9999px, left:-9999px を設定したりすることで、ボタン要素が見えないが、label のクリックは効くようになります。
もう一つは Label に click ハンドラーをバインドして、もし label が click したら、for attribute で設定したボタンの click 関数を呼び出します。
それでは。
以前から、出て来た IE8 ボタンのクリック問題をまとめておきます。
1 まずは、radio ボタンをクリックしたら、何か動作を自動的に実行する場合、IE8 だと、クリックだけでは onchange イベントハンドラーが呼び出されない。これは、radio ボタンを focus を失うとき、onchange が trigger されます。。。まぁ、理解できますけど。。。
直し方としては、radio ボタンにもう一個の onclick イベントハンドラーをバインドします。そのイベントハンドラーに
$("radio button の selector").focus().blur()
を実行します。これで、onchange が自動的に呼ばれるようになります。
2 display:none のボタンの label をクリックしても、ボタンの状態が変わらない。。。
これは display:none だから。直す方法が二つあります。display:none をやめて、opacity を 0 に設定したり、position を absolute に設定して、top: -9999px, left:-9999px を設定したりすることで、ボタン要素が見えないが、label のクリックは効くようになります。
もう一つは Label に click ハンドラーをバインドして、もし label が click したら、for attribute で設定したボタンの click 関数を呼び出します。
それでは。
2014年7月24日木曜日
WCF + Angular で Sever と Client の Decouple
最近、C# ASP.NET に突入。Back end は C# ASP.NET で、Front End は主に AngularJS を使います。以前 Microsoft の Web Application サンプルはほとんど knockout.js を使っていたため、古いシステムはほとんどそれでした。が、AngularJS から Dependency Injection などのデザインパタンが導入されて、Unit Test や End to End Test が簡単にできるようになったので、だんだん AngularJS に移行する動きがあります。
今のプロジェクトも移行中です。新しい Web Site は全部 Responsive Design を使っていて、AngularJS ベースに作っています。ただ、やはり cshtml の中に C# Razor のコードと HTML コードがごちゃごちゃになって、みにくいです。それで、WCF を使って、JSON オブジェクトを Front End に返して、View に関してのすべてのロジックは AngularJS をベースに JavaScript で実装することはいいような気がしてます。
まず、Action を WCF の End Point に変更して、Model はそんなに大きくないので、$http を使って、AJAX でロードして、その後、もう Front End の世界です。新しいブラウザだと、もっと速いような気がしています。サーバーの負荷を考えると、もっと低いような気がしてます。なぜなら、View の parse が必要なくなり、すべて C# コードになるからです。 Ajax コールが多くなりますが、データを組み立てるだけなら、全然軽いと思います。
Front End でも、Dependency Injection を使っているので、サーバーと隔離するできるし、サーバー側もデータをメインでテストできるから、すべてはスムーズに分けるようになります。Decouple も MVC だけでなく、Server と Client の間でもできるようになります。
これは多分いいことです。次回の会議でちゃんと話しましょう。
それでは。
今のプロジェクトも移行中です。新しい Web Site は全部 Responsive Design を使っていて、AngularJS ベースに作っています。ただ、やはり cshtml の中に C# Razor のコードと HTML コードがごちゃごちゃになって、みにくいです。それで、WCF を使って、JSON オブジェクトを Front End に返して、View に関してのすべてのロジックは AngularJS をベースに JavaScript で実装することはいいような気がしてます。
まず、Action を WCF の End Point に変更して、Model はそんなに大きくないので、$http を使って、AJAX でロードして、その後、もう Front End の世界です。新しいブラウザだと、もっと速いような気がしています。サーバーの負荷を考えると、もっと低いような気がしてます。なぜなら、View の parse が必要なくなり、すべて C# コードになるからです。 Ajax コールが多くなりますが、データを組み立てるだけなら、全然軽いと思います。
Front End でも、Dependency Injection を使っているので、サーバーと隔離するできるし、サーバー側もデータをメインでテストできるから、すべてはスムーズに分けるようになります。Decouple も MVC だけでなく、Server と Client の間でもできるようになります。
これは多分いいことです。次回の会議でちゃんと話しましょう。
それでは。
登録:
投稿 (Atom)