2009年11月28日土曜日

Googleサイト内検索結果でページの下端が切れる問題

検索向けAdSenseによるサイト内検索で、IE以外だと検索結果の下端が切れてしまう問題について。


【発生条件】
検索結果を自分のサイト内に表示する場合に起きる。
広告を右のみにした場合に起きた。広告を他の位置にしたら起きないかも。
Firefox(3.5)、Google Chrome(4)、Safari(4 Windows版)で発生を確認した。


【現象】
検索結果の内容に関わらず、検索結果を表示するiframe(JavaScript http://www.google.com/afsonline/show_afs_search.js により生成される)の高さが常に1200pxになる。
その結果、検索結果が少ない場合は検索結果とフッターの間に大きな空白ができる。反対に検索結果が多い場合、検索結果の下端に表示されるページ番号の表示が途中で切れてしまったり表示されなくなってしまう。(実際にはframeの外側にあり、かつframeのスクロールバーが非表示になっているので隠れているだけだが。)


IEの場合、iframe内の検索結果のHTMLのbody要素に、onloadで下記のようなJavaScriptを実行している。

onload="window.top.location='http://(親windowの検索結果のURL)#'+document.body.scrollHeight;"
これにより、親windowのURLの末尾にiframe内のコンテンツの高さを表すhashが付く。

そして検索結果のshow_afs_search.jsの方でlocation.hashを取得し、それをiframeの高さとして設定している。(0.01秒ごとに実行するという監視をしている。高さが取得できた後も。)
その結果、検索結果のコンテンツの内容に応じて検索結果をリサイズするというユーザーフレンドリーなユーザーインターフェイスを実現している。

試しに検索結果を表示した後、URLの最後に「#500」などと付けると検索結果の高さが変わる。これはFirefoxでも有効。


なぜIE以外ではiframe内のbodyのonloadのJavaScriptを付けないのだろう?Firefoxのセキュリティ等により無意味なんだろうか?


【回避策】
検索結果を表示するページのgoogleSearchXxxを設定しているJavaScriptに、下記のように高さを明示的に指定してあげる。
var var googleSearchFrameWidth = 800; //これは既存のコード
var googleSearchFrameHeight = 1210; //これを追加する
これにより、デフォルトの高さが1210pxになり、検索結果の下端が切れなくなる。

IEの場合、これでもリサイズが有効なので、この値は無視してリサイズしてくれると思う。

CakePHPの GetText (i18n)で複数形の翻訳をする

はまったのでメモ。最初からリファレンスや参考サイトを見ながらやれば書いてあったんだろうけど。


CakePHPで単数と複数で異なる翻訳を付けようとした。

__n(' person', ' people', $count);
poファイルも書いた。
msgid " person"
msgid_plural " people"
msgstr[0] "人"
msgstr[1] "人"

するとエラー(Warning)が表示された。
Warning (2): strlen() expects parameter 1 to be string, array given ...


該当箇所はCORE/cake/libs/i18n.phpのtranslate()。
if (is_array($trans)) {
if (isset($trans[$plurals])) {
$trans = $trans[$plurals];
}
}
if (strlen($trans)) {
$singular = $trans;
return $singular;
}
strlen()に配列を渡しているようだ。
でもその上で$transが配列の場合は文字列に入れ替えてるはず。(変数の使いまわしは良くないと思う。)それが効いてないのは「isset($trans[$plurals])」がfalseになっているということか。

この$pluralsをセットしているところを追うと、今回の場合は同じファイル内の__pluralGuess()によりセットしているようだ。
__pluralGuess()の中を追うと、$headerの中身で判断してるようだ。
if (strpos($header, "plurals=3")) {
if (strpos($header, "100!=11")) {
if (strpos($header, "10<=4")) {
return $n % 10 === 1 && $n % 100 !== 11 ? 0 : (略)

$headerを出力してみる。
nplurals=INTEGER;plural=EXPRESSION;

ん?さっきのif文の条件と似ても似つかない。
これは...poファイルで何か設定不足?

poファイルの最初の方を見てみる。
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
まさにこれだ。

こうしてみる。(キャッシュの削除も忘れずに。)
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

動いた!

以上。

2009年11月6日金曜日

PHPで Fatal Error時の状況を調べる方法

PHPでFatal Errorが発生すると以降の処理が実行されない。
かつ、try/catchやset_error_handler()ではFatal Errorを捕まえられない。
なので通常は、Fatal Errorが起きたときの詳しい状況はログに残らない。
(例えばエラーが起きたPHPのファイル名はログに残るが、それを呼び出した側のPHP名や、データの状況は分からない。)

しかし、register_shutdown_function()を使えばエラーが起きたときの状況をログに残せる。

register_shutdown_function('shutdownHandler');

function shutdownHandler(){
$error = error_get_last();
if ($error['type']) { //エラーの場合のみ
error_log(...); //必要な情報を書き出す。
}
}

ただしこれだとE_NOTICEとかでもひっかかってしまうので、Fatal Errorだけを拾いたい場合はtypeを指定する。
register_shutdown_function('shutdownHandler');

function shutdownHandler(){
$error = error_get_last();
if ($error['type'] == E_ERROR) {
error_log(...); //必要な情報を書き出す。
}
}
これだとExceptionが拾えない?


参考:
PHP の「エラー処理ハンドラ」「シャットダウンハンドラ」「例外処理ハンドラ」の挙動 - Web/DB プログラミング徹底解説
PHP: register_shutdown_function - Manual

JavaScriptで画像の大きさを縮小する方法

半分にするならこんな感じ。

<img src="test.jpg" alt="test" onload="this.width /= 2" />


最大値を決めてそれ以下になるようにするならこんな感じ。
<img src="test.jpg" alt="test" onload="this.width = Math.min(this.width, 100)" />


widthを変えれば勝手にheightも同じ比率で変わってくれるようだ。
Firefox3.5とIE7のみで確認。

CakePHPで用意されている layout

CakePHPでは、コントローラでlayoutを指定すれば任意のlayoutを使うことができる。
通常layoutは自分で用意するが(たいていはdefalut.ctp)、いくつか組み込みのlayoutが用意されている。
それぞれどんな出力になるか試してみた。

なお、以下のサンプルで使っているviewの中身は下記の通り。

test


1. $this->layout = 'ajax' の場合の出力結果
test
<!-- 1.7983s -->


2. $this->layout = 'flash' の場合の出力結果
(コントローラ内で$this->flash('foo', 'bar');をセット済み)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>foo</title>

<style><!--
P { text-align:center; font:bold 1.1em sans-serif }
A { color:#444; text-decoration:none }
A:HOVER { text-decoration: underline; color:#44E }
--></style>
</head>
<body>
<p><a href="/test/bar">foo</a></p>
</body>
</html><!-- 1.6536s -->
詳細はよく分からないが、何かFlashのために便利なものなのだろう、きっと。


その他にjs、xml、rssというディレクトリが用意されているがそのままでは使えない。マニュアルを参考に自分でlayoutを作るようだ。(RSSの場合はこちら(Layout :: Controller Code :: Creating an RSS feed with the RssHelper :: RSS :: Core Helpers :: The Manual :: 1.2 Collection :: The Cookbook)をコピーすればすむかも。


参考:Layouts :: Views :: Developing with CakePHP :: The Manual :: 1.2 Collection :: The Cookbook

Safariだけ img要素の onloadの動きが違う

動的にsrc属性の値を変えた時、かつ変更前と変更後のsrc属性の値が同じ場合にonloadが実行されるかどうかが違う。
IE(7)、Firefox(3.5)ではonloadが実行されたが、Safari(4)では実行されなかった。


テスト用コード

<img src="test.jpg" alt="test"
 onload="alert('Loaded!')"
 onclick="this.src = 'test.jpg'" />


load時に"Loaded!"が表示されるのは同じだが、クリックした時にsrc属性に同じ値をセットしたタイミングで、Safariだけはonloadが走らない。
src属性にセットする値が元の値と別ならば、Safariでもonloadが走る。

こんな感じならSafariでもonloadが走る。
<img src="test.jpg" alt="test"

 onload="alert('Loaded!')"

 onclick="this.src = 'test2.jpg'" />




Safari曰く、「変わってないからloadしてない、だからonloadではない」そうだ。(推測)

YUI AutoCompleteを使うコードの例

【CSS】
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.8.0r4/build/autocomplete/assets/skins/sam/autocomplete.css" />


【JavaScript】
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.8.0r4/build/yahoo-dom-event/yahoo-dom-event.js&2.8.0r4/build/animation/animation-min.js&2.8.0r4/build/datasource/datasource-min.js&2.8.0r4/build/autocomplete/autocomplete-min.js"></script>

<script type="text/javascript">
new YAHOO.widget.AutoComplete(
    "myInput",
    "myContainer",
    new YAHOO.util.LocalDataSource(["とうきょう", "とちぎ", "とやま"]),
    {useShadow: true} //AutoCompleteに影を表示
);
</script>


【HTML】
<div style="width:15em">
    <input id="myInput" type="text">
    <div id="myContainer"></div>
</div>


参考:YUI 2: AutoComplete

Rails 2.xで XMLサイトマップを生成する方法

Rails2.3で試した。
必要最低限の要素のみ。


config/routes.rb

map.connect "sitemap.xml", :controller => :test, :action => :sitemap


controllers/test_controller.rb
class TestController < ApplicationController
def sitemap
# サイトマップとして送信したいページのURL生成に必要な情報を取得する
@members = Member.find(:all)
@entries = Entry.find(:all)

  headers["Content-Type"] = "text/xml; charset=utf-8"

  respond_to do |format|
format.xml {render :layout => false}
end

end
end


views/test/sitemap.xml.builder
xml.instruct!
xml.urlset(:xmlns => "http://www.sitemaps.org/schemas/sitemap/0.9") do
# トップページ
xml.url do
xml.loc(url_for(:controller => :top, :only_path => false))
end
      
# 個別のページ
@members.each do |member|
xml.url do
xml.loc(url_for(:controller => :members, :action => :show, :id => member.id, :only_path => false))
end
end



end
「http://」から始まるフルなURLにするために「:only_path => false」を付けている。

状況によってはキャッシュさせた方が良いだろう。

検索エンジンへの通知はrobots.txt等で。(Google Webマスターツールとか使った方が安心感があるけど。)

robots.txt
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /

Sitemap: http://www.example.com/sitemap.xml


参考:
「はじめてのRuby on Rails2」著者サポートページ
Rails2.0でGoogleサイトマップとかのsitemap.xml作る - ひげろぐ
sitemaps.org - プロトコル

CSSフレームワーク YAMLと Highslide JSは相性が悪い

CSSフレームワークのYAMLとページ内ポップアップライブラリのHighslide JSを併用したら、IEで見た場合のみHighslideの表示がおかしくなった。

参考情報を探したが、ドイツ語しか見つからない...


結局解決していない?


具体的には、2カラムの右側(div#col3)にあるリンクをクリックしたらHighslideがポップアップするようにしたら、IEの場合だけ"Loading"の表示が画面の右の方にずれてしまう
場合によっては画面の外にまでずれてしまい、その時だけ横スクロールバーが出るような状況に。

Firebug Liteなどを使って調べていたら、どうやらLoadingのCSSプロパティleftの値がおかしいらしい。
Highslideは、Loadingの位置を計算で求めている。
その際に、offsetLeftを積み上げてleftを計算する。積み上げるというのはリンクからoffsetParentをたどっていき、そのoffsetLeftの合計を求める。(scrollLeftの考慮もしている。)
で、このoffset系のプロパティは、ブラウザによって挙動が違うらしい。

実際にlinkのleftの計算に使われるoffsetParentとoffsetLeftを表示させてみると、Firefox(3.5)とIE(7)ではかなり内容が違う。
まずFirefoxの方はたどるoffsetParentが少ない。すぐにBodyにたどりつく。offsetに関連しない要素は無視するようだ。
対してIEはoffsetに関連しない要素も全部拾う。何が関連するかの判断もFirefoxとはかなり違う。
さらに、IEではdiv#col3の子要素のoffsetLeftの値がおかしい
具体的には、div#col3の子要素は、正しいoffsetLeftの値に加えて、なぜかdiv#col3のoffsetLeftの値が加算されている。
これにより、LoadingのLeftがdiv#col3のoffsetLeft分だけ右にずれてしまうようだ。

ここで、上記の参考サイトを見て、div#col3のpositionプロパティを変えたらなんとなかるかなあと思い実験。
FirefoxのFirebugで調べたところ、div#col3はpositionを指定していないので、デフォルトのstaticのはず。
が、IEでFirebug Liteで調べたところ、なぜかrelativeになっている。IE用のパッチのCSSの影響?
そこで、div#col3のpositionにstaticを指定したら、Loadingがリンクと同じ位置に表示されるようになった。
(たぶん)position:staticの場合、offsetParentの対象でなくなるようだ。これにより、div#col3のoffsetLeftが重複して加算されることを回避できた。

2009年11月3日火曜日

Google Analyticsの携帯からのアクセス解析で出来ることと出来ないこと

下記はGoogle Analyticsの携帯向け(公式Beta版)による統計を元にしているが、説明については推測で補完している部分もあるので注意。


主な解析出来ること

  • アクセス元(携帯の基地局?)の地図上へのマッピング表示
  • サイト滞在時間
  • 携帯キャリア
    • 「ユーザー→PC環境→ブラウザ」で。ただしauは「UP.Browser」、SoftBankは主に「NetFront」になる
    • あるいは、「ユーザー→PC環境→OS」で。この場合、SoftBankは「(not set)」になるようだ
    • あるいは、「ユーザー→ネットワーク環境→利用ネットワーク」から見る?
  • 参照サイト(ただし、ほとんどのdocomoはリファラを吐かないので対象外)
  • 検索エンジン(同上)
  • 検索キーワード(同上)
  • アクセス数順のURL

主な解析出来ないこと
  • ユーザーの言語
  • 画面の色
  • 画面の解像度
  • Flash Playerのバージョン
  • ページのタイトル(Google Botを使って取得してくれればいいのに)


ほとんどのdocomoがリファラを吐かないので参照サイトや検索キーワードが取れないのは他の解析サービスと同じ。

画面の解像度やFlash Playerのバージョン等が取れないのが、日本の携帯事情を隅々まで飲み込んでいる「うごくひと2」などと違うところか。

ブログ アーカイブ

tags