ページ遷移時に情報を保持(cookie使用)
ページ遷移戻りでcssやJavascriptの変数を保持する方法
ページ遷移後元に戻って来た時に前の状態を保持していて欲しいことは 良くあります。
簡単なブログであっても、ちょっとした
cssのstyleやJavascriptの大域変数ひとつ
くらいは保持してもらいたいものです。
このサンプルはcookieを用いて情報を記憶します。
別途hidden要素を用いたやり方を本記事の
hiddenに値を記憶させる方法に
置いてあります。
下の例では左の2つの要素が通常の取り扱いで、右の2つの要素が情報保持を 行っているものです。[test]ボタンで色と値が変わります。他ページに遷移後 戻ってくると、右の2つが状態を保っていることが分かります。 左のカウンタは表示は環境によっては状態を保つ場合も ありますが変数は初期値に戻ってしまっています。
AAAA BBBB
コードは次のようになっています。
情報記憶に関連しない部分はグレイアウトしてあります。
<p> <span id=span1>AAAA</span> <input type=text id=text1> <span id=span2>BBBB</span> <input type=text id=text2> </p> <script type="text/javascript"> function $ELM(id){return document.getElementById(id);} // 簡素化関数 var color = "black"; var count = 0; var pageMemory = { // 保存される情報 "id":"b336", // cookieのid "count":0, "col":"black" }; function test(){ $ELM('text1').value = ++count; // 非保存のカウンタ color = nextColor(color); // 非保存の色 $ELM('span1').style.color= color; $ELM('text2').value = ++pageMemory.count; pageMemory.col = nextColor(pageMemory.col);// 保存されるカウンタ $ELM('span2').style.color= pageMemory.col; } function nextColor(col){ // 色変遷関数 if( col=="red") return "blue"; else if( col=="blue") return "brown"; else if( col=="brown" ) return "green"; return "red"; } function restorePage(){ // 戻り時の情報セット $ELM('text2').value = pageMemory.count; $ELM('span2').style.color= pageMemory.col; } </script> <input type=button value="test" onClick='test()'> <script type="text/javascript" src="../../../js/json2.js"></script> <script type="text/javascript" src="../../../js/hiPageMemoryCookie.js"></script>
短期のcookieを用いてJavascriptの特定objectを自動保存するようにしました。
cookieを利用しますが、アプリケーションレベルではcookieアクセスは
意識する必要はありません。$ELM()はアンチジュゲム関数で、前の方で定義してあります。
pageMemoryという特別な名前のオブジェクトが cookieにページ遷移時に短期保存され、ページが戻った時に元の 値になった上、restorePageという特別な名前 の関数が呼び出されるようになっています。
pageMemoryオブジェクトは文字列idフィールドを持たなければなりません。この 値がcookieの名前となります。
pageMemory.not_changedを作成し"true"をセットすれば、 場合cookieのセットをしないことになっています。
オブジェクトはJSON化してcookieに保存しますが、そのエンコード/デコードにはjson2.jsを使っています。 json2.jsは http://www.json.org のJavascript/json2.jsからダウンロードできます。
hiPageMemoryCookie.jsは次のようになっています。
var hiPageMemories=["pageMemory"]; function hiCookieObjEncode(obj){ // cookie用のJSONのコンパクトなエンコード var json = JSON.stringify(obj,null); var uri = encodeURIComponent(json);// "[]{}<>|"は"%5B%5D%7B%7D%3C%3E%7C" var text = uri.replace(/%22%3A%22/g,'<').replace(/%22%2C%22/g,'>')//":" "," .replace(/%22%3A/g,'#').replace(/%2C%22/g,':') // ": ," .replace(/%7B%22/g,'{').replace(/%7D/g,'}') .replace(/%5B/g,'[').replace(/%5D/g,']') .replace(/%22/g,'|').replace(/%2C/g,'/'); return text; } function hiCookieObjDecode(text){ // cookie用のJSONのコンパクトなデコード var uri= text.replace(/\</g,"%22%3A%22").replace(/\>/g,"%22%2C%22") .replace(/\#/g,"%22%3A").replace(/\:/g,"%2C%22") // ":と," .replace(/\{/g,'%7B%22').replace(/\}/g,'%7D') .replace(/\[/g,"%5B").replace(/\]/g,"%5D") .replace(/\|/g,"%22").replace(/\//g,"%2C"); var json = decodeURIComponent(uri); try{ return JSON.parse(json); } catch(e){} return null; } function hiReadCookieObj(key){ var cookies= document.cookie; var key = pageMemory.id+"="; var pos=-1; while( (pos=cookies.indexOf(key)) != -1 ){ if( pos==0 || cookies.charAt(pos-1)==' ' || cookies.charAt(pos-1)==';' ){ var start= pos+key.length; var end = cookies.indexOf(';',start); if( end==-1 ) end= cookies.length; return hiCookieObjDecode(cookies.substring(start,end)); } } return null; } function hiGetMemory(){ var found=false; for(var i=0;i<hiPageMemories.length;++i){ var pageMem= window[hiPageMemories[i]]; if( pageMem != undefined // pageMemoryがある && pageMem.id!=undefined ){ // pageMemory.idが値を持つ var obj= hiReadCookieObj(pageMemory.id); if( obj !=null && obj.id !=undefined && obj.id == pageMemory.id ){ window[hiPageMemories[i]] = obj; found= true; } } } if( typeof window.restorePage != "undefined" ) restorePage(found); } function hiSetMemory(){ if( typeof window.onPageOut != "undefined" ) onPageOut(); for(var i=0;i<hiPageMemories.length;++i){ var pageMem= window[hiPageMemories[i]]; if( pageMem != undefined // pageMemoryがある && pageMem.id!=undefined ){ // pageMemory.idが値を持つ if( pageMem.not_changed==undefined || !pageMem.not_changed ){ document.cookie= pageMem.id+"="+hiCookieObjEncode(pageMemory); } } } } try{ window.addEventListener("load",hiGetMemory,false); window.addEventListener("unload",hiSetMemory,false); } catch(e){ window.attachEvent("onload",hiGetMemory); window.attachEvent("onunload",hiSetMemory); }
仕様:
ページから出るときに固定名の大域変数pageMemoryをjson化しcookieにセット、
ページに入る時にcookieを読みpageMemoryに値をセットします。
cookieを書く条件はpageMemory.idフィールドが存在することと、
pageMemory.not_changedフィールドが無いかfalseであることです。
読み込みの条件はpageMemory.idフィールドがあることです。
読み込み時cookieが無い場合はpageMemoryの値は初期値のままです。
ページに入る時は固定名のrestorePage()関数を呼び出します。restoreMemory
は引数を一つもち、cookieから情報を読み取った場合true、読み取れなかった
場合はfalseが設定されます。この情報が不要の場合利用者は引数を書く必要は
ありません。
関数onPageOut()が存在する場合、ページから出る時、
cookieへの書き込み前に呼ばれます。この関数でpageMemoryにセットした値
は反映されます。ただし、この関数の呼び出し時にウィンドウの各要素(inputなど)
の状態は不定です。
なお、記憶する大域変数は基本的にはpageMemory固定ですが、 文字列の配列hiPageMemoriesに指定することにより、別の複数の変数を 取り扱う形に変更することも可能です。
<script type="text/javascript" src="../../../js/hiPageMemoryCookie.js"></script> hiPageMemories=["myPageMemory1","myPageMemory2"]; // 記憶する変数を変更
json2.jsを使用していますので、json2.jsを宣言しておく必要があります。
<script type="text/javascript" src="../../../js/json2.js"></script> <script type="text/javascript" src="../../../js/hiPageMemoryCookie.js"></script>
コードの入手:
だんだんと大がかりになり、コピペレベルではなくなりましたので、
ダウンロード用のファイルを用意しました。
hiPageMemoryCookie.js
この記述は
[万象酔歩目次]でも実際に利用しています。リストの昇順/降順などを
切り替えた後別ページに移動し、戻ってくると切り替えた状態が
保持されています。
目次ページが読み込んでいるファイルはコメントや空白を取り払った
hiPageMemoryCookie.js (圧縮版)
です。
json2.jsは http://www.json.org のJavascript/json2.jsからダウンロードできます。
処理説明:
jsonエンコード/デコードはJSON.stringify(),JSON.parse()
で行っています。
jsonには
引用符とカンマ、コロンが多く含まれます。encodeURIComponent()を使うと
引用符、カンマ、コロンがそれぞれ%22,%3A,%2Cと3文字に変換されてしまうため、
極めて長い文字列となってしまいます。
JSONでは %22%3A%22(":") と %22%2C%22(",") というパターンが
多く出ます。そこで次のようなパターンで置き換えるようにしました。
URI表現 | 本来の文字 | cookie用表現 | 補足 |
%22%3A%22 | ":" | < | 要素が文字列の場合 "要素名":"値" となります | %22%2C%22 | "," | > | 要素が文字列の場合次の要素との境は "値","次の要素名" となります |
%22%3A | ": | # | 要素が文字列以外の場合 "要素名":値 となります。 要素がobjectの場合#{、。 | %2C%22 | ," | : | 要素が文字列以外の場合次の要素との境は 値,"次の要素名" となります |
%7B%22 | {" | { | オブジェクトの開始は {"要素名 となります。 |
%7D | } | } | オブジェクトの終わりは最後の要素が文字列か 数値かによって%22%7Dとなる場合とならない場合がありますが、全体の割合を 考えると大きくならないはずなので%22%7Dの対処はしません |
%22 | " | | | 他のパターンでもれる引用符です |
%2C | , | # | 他のパターンでもれるカンマです |
%5B | [ | [ | 配列の開始です |
%5D | [ | ] | 配列の終了です |
cookie設定時にcookie用表現に置き換え、読み取り時には元に戻します。
なおcookie変換はあくまでURIパターンに対して行っているので例えば日本語で一部が%5Dになり それがcookieとして"]"で登録されても、読みだし時には再び%5DになったうえでURIデコード されますので問題は発生しません。
変換に関する補助記述を本記事後部 URIパターンのモディファイに関する補足に載せました。
利用者関数の呼び出しをtry{}catch{}で囲んだのは、関数定義の存在そのもの のチェックがブラウザによっては不安定であるため、最も確実な形としたもの です。
最後のtry{}catch{}部はページ出入りのイベント取得関数の設定です。
cookieに関するメモ
当初jquery.cookieを用いていましたが、余り効率のよい作りではないため cookieの読み書きは自作に置き換えました。
cookieの書き込みには次の呼び出しを行います。
document.cookie= キー +"="+ encodeURIComponent(内容文字列);cookieに保存する文字列には空白や;などが許されませんがencodeURIComponent()で cookieに保存可能な文字列に置き換えることができます。
留意すべきことはこの「document.cookie=」はcookieの置き換えではなく追加を 意味していることです。document.cookieを参照すると複数のcookie内容が入って おり、非対称な不自然極まりないインターフェースとなっています。
cookieには幾つかのオプションを付加することができます。
オプションを付けない場合cookieの保存期間はブラウザが起動されている
期間となります。
オプションの付加は"; "の後ろに"オプション名=オプション"の文字列を追加します。 オプションには次のものがあります。
オプション形式 | 説明 |
---|---|
; max-age=10進値 | 秒単位で有効期間を指定 |
; expires=グリニッチ時刻 |
現在この形式は推奨されずmax-ageを使います。この指定が
推奨されない理由はフォーマットが余りに煩雑なためです。 有効期間を指定日時までとする。 有効期間を1年とする例 var nextYear = new Date(); nextYear.setFullYear(nextYear.getFullYear()+1); document.cookie= キー+"="+encodeURIComponent(値) +"; expires="+nextYear.toGMTString(); | ; path=パス文字列 | 指定したパスおよびその配下のページでcookieを取得できる
ようにします。無指定時は、当該ページと同じフォルダの下の
ページでのみ取得できます。 他のページでcookieを取得する必要がある場合に指定します。 危険な指定ですので、通常は行うべきではありません。 なお、文字コードのエンコードを行うと無効なので、 日本語のフォルダには対応しません。この時代に多国語対応 が出来ない仕様である点をみても、この指定の無意味さが 理解できると思います。 |
; domain=ドメイン文字列 | 他のドメインでもcookieを取得できるようにします。危険な 指定ですので通常は行うべきではありません。 |
; secure | さあ?なんでしょう |
cookieの読み取りにはdocument.cookie値を得ます。
var cookies= document.cookie; // クッキー文字列取得取得した文字列には複数のcookie情報が";"で区切られて「キー=cookie文字列値」の形で入っています。
通常;の後ろにはなぜか空白が付加されています。西洋の規格は空白に関して余りにも 雑なのです。
取得した文字列にはオプション値(expiresなど)は含まれません。
cookie文字列値はdecodeURIComponent()で元の文字列に戻します。
なお、大昔の記述ではencodeURIComponent()/decodeURIComponent()の代わりに escape()/unescape()を使うと書いてあるものがありますが、これらは現在では 推奨されません。
cookieのテストプログラムを以下に置きます。A=AAA,B=BBB,C=CCCをセットしています。 読み取るとmax-ageは表示されないことが分かります。有効期限を切れたcookieが いつ消去されるかはブラウザによってことなります。
プログラムは次のものです。読み取ったcookieをそのまま出すのはちょっと抵抗が あるので、数値およびtrue,falseを*に置き換えています。replaceを呼ばない ようにすればcookieの内容がそのまま見られます。
function cookieSet(){ alert("before:"+document.cookie.replace(/[0-9]|true|false/g,"*")); document.cookie="A=AAA; max-age=10"; document.cookie="B=BBB; max-age=120; secure"; document.cookie="C=CCC;" alert("after:"+document.cookie.replace(/[0-9]|true|false/g,"*")); } function cookieCheck(){ alert(document.cookie.replace(/[0-9]|true|false/g,"*")); } </script> <p><input type=button value='set cookie' onClick='cookieSet()'> <input type=button value='check cookie' onClick='cookieCheck()'></p>
なお、 hiPageMemoryCookie.jsではcookieに投入する文字列がJSONなので 単純なencodeURIComponent()/decodeURIComponent()ではなくより コンパクトな文字列に変換していましたが、cookie読み取りは通常 次のような関数となります。
function hiReadCookie(key){ var cookies= document.cookie; var key = pageMemory.id+"="; var pos=-1; while( (pos=cookies.indexOf(key)) != -1 ){ if( pos==0 || cookies.charAt(pos-1)==' ' || cookies.charAt(pos-1)==';' ){ var start= pos+key.length; var end = cookies.indexOf(';',start); if( end==-1 ) end= cookies.length; return decodeURIComponent(cookies.substring(start,end)); } } return ""; }
hiddenに値を保持させることもできます。単純なものであればこの方法でよいかも知れません。 ただし、環境によってはページ移動時に保持されたりされなかったりします。ページ遷移と画面要素 の保持に関しては明確な仕様はないようです。
AAAAx BBBBx
コードは次にものです。$ELM()とnextColor()はcookieの例のものと同じです。
<p> <span id=span1X>AAAAx</span> <input type=text id=text1X> <span id=span2X>BBBBx</span> <input type=text id=text2X value='0'> <input type=hidden id=h_color> </p> <script type="text/javascript"> var colorX = "black"; var countX = 0; function testX(){ $ELM('text1X').value = ++countX; // 非保存のカウンタ colorX = nextColor(colorX); // 非保存の色 $ELM('span1X').style.color= colorX; var num=Number($ELM('text2X').value); if( num==NaN ) num=0; $ELM('text2X').value = ++num; $ELM('h_color').value = nextColor($ELM('h_color').value); $ELM('span2X').style.color= $ELM('h_color').value; } function restoreX(){ $ELM('span2X').style.color= $ELM('h_color').value; } try{ window.addEventListener("load",restoreX,false); } catch(e){ window.attachEvent("onload",restoreX); } </script> <input type=button value="test" onClick='testX()'>
当初この形で記事を書いたのですが、環境によっては記憶されないことが 分かりcookieを使う確実な版に改めました。
URIエンコードされたJSONをコンパクトにするに当たり当初は次の変換を行っていました。
URI表現 | 本来の文字 | cookie用表現 |
%22 | " | | |
%3A | : | : |
%2C | , | # |
もう少し効率を上げたいことと、変換後の見やすさのため現在の変換法に変えました。
参考までに、JSON、URIエンコード、旧cookieエンコード、現cookieエンコードの例を載せます。
--- Javascriptオブジェクト var obj={ "elm_1":7, "elm_2":"ABあC", "struct":{ "elm_3":17, "elm_4":"XYZ" }, "array":[9,10,11] }; --- オブジェクトをJSON化 {"elm_1":7,"elm_2":"ABあC","struct":{"elm_3":17,"elm_4":"XYZ"},"array":[9,10,11]} --- JSONのURIエンコード %7B%22elm_1%22%3A7%2C%22elm_2%22%3A%22AB%E3%81%82C%22%2C%22struct%22%3A%7B%22elm_3%22%3A17%2C%22elm_4%22%3A%22XYZ%22%7D%2C%22array%22%3A%5B9%2C10%2C11%5D%7D --- 当初のcookieエンコード %7B|elm_1|:7#|elm_2|:|AB%E3%81%82C|#|struct|:%7B|elm_3|:17#|elm_4|:|XYZ|%7D#|array|:%5B9#10#11%5D%7D --- 現版でのcookieエンコード {elm_1#7:elm_2<AB%E3%81%82C>struct#{elm_3#17:elm_4<XYZ|}:array#[9/10/11]}
次の文字はURIエンコードスルーですのでcookieエンコードには用いてません。
! * ( ) . _次の文字はcookieに置くことはできませんのでcookieエンコードには用いていません。
; ,
| 固定リンク