2010年5月28日金曜日

JavaScriptによって動的に CSSを追加する場合のブラウザごとの挙動の違い


(2011/3/29追記:Firefox4とChrome10の実験結果を追加。)


JavaScriptに続いて、動的にCSS(またはstyle要素)を追加する場合の、追加方法の違いによるブラウザごとの挙動も調べた。


調査に使ったJavaScript(HTML5なのでtype属性は省略してある。)
window.onload = function() {
    // divのinnerHTMLに外部CSSの読み込みを突っ込む
    try {
        var div1 = document.createElement("div");
        div1.innerHTML = '<link rel="stylesheet" href="test1.css" />';
        document.body.appendChild(div1);
    } catch(e) {
    }
    
    // createElementでlink要素を生成して外部CSSを読み込む
    try {
        var link2 = document.createElement("link");
        link2.rel = "stylesheet";
        link2.href = "test2.css";
        document.getElementsByTagName("head")[0].appendChild(link2);
    } catch(e) {
    }
    
    // divのinnerHTMLに生のstyleを突っ込む
    try {
        var div3 = document.createElement("div");
        div3.innerHTML = '<style>#test3{ color: red }</style>';
        document.body.appendChild(div3);
    } catch(e) {
    }
    
    // createElementで生のstyleを追加する
    try {
        var style4 = document.createElement("style");
        style4.innerHTML = "#test4{ color: red }";
        document.getElementsByTagName("head")[0].appendChild(style4);
    } catch(e) {
    }
};

test1.css
#test1{ color: red }

test2.css
#test2{ color: red }


結果は下記の通り。

ブラウザ
1
2
3
4
IE 8




Firefox 3.6




Firefox 4.0




Chrome 4




Chrome 10




Safari 4




Opera 10.53






やはりIEではcreateElementで外部ファイルを読み込む方法しか動かない。
(ちなみに外部ファイルはhead要素でなくbody要素にappendChild()してもOKだった。)

でもChrome、SafariやOperaでほぼ動いたのが意外だった。


(2011/3/29 追記)
style 要素を動的に生成する - m2の方法を使えば、innerHTMLに生のstyleを突っ込むことができる。IE8、Firefox4、Chrome10、Opera11で動作することを確認した。

JavaScriptによって動的に script要素を追加する場合のブラウザごとの挙動の違い


(2011/3/29追記:Firefox4の実行結果を追加)


JavaScriptによって動的にscript要素を追加する場合の、追加方法の違いによるブラウザごとの挙動を調べた。


調査に使ったJavaScript(HTML5なのでtype属性は省略してある。)
<script>
function test(value) {
    document.getElementById("test").value += value + ",";
}

window.onload = function() {
    // innerHTMLに外部JSの読み込みを突っ込む
    try {
        var div1 = document.createElement("div");
        div1.innerHTML = '<script src="test1.js"><' + '/script>';
        document.body.appendChild(div1);
    } catch(e) {
    }
    
    // createElementでscript要素を生成して外部JSを読み込む
    try {
        var script2 = document.createElement("script");
        script2.src = "test2.js";
        document.body.appendChild(script2);
    } catch(e) {
    }
    
    // innerHTMLに生のJSを突っ込む
    try {
        var div3 = document.createElement("div");
        div3.innerHTML = '<script>test(3);<' + '/script>';
        document.body.appendChild(div3);
    } catch(e) {
    }
    
    // createElementで生のJSを追加する
    try {
        var script4 = document.createElement("script");
        script4.innerHTML = "test(4);";
        document.body.appendChild(script4);
    } catch(e) {
    }
};
</script>


test1.js
test(1);

test2.js
test(2);

処理が実行されたら、表示用のテキストボックス(id="test")に番号が表示される。

結果は下記の通り。

ブラウザ
1
2
3
4
表示順
IE 6/8




2
Firefox 3.6




1→2→3→4
Firefox 4.0




4→2
Chrome 4




4→2
Safari 4




4→2
Opera 10.53




2→4


innerHTMLにscript要素を入れるのはFirefox3.6以外では動かない。(セキュリティ的な施策?)
Firefoxでしか動作確認していないと嵌りそうだ。(というか嵌った。)

IEのことを考えると、生のcreateElementで生成したscript要素に生のJavaScriptを書くやり方も使えない。
(そんなことが必要になるケースは無いかもしれないが。)

素直にcreateElementで外部JSを読み込むのが良いようだ。


(2011/3/29 追記)
innerHTMLに生のJavaScriptを入れる方法としてinnerHTMLでscriptする - Thousand Yearsの方法があったようだ。
しかし現在のブラウザでは、IE8では動作したが、Firefox4、Chrome10、Safari5、Opera11では動作しなかった。

PHPでマイナスの少数を切り上げ、切り捨て、四捨五入


PHPに限った話ではないんだろうけど、マイナスの数の丸め方について混乱したので実験してみた。

//切り上げ
echo ceil(0.5); // => 1
echo ceil(-0.5); // => -0

//切り捨て
echo floor(0.5); // => 0
echo floor(-0.5); // => -1

//四捨五入
echo round(0.5); // => 1
echo round(-0.5); // => -1

ceil()で"-0"が謎なのはおいておいて、round()で-0.5が-1になるのはExcelと同じだ。



(2010/06/02 追記)
intval()の場合、floor()とは違う結果になるようだ。
//全てゼロになる
echo intval(0.4);
echo intval(0.5);
echo intval(0.6);
echo intval(-0.4);
echo intval(-0.5);
echo intval(-0.6);

SQLの集計関数で NULLを含む列を集計する場合の注意点

MySQLとPostgreSQLの話。他のRDBMSは試してない。


SQLでNULLを含む列を集計する場合、NULLの行は無視される。
SELECT
  SUM(t.v) -- => 15
, MIN(t.v) -- => 0
, MAX(t.v) -- => 10
, COUNT(t.v) -- => 3 (NULLの行も入れれば4行)
, AVG(t.v) -- => 5.0000 (NULLの行をゼロと考えれば3.75)
FROM (
  SELECT NULL v
  UNION
  SELECT 0 v
  UNION
  SELECT 5 v
  UNION
  SELECT 10 v
) t
(PostgreSQL 8.4とMySQL 5.1で確認。)


SUM()やMIN()、MAX()では問題ないが、COUNT()やAVG()ではNULLの行の分も含めて計算したい場合に困る。

そういう場合、COALESCE()でNULLをゼロに変換するとよい。
SELECT
  COUNT(COALESCE(t.v, 0)) -- => 4
, AVG(COALESCE(t.v, 0)) -- => 3.7500
FROM (
  SELECT NULL v
  UNION
  SELECT 0 v
  UNION
  SELECT 5 v
  UNION
  SELECT 10 v
) t

MySQLの場合はIFNULL()でもOK。


参考
PostgreSQL: Documentation: Manuals: PostgreSQL 8.4: Conditional Expressions
MySQL :: MySQL 5.5 Reference Manual :: 11.2.3 Comparison Functions and Operators

2010年5月18日火曜日

Google App Engineと Google Codeからのファイル取得速度の比較

Webブラウザ(Firefox)でファイルを取得する際に、Google App EngineとGoogle Codeのどちらから取得した方が速いかの実験。
12KB程度のJavaScriptファイルで比べてみた。
ついでにDropboxのpublicフォルダからの取得も調べてみた。


計測結果


App EngineGoogle CodeDropbox
gzipありなしあり
サイズ4.2KB12.5KB4.7KB
1回目282ms47ms766ms
2回目219ms31ms1030ms
3回目218ms47ms1030ms
平均240ms42ms942ms

(gzipでもGoogle App EngineとDropboxでサイズが違うのはなぜ?)

gzipなしにも関わらず、Google Codeが速い。キャッシュサーバとかを使ってCDNとして機能しているということか。
App Engineは遠いサーバにあるのかな?それともstaticなファイルとして送り出されるまでの処理に時間がかかっているのか。


また、Google App Engine、Google Codeともにしばらく(1分程度?)誰もアクセスしないとSpin Down的なことが起きるようで、その後の初回アクセス時のレスポンスが遅い。だいたい、Google App Engineは200~600ms程度、Google Codeは200~300ms程度かかるようだ。
(上記の表ではこの「初回アクセス」は含んでいない。)

PHPで 画像をdataスキーム URI化して表示するサンプル

使いどころは思いつかないがメモしておく。
$path = 'path/to/target.gif';

$uri = 'data:' . mime_content_type($path) . ';base64,';
$uri .= base64_encode(file_get_contents($path));

echo '<img src="' . $uri . '" />';

参考:php :: gif画像をbase64エンコードしてimgタグで表示する :: ウェブデザイナーの日記

PHPで画像ファイルの Mime-Type (Content-Type)を取得する方法

4つ見つけた。


getimagesizeを使う方法
$info = getImageSize($path);
echo $info['mime'];
PHPのGD拡張が必要。
2行になってしまってちょっと冗長か。



exif_imagetypeを使う方法
echo image_type_to_mime_type(exif_imageType($path));
前提としてPHPのExif拡張が有効になっている必要あり。(参考:PHP: インストール手順 - Manual
今度は横に冗長だ。



mime_content_typeを使う方法
echo mime_content_type($path);
シンプルで良いのだが、なぜか非推奨。
(環境によってはmime_content_typeが無い場合もある?)



Fileinfoオブジェクト(FInfoクラス)を使う方法
$info = new FInfo(FILEINFO_MIME_TYPE);
echo $info->file($path);
PHP5.3以降で使える。それ以前はPECL拡張だった(参考:PHP: インストール手順 - Manual
ちょっと大袈裟な気がするが、PHP5.3以降ではこれが推奨されるようだ。

でもクラス名はFileInfoにすべきだった?

PHPの header()でリダイレクト以外でも HTTPステータスコードを指定する

PHPのheader()を使ってリダイレクトをさせる場合、デフォルトではHTTPステータスコードは302(Found、一時的なURL変更等)になる。
リダイレクト時のHTTPステータスコード301(Moved Permanently、恒久的なURL変更)にしたい場合、header()の第3引数に301を指定する。
header('Location: http://www.example.com/', true, 301);
参考:生の HTTP ヘッダを送信する - PHP 5.3 日本語マニュアル



この第3引数でのHTTPステータスコードの指定だが、リダイレクト以外でも使えるか試してみた。
<?php header('X-Test: TEST', true, 404) ?>

テストです。

結果(HTTPレスポンスヘッダーから一部を抜粋)
HTTP/1.1 404 Not Found
X-Test: TEST

できた。

これを使えば、header()でHTTPステータスコードを指定し、なおかつ他のHTTPレスポンスヘッダーの値をセットしたい場合に、1行で処理できる。
あまりそんな機会はないか。

2010年5月8日土曜日

Webでの静的ファイル取得について Google App Engineと Google Codeを比較

(2010/5/11 修正:Google Codeでもmax-ageが設定可能だったので一部修正。参考:SubversionFAQ - support - Subversion FAQ - Project Hosting on Google Code


スタティックなファイルのhttp(s)での取得について、Google App EngineとGoogle Codeを比較してみる。


Google App Engine
Google Code
https


gzip
あり
なし
Etag
あり
あり
Last-Modified
なし
あり
max-age
(Expires)
設定可能
デフォルトは10分
設定可能
デフォルトは1分

Server
Google Frontend
Apache


URLは下記のような感じ。App Engineの方がちょっと短い。
Google App Engine → http://<アプリ名>.appspot.com/<パス>
Google Code → http://<プロジェクト名>.googlecode.com/svn/<パス>


Google App Engineのメリット
gzipで圧縮されるのでトラフィックを減らすことができる。
Google Codeの方はmax-ageはまだいいとしても、Google Codeはgzipされないのが痛い。
主なクライアントがSubversion(等)のクライアントソフトだから仕方ないか。


Google Codeのメリット
そのままSubversion(等)でバージョン管理できる。使い慣れたクライアントソフトで制御できるので使い易い。
Google App Engine Launcherは毎回パスワードを訊かれるのが面倒。


Google App Engineは転送量等に制限があるが、かといってGoogle Codeでもそんなにも使ったらBanされるんだろう。
(参考:floatingdays: Google Codeの JavaScriptファイルを外部サイトから読み込んでも良い?

それから、Google Codeではその性格上オープンソースとして公開する必要がある。

Cucumberインストールメモ

素に近いCentOSへのインストールのメモ。Railsは使わない。
(やったことを順に書いたので、「正しいインストール方法」としては読み辛い。)


  1. Rubyインストール

    yum install ruby ← 1.8.6以降が必要になり、後でソースから入れなおした
  2. Ruby Gemsインストール

    yum install rubygems ← Rubyを入れなおしたのでこちらもソースから入れなおした
  3. Cucumberインストール

    gem install cucumber
    最初はずっとcucamberと書いていてエラーにされた
  4. Webratインストール

    gem install webrat
    しようとしたらエラーになった。

    Building native extensions.  This could take a while...
    ERROR:  Error installing webrat:
            ERROR: Failed to build gem native extension.

    /usr/bin/ruby extconf.rb install webrat
    can't find header files for ruby.


    Gem files will remain installed in /usr/lib/ruby/gems/1.8/gems/nokogiri-1.4.1 for inspection.
    Results logged to /usr/lib/ruby/gems/1.8/gems/nokogiri-1.4.1/ext/nokogiri/gem_make.out

    1. 参考になりそうな記事を見つけた → gem で nokogiri, webrat放り込む時にエラーでた on Ubuntu 9.04 - ザ・職人
    2. libxml2はインストール済みだった
    3. libxml2-develインストール

      yum install libxml2-devel
      でもWebratはインストールできなかった
    4. libxsltはインストール済み
    5. libxslt-develインストール

      yum install libxslt-devel
      でもWebratはダメ
    6. もしかしてruby-devel?

      yum install ruby-devel
    7. webrat再チャレンジ

      gem install webrat
      入った!
  5. Mechanizeインストール

    gem install mechanize
  6. RSpecも必要?

    gem install rspec
これでインストール完了。

インストールされたバージョンは
  • Ruby 1.8.5
  • Cucumber 0.6.4
  • Webrat 0.7.0
  • Mechanize 1.0.0
  • RSpec 1.3.0



試しに動かしてみたい。
ここが参考になりそう → エンジニアは空を飛ぶ: Cucumber入門(1) 一番最初のCucumber
featureの日本語化についてはここを参考にやってみる → Ruby/cucumber/日本語を使う方法 - TOBY SOFT wiki


ディレクトリ作成
cd /tmp
mkdir cuke
cd cuke
mkdir features
mkdir features/step_definitions
mkdir features/support


環境設定ファイル作成
下記を参考に

vi features/support/env.rb
require 'spec/expectations'
require 'cucumber/formatter/unicode'

# Webrat
require 'webrat'

require 'test/unit/assertions'
World(Test::Unit::Assertions)

Webrat.configure do |config|
  config.mode = :mechanize
end

World do
  session = Webrat::Session.new
  session.extend(Webrat::Methods)
  session.extend(Webrat::Matchers)
  session
end


featureファイル作成
vi features/test.feature
# language: ja
機能: Cukeのテスト
  "Cucumber"をGoogle検索してCucumber関連の検索結果を得る

  シナリオ: 同上
    前提 "http://www.google.co.jp/"ページを表示している
    もし "cucumber"を検索する
    ならば "moroの日記"と表示されていること


stepsファイル作成(ためしにGivenだけ)
vi features/step_definitions/test_steps.rb
# -*- encoding: UTF-8 -*-

前提 /^"([^"]+)"ページを表示している$/ do |url|
  visit url
end
動かしてみる
cucumber
エラー発生
    前提 "http://www.google.co.jp/"ページを表示している # features/step_definitions/test_steps.rb:3
      undefined method `instance_variable_defined?' for #<Net::HTTP www.google.co.jp:80 open=false> (NoMethodError)
      (eval):2:in `visit'
      ./features/step_definitions/test_steps.rb:4:in `/^"([^"]+)"ページを表示している$/'
      features/test.feature:6:in `前提 "http://www.google.co.jp/"ページを表示している'

instance_variable_defined
はRuby1.8.6以降が必要らしい → rip のインストール - オボロぼろぼろ
Ruby1.8.6以降はyumでは見つからなかったので、ソースからコンパイルする必要があるようだ。


こちらを参考に → マルニカ。 CentOSに最新Rubyをインストール。

Rubyをアンインストール
yum remove ruby
gemsも一緒にremoveされた。

後は上記サイトのとおりに。
Rubyはなんとなく1.9.1を選んだ。
最新のバージョン番号は下記で確認で。

またひととおりCucumberからインストールし直しだ...

(Gemでインストールし直す。)

インストールされたバージョンは
  • Ruby 1.9.1
  • Cucumber 0.6.4
  • Webrat 0.7.0
  • Mechanize 1.0.0
  • RSpec 1.3.0

再チャレンジ
cucumber
3 steps (2 undefined, 1 passed)
「前提」が通った!


steps内の記述については下記が参考になりそう。

日本語でStepsを定義する際には書き方をmoro-misoに合わせておいた方が後々幸せになれるかも。


また、Cucumberの問題ではないが、CentOS 5.4でもprelinkはRuby-1.9.1のバイナリを破壊する、かも | Selfkleptomaniacという問題が発生したので、ここに書いてある対応をした。

HTML5のテンプレート例

(2010/05/28 変更:Google Analytics非同期版が正式リリースされたので、Beta版から正式版に変えた)
(2010/08/30 変更:div構造を少しすっきりさせた)
(2010/11/04 変更:Google Analyticsのコードをhead要素内へ移動)
(2011/04/25 追加:IEの条件付きコメント(Conditional Comments)の例を追加)

Google Analytics非同期版YUI 2 Grids CSSjQueryを組み込んだHTML5の例。
header要素等のHTML5の新要素は使わない。(今のところあまりメリットを感じないので、)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>{{ TITLE }}</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<link rel="alternate" type="application/rss+xml" href="{{ RSS_URL }}" title="{{ FEED_TITLE }}" />
<link rel="alternate" type="application/atom+xml" href="{{ ATOM_FEED_URL }}" title="{{ FEED_TITLE }}" />
<link rel="stylesheet" href="{{ STYLESHEET_URL }}" />

<style>
{{ STYLE }}
</style>
<!--[if lt IE 9.0]>
<style>
{{ STYLE for IE8- }}
</style>
<![endif]-->

<script>
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-{{ GOOGLE_ANALYTICS_ID }}']);
_gaq.push(['_trackPageview']);
(function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>

<!-- @see http://developer.yahoo.com/yui/grids/builder/ -->
<body id="doc4">

<div id="hd">
{{ HEADER }}
</div><!-- #hd -->

<div id="bd">
{{ MAIN_CONTENT }}
</div><!-- #bd -->

<div id="ft">
{{ FOOTER }}
</div><!-- #ft -->

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="{{ JAVASCRIPT_URL }}"></script>
<script>
{{ JAVASCRIPT }}
</script>

</body>
</html>

使うときに不要な要素は減らす。
{{ }}のところは置き換える。

PHPの場合、default_charsetを設定してあれば<meta charset="UTF-8" />は無くても大丈夫。

YUI 2 CSS Toolsはcommon.cssとか作ってその中でimportした方が変更に強い。

Google Analyticsは外部JavaScriptファイルにした方がすっきりするけど、そのためだけにHTTPアクセスを1回増やすのもどうかと思う。
「http://www.google-analytics.com/ga.js#UA-XXXXXXX-X」の形で呼び出せるようにすればいいのに。
(ただし静的なHTMLに埋め込む場合は、変更が発生したときに全部変更するのは大変なので外出ししておいた方がよい。)

jQueryはプラグインをいろいろ使うならマイナーバージョン(1.4とか)まで指定しておいた方が無難。


(2010/11/04 追記)
Google Libraries APIのCDNから読み込むJavaScriptが複数ある場合は、google.load()を使って非同期で読み込んだ方が少し早くなる場合がある。
<!-- 例:jQueryとjQuery UIを非同期で読み込む -->
<script src="//www.google.com/jsapi?key={{ GOOGLE_LOADER_API_KEY }}"></script>
<script>
google.load("jquery", "1");
google.load("jqueryui", "1");
google.setOnLoadCallback(function() {
    $(function() {
        //初期処理
    });
});
</script>

注意点としては、google.load()を使うには先に http://www.google.com/jsapi 読み込む必要がある。これを読み込むコストを考えると、必要なJavaScriptが1つしかないならgoogle.load()を使ってもそれほど効果はないか、逆に遅くなる。


(2011/03/15 追記)
シンプル版も書いておく。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>HTML5 Simple Template</title>
<link rel="stylesheet" href="stylesheet.css" />
<style>
/* STYLE */
</style>
</head>
<body>

CONTENT

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="javascript.js"></script>
<script>
/* JAVASCRIPT */
</script>
</body>
</html>

dataスキームによる画像を手軽に作れるジェネレータ

dataスキームはIE6・7は未対応。IE8から対応した。


グラデーション画像は、cyokodogさんが作った生成ツールが便利そう。
IEはfilterにより表現されるのでIEでも表示できる。


画像ファイルからdataスキームを生成したい場合は[JavaScript] dataスキームURI生成(画像データのBase64変換)が便利そう。


その他の場合でかつHTML5のcanvasで描けるような図形の場合、toDataURL() メソッド - Canvasリファレンス - HTML5.JPを参考にしならが自分で作る。