2008-09-30

CSS Browser Selector を使って Google Chrome を個別対応する。textarea のフォントサイズの相対値がおかしな件。

Google Chrome だけ textarea の font-size がひとまわり小さくなってしまう
Google Chrome を使って Golazo MA4 の動作確認をして気が付いたのですが、Google Chrome だけ、次のように textarea の font-size がひとまわり小さく表示されてしまうのです。em 単位で指定してるのが良くないのかなぁ。ネットで調べてみたのですが、それらしき話題は見つかりませんでした。時間があるときに原因を調べてみます。
引き続き、調べてみました。Google Chrome の textarea は、気になるところが2点ほどあります。

1つ目は、textarea のフォントサイズが、他のブラウザに比べて、ひとまわり小さいことです。他のブラウザに比べて 80% くらいの大きさでしょうか。Styling even more form controls を使うと、様々なフォームのスタイルを手軽に確認できます。

2つ目は、次のように textarea のフォントサイズに相対値を指定すると、元々の textarea のフォントサイズに対して .8em 分、小さく表示されるということです。CSS の仕様は、あまり詳しく把握できていないのですが、.8em と指定したときは、1em に対する .8em のフォントサイズになると理解しているのですが、どうもそうじゃないようです。Safari 3.1 だと期待どおりなのですが...。

#content form input,
#content form textarea {
font-size: .8em;
}

次のとおり、フォントサイズに絶対値を指定すると、Google Chrome も含め、すべてのブラウザで、期待どおりのフォントサイズで表示できます。ただし、フォントサイズを固定すると、文字サイズの拡大と縮小で、ブラウザ間で差異が出てしまう(特にIE)ため、対処としては不完全といえます。

#content form input,
#content form textarea {
font-size: 13px;
}

Google Chrome だけ個別に対応する手法(いわゆる CSS Hack)がないか探してみましたが、Google Chrome は Safari 3 の CSS Hack を適用できるが、Google Chrome だけ適用できる CSS Hack はなさそうでした。Google Chrome を調整すると Safari 3 に影響が出ちゃうということですね。う~む。

ということで CSS だけでの対応はあきらめ、CSS Browser Selector という JavaScript を使って、User-Agent ごとに CSS を切り替える仕組みを使って対応することにしました。

CSS Browser Selector の導入はとても簡単です。次のように JavaScript をロードして、

<script type="text/javascript" src="css_browser_selector.js"></script>

次のように、ブラウザごとに CSS を記述するだけです。Google Chrome だけ textarea のフォントサイズを 1em にしています。これで、すべてのブラウザで、ちょうど 80% の大きさになります。

#content form input,
#content form textarea {
font-size: .8em;
}

.chrome #content form textarea {
font-size: 1em;
}

CSS Browser Selector が何をするかというと、navigator.userAgent を見分けて、HTML 要素にブラウザの種類を表すクラス名を動的に追加するというだけです。ブラウザのエンジン、オペレーティングシステムの区別もできます。なかなかシンプルなアイディアに感心します。

<html class="gecko ff2 win js">

JavaScript が無効だと使えないという欠点がありますが、JavaScript の有無による CSS の区別も備えるため、JavaScript が無効なときの妥協的な CSS を用意するといった対応もできます。

2008-09-29

Google Chrome だけ textarea の font-size がひとまわり小さくなってしまう

Google Chrome を使って Golazo MA4 の動作確認をして気が付いたのですが、Google Chrome だけ、次のように textarea の font-size がひとまわり小さく表示されてしまうのです。



次のように、その他のブラウザ (画像は Firefox2) では、だいたい同じ大きさで表示されます。



font-size を指定するスタイルシートは、次のとおりです。

#content form input,
#content form textarea {
font-size: .8em;
}

em 単位で指定してるのが良くないのかなぁ。ネットで調べてみたのですが、それらしき話題は見つかりませんでした。時間があるときに原因を調べてみます。ひとまず、デザインに方針に影響を与えそうなので、事象だけお伝えしときます。

2008-09-27

Aptana Jaxer から LocoSticker を使ってメモから位置表現と緯度経度を抽出する

Golazo MA4LocoSticker 位置表現特定 API を使って、メモから位置情報を自動抽出しています。LocoSticker 位置表現特定 API は、住所やスポットのテキストを抽出することに加えて、テキストの主題との関連の強さを判断し、関連の強いものを抽出するという、頭の良さを備えた優れものの API です。

Golazo MA4 は Aptana Jaxer を使って JavaScript のみで動かしています
先日公開した Golazo MA4 ですが、サーバサイド JavaScript (Ajax Server) の仕組みを備えた Aptana Jaxer を使って、アプリケーションを構築し、運用しています。
LocoSticker 位置表現特定 API は、GET や POST でクエリを与えると、JSON 形式で結果を返すという、よくあるパターンの API なので、具体的な呼び出し方を紹介する必要はないのですが、Golazo MA4 は Aptana Jaxer を使って、サーバサイドの JavaScript から LocoSticker 位置表現特定 API を呼び出していますので、どうやってるの?という関心があるかと思います。

そこで、Golazo MA4 から LocoSticker 位置表現特定 API をどう呼び出しているか、実際のソースコードを引用して紹介します。

// LocoSticker 位置表現特定 API
// http://okilab.jp/project/location/2007/11/locosticker_api.html
var url = 'http://api.locosticker.jp/v1/extract_place/';
var data = $.param({ text: text, selection: 'on' });

LocoSticker 位置表現特定 API の URL とクエリを準備します。クエリの URL エンコードは jQuery を使っています。text は抽出対象のテキストです。selection を on とすると、前述の頭の良さを発揮します。

var clusters;
try {
clusters = eval('(' + Jaxer.Web.post(url, data) + ')')
.result_select;
} catch (e) {
throw new Error(''+e);
}

LocoSticker 位置表現特定 API を POST メソッドで呼び出して、実行結果の JSON を JavaScript オブジェクトに変換しています。

// クラスタをフラットな配列に変換する
clusters.forEach(function(cluster) {
cluster.forEach(function(where) {
where.label = where.text;
where.latitude = where.lat;
where.longitude = where.lng;
where.altitude = Golazo.WHERE_TOKYO.altitude;
response.push(where);
});
});

実行結果から位置表現、緯度、経度を取り出して、配列にまとめています。

// 住所よりもスポットを優先し、重みでソートする
response.sort(function(a, b) {
if (a.type == b.type)
return a.weight - b.weight;
else if (a.type == 'spot')
return -1;
else
return 1;
});

実行結果は、住所とスポットが入り混じっているので、スポットを優先に重み(スコアが高い)順で並べ替えをしています。

// 位置表現が特定できたら継続しない
if (response.length > 0)
return response; // found.

そして、その並べ替えた実行結果を使って、メモに位置情報を付与しています。1件も位置表現が抽出できないときは、別の API を使って、メモから位置情報を抽出しようと試みます。この続きは、後日、紹介します。

2008-09-23

Golazo MA4 は Aptana Jaxer を使って JavaScript のみで動かしています

Golazo MA4 という Mashup Awards 4 応募作品をオープンしました。
ここ1ヶ月ほど、Mashup Awards 4 応募作品の開発に専念するため、エントリの投稿をサボっていました。いろんな人からの協力もあり、おかげさまで Mashup Awards 4 に作品を応募することができました。この場を借りてお礼します。ありがとうございました。
先日公開した Golazo MA4 ですが、サーバサイド JavaScript (Ajax Server) の仕組みを備えた Aptana Jaxer を使って、アプリケーションを構築し、運用しています。

Mashup Awards をどう取り組むかは、様々な想いがあると思いますが、Golazo MA4 team としては、新しい技術を試す絶好の機会と捉えていて、実際に Aptana Jaxer を使って JavaScript のみでどこまで開発できるのかチャレンジしてみた次第です。

Aptana Jaxer サイトに Ajax アプリケーションの特性をよく表している図があります。この図は Aptana Jaxer に限らず、一般的な Ajax アプリケーションの構成をも表すもと理解できます。

1 がサーバサイド、2 がクライアントサイド、3 が XMLHttpRequest (XHR) を使ったサーバサイドとクライアントサイドの関係を表しています。一般的に 1 は Java、PHP、Ruby、Python などを使い、2 は JavaScript を使うことになります。3 はその組み合わせになりますが、1 が持つフレームワークの仕組みで 2 と 3 を実現することも多いですね。


Copyright 2005-2008 Aptana, Inc.

Golazo MA4 は 1~3 のすべてを JavaScript を使って実現しています。Golazo MA4 の中身がどんな構成になっていて、どんなことをしているのか、その概観を紹介します。

Jaxer Server は HTTP サーバの機能を持ちません。ですので、Apache 2.2 をフロントエンドにしています。そして、mod_jaxer というモジュールを介して Apache と Jaxer Server を繋げています。

データベースは SQLite を使っています。データベースの操作は Jaxer Framework がサポートしています。Google Gears のインタフェースを包含しています。MySQL などにも対応しているので、必要があればスケールアップすることも考えています。


Copyright 2005-2008 Aptana, Inc.

会員のプロフィール画像をアップロードして、ローカルファイルとして保存しています。ローカルファイルの操作も Jaxer Framework がサポートしています。JSLib をベースにしているようです。ですので、ソケットなんかも使えたりします。

外部の WEB API を呼び出して (GET/POST) して、データ (JSON/XML) を取得しています。外部ホストに対する操作も Jaxer Framework がサポートしています。Jaxer Server は Firefox3 をエンジンとするため、サーバサイドから XHR が使えたりします。


Copyright 2005-2008 Aptana, Inc.

メモの登録や削除、友達になる、友達をやめるなどの操作は、XHR を使ってクライアントサイドからサーバサイドを操作しています。いわゆる Ajax といわれるものです。このとき、Jaxer Framework がクライアントサイドとサーバサイドを仲介してくれるので、JSON のエンコードやデコード、シリアライズというデータ交換のコードはいっさい書いていません。次のように JavaScript の関数を定義して呼び出すだけです。


Copyright 2005-2008 Aptana, Inc.

送信フォームの入力チェックは、基本的にクライアントサイドで実行しています。が、同じ入力チェックをサーバサイドでも実行しています。クライアントサイドとサーバサイドで、同じコードを共有しているので、クライアントサイドだから、サーバサイドだからといった余計なことを考えなくて済んでいます。


Copyright 2005-2008 Aptana, Inc.

サーバサイドでの HTML ページの生成は DOM/CSS をベースとしています。これは Firefox3 をエンジンとする Jaxer Server の特長ともいえます。上から下に向かって HTML ページを生成する一般的なテンプレートエンジンとは発想が異なるため、今までの経験が適用しにくく戸惑いました。


Copyright 2005-2008 Aptana, Inc.

Jaxer Server は、HTTP リクエストがあると、その HTML ページを読み込んで DOM にします。そして、その DOM に対して操作をするタイミングを用意し、その後、操作した DOM をシリアライズして HTTP レスポンスとします。ですので、普段クライアントサイドで使っている JavaScript ライブラリをサーバサイドでも使えるメリットがあります。今回は jQuery を使って、サーバサイドで HTML ページを生成しています。

残念ながら CARWINGS-CASTING (RSS 2.0) は Aptana Jaxer ではなく Ruby を使っています。というのは、Aptana Jaxer は HTML を扱うことを基本(例外もあるらしいが未確認)としているためです。このとき、Ruby から前述の SQLite のデータベースを参照して、Aptana Jaxer と Ruby の共存をはかっています。

う~ん。ざっと全体像をなぞったつもりですが、いろいろ書きたいことがあって、うまく伝わらないかもしれません。今後は、それぞれの領域で、具体的なコードとかをまじえて紹介できればと思います。Aptana Jaxer 自体の Tips は、過去にこのブログで取り上げていますので、参考にしてください。

2008-09-22

Aptana Jaxer は同名パラメータの複数値を扱えないので個別の対応が必要です

チェックボックスを並べた送信フォームを用意し、そのチェックボックスのパラメータ名(input@name)を同じにして、送信先で、その複数のパラメータ値を取得する... といったことは、ウェブアプリケーションの開発でよく使うパターンです。

ですが、Aptana Jaxer の Jaxer.Request クラスは、同名パラメータの複数値を扱えないため、個々のパラメータ名を重複しないようにするか、Jaxer.Request を使わない個別の対応が必要です。

Jaxer の実装がどうなっているかというと、GET メソッドのときは、次のとおり、QUERY_STRING を解析しています。同じパラメータ名があると、後のパラメータ値で上書きしています。

var queryStrings = query.split("&");
for (var i = 0; i < queryStrings.length; i++)
{
var nameValue = queryStrings[i].split("=");
hash[Util.Url.formUrlDecode(nameValue[0])] = (nameValue.length > 1)
? Util.Url.formUrlDecode(nameValue[1])
: null;
}

POST メソッドのときは、次のとおり、HTTP ボディを解析しています。GET メソッド同様に、同じパラメータ名があると、後のパラメータ値で上書きしています。

var postCount = _request.GetDataItemCount();
for (var i = 0; i < postCount; i++)
{
var name = _request.GetDataItemName(i);
var value = _request.GetDataItemValue(i);
data[name] = value;
}

この Jaxer.Request クラスの良し悪しを簡単に断定することはできません。が、RPC ベースの Jaxer Callback を主体とする Jaxer としては、用途を限定することを知りつつも、HTTP リクエストのデータ構造を単純にして、手軽さを採用したのだと推測しています。

ですので、同名パラメータの複数値を扱いたいときは、Jaxer.Request クラスのパラメータ解析に頼らず、次のように、自らのコードでパラメータを解析することになります。

このコードは、POST メソッドのとき、subscriber というパラメータが複数の値になることを前提としています。GET メソッドのときは Jaxer.Request.query プロパティ、POST メソッドのときは Jaxer.Request.postData を解析するとよいでしょう。

var subscribers = [];
request.postData.split('&').forEach(function(param) {
param = param.split('=');
if (param[0] == 'subscriber')
subscribers.push(param[1] || '');
});

なお、ここで扱った Aptana Jaxer のバージョンは 0.9.7.2472 (Linux) です。ですので、公開中の 1.0.0 RC B では改善されているかもしれません。

2008-09-21

『マイクロフォーマット Webページをより便利にする最新マークアップテクニック』

Microformats をどう使うかにもフォーカスしている『Microformats: Empowering Your Markup for Web 2.0』
Microformats wiki は、リファレンスとしての要素が強く、具体的にどう使ってよいのかが弱かったり、見つけにくいという印象があります。また、たくさんの情報が集まっていますが、その情報が多すぎて、必要な情報を見つけにくく、見落としてしまう実感があります。『Microformats: Empowering Your Markup for Web 2.0』という洋書は、前述の欠点を補ってくれるものですので、その内容を簡単に紹介します。
以前に紹介した洋書を翻訳したものが発売されていました。2008年7月末ころの発売だったようです。知らなかった。

マイクロフォーマット Webページをより便利にする最新マークアップテクニック (Web Designing BOOKS)

マイクロフォーマット ~Webページをより便利にする最新マークアップテクニック~ (Web Designing BOOKS)

2008-09-20

Safari2 で Golazo MA4 の CSS が適用されない不具合を解消しました

Golazo MA4 team のなかじまんです。

ごめんなさい。Golazo MA4 ですが、Safari2 だと CSS が適用されない状態となっていました。本日(9/20)、その不具合を改善したことをお知らせします。

その原因ですが、Golazo MA4 を iPhone (iPod touch を含む) 向けに最適化したものを公開しようと考えていました。その前準備として、メディアクエリを使って、CSS を切り替えるようにしていたのですが、Safari2 がメディアクエリに対応していないため、CSS が適用されないという状態になっていました。これらの経緯は、次のエントリにまとめてあります。

メディアクエリによる iPhone スタイルの切り替えは万能じゃないのか?
メディアクエリを使って iPhone (iPod touch を含む) とその他のブラウザで、スタイルシートを切り替えようと試みているのですが、Apple の開発者サイトやネット上で紹介している方法だと、なぜか Safari 2.0.4 (Multi-Safari) が期待どおり振る舞ってくれません。
まだ準備の段階にも関わらず、iPhone (iPod touch を含む) を意識したコードを HTML ページに忍ばせたのが失敗でした。Safari2 はきっと大丈夫という勝手な判断もよくなかったですね。ちょー反省です。

MacOS X Tiger と Safari3、Safari2 (Multi-Safari)、Firefox3、Firefox2 を使ってみて、どのブラウザでも不都合なくレンダリングしていることを確認できました。もしも、まだ不都合があるようでしたら教えてください。改善していきます。

2008-09-19

メディアクエリによる iPhone スタイルの切り替えは万能じゃないのか?

メディアクエリを使って iPhone (iPod touch を含む) とその他のブラウザで、スタイルシートを切り替えようと試みているのですが、Apple の開発者サイトやネット上で紹介している方法だと、なぜか Safari 2.0.4 (Multi-Safari) が期待どおり振る舞ってくれません。

次のとおり link 要素を記述しました。ですが、

<link media="only screen and (min-device-width:480px)" rel="stylesheet" type="text/css" href="/css/golazo-iphone.css" />
<link media="screen and (min-device-width:481px)" rel="stylesheet" type="text/css" href="/css/golazo.css" />

IE6 と IE7 がスタイルシートを読み込まなかったため、次のとおり [if IE] .. [endif] を追加して個別に対応しました。

<link media="only screen and (min-device-width:480px)" rel="stylesheet" type="text/css" href="/css/golazo-iphone.css" />
<link media="screen and (min-device-width:481px)" rel="stylesheet" type="text/css" href="/css/golazo.css" />
<!--[if IE]>
<link rel="stylesheet" type="text/css" href="/css/golazo.css" />
<![endif]-->

これで OK という気になっていたのですが、MacOS X Tiger の Safari 2.0.4 (Multi-Safari) を使ってみると、どのスタイルシートも読み込まれないのです。[if IE] .. [endif] はコメントでスキップしているので、つまり、メディアクエリを指定した link 要素をスキップしているということです。

何か方法が悪いのか、勘違いしているのか、自分自身の中で謎が深まっていくばかりです。Safari 2.0.4 (Multi-Safari) 固有の振る舞いなんでしょうか。今となっては Safari 2.0.4 を試す環境がないので、確認もできないのです。困りました。ヘルプみー。

追記です。-- 2008-09-19

明確な記述のある文献は見つかっていないのですが、ネット上などの情報を総合すると、Safari 2.0.4 の時点では、メディアクエリに対応していなかったようです。

ですので、1~1.5世代くらい前のブラウザも範疇に含める必要があるなら、UserAgent を見分けた切り替えが妥当という結論です。う~む。やっぱり、何らかのプログラム的な要素が必要になりますね。

2008-09-18

Jaxer Server (mod_jaxer) と Apache Etag と Last-Modified の矛盾を解消せよ

Aptana からダウンロードできる Jaxer のアーカイブ には Apache HTTP server も含まれますが、そのアーカイブをそのまま使うと、Apache の Etag と Last-Modified が働いてしまい、Jaxer Server (mod_jaxer) の動的コンテンツのキャッシングに矛盾が発生してしまいます。

どういうことかというと、次のような HTTP レスポンスヘッダ(抜粋)が返ってきます。Expires、Cache-Control、Pragma ヘッダで、ブラウザにキャッシュするなという一方で、Last-Modified と Etag ヘッダで、キャッシュを促すようにもしています。

HTTP/1.x 200 OK
Server: ModJaxer/0.9.7.2472 Apache/2.2.4
Expires: Fri, 23 May 1997 05:00:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Last-Modified: Wed, 17 Sep 2008 13:42:10 GMT
Etag: "351aa-3f1b-a2952661"

これは Jaxer Server (mod_jaxer) が、Apache のフィルタとして機能することに起因しています。Jaxer Server は、動的コンテンツを生成しているつもりでも、Apache は静的コンテンツを扱っていると認識しているという矛盾です。

IE7 や Opera は Last-Modified を優先するらしく、このまま使うと、HTML ファイルの最終更新日に応じてキャッシュが効いてしまい、ページ遷移したり、リロードしたりしても、動的コンテンツにも関わらず、304 Not Modified になってしまいます。

また、プロキシを介している環境であれば、ブラウザによらず、そのプロキシの振る舞いに影響を与えるかもしれません。

本来なら Jaxer Server (mod_jaxer) で Etag と Last-Modified を制御すべきなんだろうと思うのですが、[#JXR-308] Remove ETag and 'Last-Modified" response headers - ASAP - The Aptana Issues Tracker のとおり、Jaxer Server で何か対応するつもりはなさそうです。

ですので、Apache の設定で対応することになります。その一例は、次のとおりです。

FileETag ディレクティブを使って ETag ヘッダを送出しないようにしています。Header ディレクティブを使って Last-Modified ヘッダを送出しないように除去しています。すべての HTTP レスポンスに適用する必要はないので、FilesMatch を使って、Jaxer Server がフィルタするコンテンツのみに適用すればいいでしょう。

FileETag None
<FilesMatch ".\html">
Header unset Last-Modified
</FilesMatch>

なお、ここで扱った Aptana Jaxer のバージョンは 0.9.7.2472 (Linux) です。ですので、公開中の 1.0.0 RC B では改善されているかもしれません。

2008-09-17

Golazo MA4 という Mashup Awards 4 応募作品をオープンしました。

ここ1ヶ月ほど、Mashup Awards 4 応募作品の開発に専念するため、エントリの投稿をサボっていました。いろんな人からの協力もあり、おかげさまで Mashup Awards 4 に作品を応募することができました。この場を借りてお礼します。ありがとうございました。

その応募作品を簡単に紹介します。Golazo MA4 といって、場所をメモして活用することを目的としたサービスです。興味や関心がありましたら、ぜひ使ってみてください。
Golazo MA4 - ココで何する?
http://golazo.offtheball.jp/

Golazo MA4 は、ブラウザで見ているページや入力した文章の内容から位置情報を自動抽出し、気になるお店やプレイスポットなどの情報を、場所付きのメモとして、簡単にクリッピングできるサービスです。

外出先の携帯電話から使えば、現在地に最も近いメモを検索したり、その場所でメモを残せるので、行ってみたかったレストランを探したり、お店での買い物リストを見たり作ったりなど、クリッピングした場所付きのメモをアクティブに活用できます。

NISSAN のカーナビ に対応しており、インターネット情報チャンネル として登録すれば、自動車の現在地からメモが検索され、地図上で場所を確認したり、その内容を音声で読み上げることができます。

場所付きのメモは、知人や友人に公開できるので、待ち合わせ場所やお勧めのお店などを共有して楽しむこともできます。
今後、このブログを使って、Golazo MA4 の開発を通じて得たことや、Golazo MA4 の内側の技術的なところを紹介していきます。

Google ChromeのJavaScriptで見つけた振る舞いの違い

Google製のブラウザがでたので自前のJavaScript実装テストを流してみたところ、Array#lastIndexの振る舞いが他のブラウザと違うのことを見つけたので、レアケースで役に立たないネタですがメモとして書いておきます。

一つ目はlastIndexで、第二引数にnullやundefined、NaN、{}を指定するとChrome、Safari or Air、その他の間で振る舞いが異なります。次のコードを実行すると
var array = [1, "0", NaN, 1];
array.lastIndexOf(1, null);
array.lastIndexOf(1, void 0)
array.lastIndexOf(1, NaN)
array.lastIndexOf(1, {})

各ブラウザで以下の結果になります。
//Chrome
3
3
0
0

//Safari or Air
0
3
3
3

//その他
0
0
0
0
nullやundefinedを指定した時の始点が違うようで、0と3がばらばらな結果になります。ChromeはWebKitベースなのでJavaScriptエンジンもSafariと同じJavaScriptCoreだと思ったのですが、V8という独自のエンジンを積んでいるそうで、この振る舞いはそこが原因のようです。

二つ目はArray#sortメソッドの振る舞いで、以前実施したテスト(ここここ)をChromeでも実施してみたところ、今までにないユニークな結果を返しました。結果がいい感じにバラバラですがFirefoxは3が振る舞いをIEに合わせるなど徐々に統一されているようです。
["",void 0,null,NaN,0]    //並び替え前

[NaN,"",0,null,void 0] //Chrome
[null,void 0,NaN,"",0] //Dreamweaver, Netscape7
[void 0,null,NaN,0,""] //NN4
["",void 0,null,NaN,0] //Mozilla
[NaN,null,"",0,void 0] //Firefox
["",null,NaN,0,void 0] //IE, Opera, Rhino, Firefox3
[null,NaN,0,"",void 0] //Safari, AIR

追記:
lastIndexについて以前同じようなこと記事で書いたことを忘れていました。lastIndexで振る舞いが異なるのは仕様書が曖昧なのが原因のように感じます。テストコード付きの仕様書があると説得力あるのだけどどこかにあるのかなぁ。