2011年04月22日の日記です


「戻る」ボタン自由自在  2011-04-22 11:37:42  コンピュータ

WEB の CGI プログラミングの話。


単に WEB ページを見せるのではなく、わざわざ CGI を使用している場合は、状況によって表示する内容が刻々と変化することが多い。

この「刻々と」は、つまり「ユーザーの操作によって」の意味である。


ユーザーの操作は積み重なっていくが、データが少ないうちは、URL にデータを入れ込んで置けばよい。


ところが、データが多いとそれは無理となる。

この場合、サーバ側にデータベースなどを持ち、URL には、そのデータベースのアクセスに必要な最小限のデータだけを持っておくことになる。



この場合、問題となるのは、ブラウザの「戻る」ボタンを押されることだ。

データベースには常に最新のデータしか保持されていないのに、ユーザーが閲覧するページには古いデータが残っている状態となり、データの不整合を起こすのだ。


多くの CGI プログラマーが、頭を悩ましている問題である。




解決策の1は、戻るボタンを押したらエラーを出すことである。

データベースには、常に更新されるチェックデータを用意しておき、URL の一部としてこのチェックデータを入れておく。

最新のページからアクセスがあった場合は、URL に入っているチェックデータと、データベースのチェックデータが一致する。

しかし、一致しなかった場合は「エラー」とする。

これで、戻るボタンが押されることでデータの不整合がおきる、と言う問題を回避できる。

めでたしめでたし。


…この方法の問題点は、WEB ページにとって「お客様」であるユーザーに、非常な不便を強いることだ。

ブラウザの「戻る」ボタンは、非常に使われる頻度の高い機能である。

使わないつもりでいても、うっかり押してしまうこともある。

そして、エラーが表示されるのだ。


これはページを見てくれる人を追い返すには十分。

最悪の解決方法である。




解決策の2は、戻るボタンを消してしまう方法。

JavaScript を使用して新しい window を開く時に、ツールバーを「表示しない」ようにすれば、戻るボタンは消えてしまう。

消えれば押されることもないわけで、解決。


…これも、解決策1とたいして変わらない。

うっかりエラーがでて最初からやり直し、という最悪の事態にはならないが、「戻る」ボタンが使えないという不便を強いることに違いはない。

それどころか、「戻る」以外の機能も全部巻き添えで使えなくしているわけで、ユーザーに大変な不便を強いることになる。




解決策3は、Javascript で戻るボタンを無効化する方法。


<script>

history.forward();

</script>


を全てのページのヘッダ内に書いておくだけ。


これは、「履歴の次のページにすすむ」というプログラムである。

通常のアクセスであれば、これが実行されても「次のページ」は存在しないため、何も起こらない。


しかし、戻るボタンが押されて開かれたページでは、次のページが存在するため、より新しいページに進む。つまり、最新ページが常に表示された状態で固定される。


結果、戻るボタンは表示されているが、機能的には無効化されている。

ユーザーの不便は最小限かもしれないが、「戻る」という重要な機能を失っていることに変わりはない。





以上3つの解決策が「悪い解決方法」なのは、戻るボタンの歴史的意義を理解していないためである。


WWW 開発以前にも、ハイパーテキストは存在した

そして、ハイパーテキスト研究の重要問題の一つが、「迷子問題」と呼ばれるものだった。


ハイパーリンクは、テキストに対する説明や、関連話題として示される。

そこで興味を持ってリンクを辿るうち、元のテキストに帰る方法がわからなくなる。

…単純に言って、これが迷子問題である。


これに対する初期の解決策が、「戻る」ボタンであり、現在ではハイパーテキストの閲覧に必須と言ってよいほどの機能となっている。


だから、戻るボタンの機能を無効化してはいけないのだ。

無効化することは、つまり「テキスト閲覧お断り」を表明しているに近い。

閲覧して欲しいからページを作っているはずなのに、本末転倒である。




昔を懐かしむわけではないが、携帯電話向けに策定された HDML では、「戻る」ボタンに自由な機能を割り振れるようになっていた。


先に書いたように、「戻る」ボタンはハイパーテキストの閲覧に必須の機能であり、無闇な書き換えは推奨されない。

しかしまた、HDML では「戻る」は、直前に閲覧したページに戻る事を意味しなかった。

複数のページを「機能的なひとまとまり」として定義する方法があったため、「戻る」ことにより機能を途中で抜け出したり(機能に入る前のページに戻る。時には数ページ前となる)、正常に完了した機能のページは「戻る」の対象から外されたりした(完了した機能内のページには戻れず、その機能の前のページに戻る)。


つまり、HDMLにおいては「戻る」とは、直前のページではなく、ユーザーが「戻りたい」と思う場所に適切に戻るための機能だったのだ。

そして、この方針に従う限り、「戻る」ボタンによって新たなページを読み込むことも許容されていた。



戻るボタンの機能を書き換えるとき、機能を「無効化する」ような書き換えを行うのではなく、WEBページ提供側に対しても、閲覧側に対しても許容できる箇所に戻るように気を使わなくてはならない。




というわけで、今回仕事で必要だったので開発したテクニック。

戻るボタンの機能を書き換え、指定された特定ページに進ませる。



注意点として、先に書いたように、閲覧者が違和感を感じないようなページ遷移にすること。

少なくとも、戻る機能を書き換えたことで袋小路に迷い込んだり、来たこともないページに飛ばされたりするのはよくない。



仕掛けとしては、解決策3で示したスクリプトの拡張にすぎない。

解決策3では、「次のページがある(戻るボタンで戻った)ときは、強制的に次のページへ」進むようにしていた。


これを、「戻るボタンで戻ったときは、最新のページで指定された URL へ」進むようにする、というだけ。

とはいえ、単純ではないので、まずは説明から。



まず、問題なのは「最新のページで指定された URL 」という部分。

基本的に、JavaScript はページ(ドキュメント)の中で完結して動作するため、変数などでデータを受け渡すことはできない。ページ遷移が行われれば、変数が破棄されるのだ。

そのため、最新のページで指定された、という「指定 URL データ」をどう渡すのかが問題となる。


とりえる方法は幾つかあるが、ここでは window.name を使う。

window.name は、ドキュメントの入れ物である「ウィンドウ」の特定に使われるものであり、ページ遷移してもデータが破棄されない。

これは、ページ間のデータ受け渡しに使用できる。


つまり、最新ページでは、読み込まれた際に window.name に、「指定 URL」を書き込む。

「戻るボタン」で戻られたことを検出した際には、この指定 URL に飛ぶようにプログラムを書く。

これで、「戻るボタンを押したら指定 URL に進んだ」ことになり、目的は達成できる。



しかしここで別の問題が発生する。「戻るボタン」で戻られたことは、どうやって検出するか?


JavaScript にできることは、「ページを表示する際にプログラムを実行する」ことだけで、その表示理由が、読み込まれたからなのか、戻ってきたからなのかは判らない。


ここでもまた、window.name が活躍する。

ページが読み込まれる毎に増加する値を JavaScript 内に用意しておき、プログラム実行の最後に、window.name の中にも書き込むのだ。


window.name への書込みよりも以前に、この値を比較すれば、プログラムの実行理由が判る。


・JavaScript 内部の値よりも、window.name の値のほうが古い

 →ページは新たに読み込まれ、表示された。

・JavaScript 内部の値よりも、window.name の値のほうが新しい

 →「戻る」ボタンによりページが表示された。

 

単純に増加していればよいので、unixtime でも十分だし、アクセスカウンタがあるならその値を利用しても良い。



基本戦略としては、以上で終わり。非常に単純な話だ。

プログラム的には、window.name は1つしかなく、書き込みたいデータは2つあるので、文字列の連結・分割が必要となる。

カウンタは数値、url は文字列だが…両者とも「空白」(ASCII 20h)は含まない、という決まりがあるので、空白で分割できるようにするのが、おそらく一番簡単だろう。


プログラムは以下のようになる。

(CGI 側で、$time $backurl の部分に、適切な値を埋め込んで JavaScript プログラムを生成すること。

 $time は unixtime 、$backurl は、戻るボタンが押されたときに進みたい URL が設定されている)


var p = window.name.split(' ');

if(p[0]>=$time) location.href=p[1];

window.name="$time $backurl";


たったの3行だ。解決策3の中央の1行の代わりに入れること。

(つまり、script タグで囲んでヘッダ内部に入れること)



実は、これだけでは FireFox で動作しない。

FireFox では、「戻る」ボタンで戻ってきた際には、JavaScript が動作しないためだ。


JavaScript が動作しない理由は、「最初に表示された時に JavaScript によって変化したデータを保持している」ためである。

再表示の際には、すでに JavaScript プログラム適用済みのデータを表示して高速化しているのだ。


しかし、onUnload という機能が設定されている場合は、この限りではない。

onUnload は、ページの表示を終了する時に JavaScript を動作させるための仕組みである。

この機能が設定されているということは、ページのデータは「再表示するのに適切でない」可能性があるため、初期状態のページから再度 JavaScript を起動させる。


というわけで、FireFox 対策に、次の1行を挿入する。


window.onunload = function(){}



以上の合計4行で、事実上「戻る」ボタンの機能を再定義したことになる。




window.name は、便利なので様々な Hack で使用される。

そのため上のテクニックが利用できない場合には、代替手段として以下のような方法もとれるだろう。


・Cookie を利用する。

 ページ間のデータ通信さえできればよいので、Cookie を使ってデータを保存しても良い。

 ただし、セキュリティのためにブラウザが Cookie を受け取らない設定になっていたり、プログラムがガジェットとして Frame 内で動作したりする場合に使用出来ないこともある。

 また、JavaScript で Cookie を扱うのは「可能だが結構面倒」なので、プログラムが無駄に長い。


var p = document.cookie.split(';');

var v;

for(var i in p){

  if(p[i].replace(/^\s+/,'').substr(0,2) == 'c='){

    v = p[i].replace(/^\s+/,'').substr(2,999).split(' ',2);

  }

}

if(v[0]>=$time) location.href=v[1];

document.cookie="c=$time $backurl";



・webStorage を利用する。

 webStorage は、新時代の Cookie である。

 Cookie の欠点がいろいろと解消されている一方、使用できるブラウザが限定される。


var p = sessionStorage.c;

if(sessionStorage.c>=$time) location.href=sessionStorage.u;

sessionStorage.c=$time;

sessionStorage.u="$backurl";



自分は仕事の都合でこの方法を編み出したのだが…仕事上の制約でいろいろ使えない機能もあったので、代替手段も一緒に開発することになってしまった。


基本戦略さえ理解してしまえば、他にも代替手段はあるのかもしれないが、自分は3つの方法を編み出した時点で「仕事上問題がなく使える技術になった」ので、ここで終了となった。





以上。

せっかく考えた技術なので、誰にも教えないで内緒で使いたいところだが、どうせ JavaScript なので、ソースを見られてしまえば何をやっているか一目瞭然なので公開した。





以上の話、3月11日の日記に書こうと思って準備中だった原稿である。


その後、なんとなくアップロードするタイミングを失っていたのだが、今思い出してファイルを見直したら、書き終わっていたようなのでアップする。

推敲中だったと思うのでどこかおかしいかもしれないが、何を書き直そうとしていたのかも忘れた。



地震直前に書いていた、なんてことは忘れていたのだが、ファイルのタイムスタンプを見たら、作成が3/11 13:00:13 、最終更新が 3/11 14:13:05だった。


閲覧する方にはどうでもよい話だが、僕にとっては妙に心に残ったので、ここに記しておく。


後日追記

デモページを作ってみました。


戻るボタンの機能を書き換えているところを、実際に体験できます。





同じテーマの日記(最近の一覧)

コンピュータ

関連ページ

「戻る」ボタンの機能変更

別年同日の日記

03年 SONY製品

05年 回線は来たけれど

13年 アンパンマンミュージアム

18年 3度目のキッザニア

18年 おわび

19年 次女の誕生日

20年 次女の誕生日

24年 最近遊んでいるゲーム


申し訳ありませんが、現在意見投稿をできない状態にしています


戻る
トップページへ

-- share --

0008

-- follow --




- Reverse Link -