この記事の反省をふまえた続編もあります。この記事で私の書き換えたソースは、非常に無駄な事をしていますので、この記事だけだと恥ずかしいから、そちらもお読み頂ければ幸いです。
なんでもかんでもオブジェクト化すればいいってもんじゃないな。反省!
経過時間を計算してくれるタイマー
マスクドライダー17号さんが、彼のブログで面白いことをやっていて、最初にそのページを開いてからカウントダウンをはじめて、24時間経過したかどうか、残り時間を教えてくれるというスクリプトを走らせています。
その記事によると「教えて!gooにて回答されていた、babu_babooさんのScriptを参考にしました」ということで、babu_babu_babooさんのスクリプトをアレンジしたそうです。
教えて!gooの内容は、期限が来たかどうかカウントしたいだけなので、そのスクリプトは必要な目的を達成しています。
現在、マスクドライダー17号さんが使っているスクリプトは、おそらく24時間で正しく止まると思います。(止まるという表現は正確でないですがわかりやすく言っています)ただ、それを彼のブログで使う場合は不足している機能があります。それは、リセット機能です。
クッキーにデータが残っている
そのスクリプトは、クッキー(cookie)を使って、実現されています。ブラウザを閉じても、最初の時間をクッキーが記憶しているので、もう一度ブログにアクセスした時に、最初の時間からの残りを計算できます。そして、残り時間がなくなると「24時間以上経過しました!!」と表示して終了します。
カウントダウンが1度だけなら、今のままで、まったく問題ないのですが、マスクドライダー17号さんの場合は、そのタイマーを24時間経過する度に、リセットして使いまわしたいのだと思います。でも、今のままだと、最初の24時間が過ぎると「24時間以上経過しました!!」と表示されて、そのまま動かなくなります。その後、もう一度使おうとしても、24時間たってしまったので動きません。
繰り返しますが、教えて!gooの場合は、これで目的を達成出来ているので問題ありません。
現在、マスクドライダー17号さんは、クッキーの有効期限を10日にしているので、止まってから、さらに9日経てば、またスクリプトは使えるようになりますが、かといって、有効期限を短くすると、ブログを開いたときにスクリプトがリセットされてしまい、24時間経ったのかどうか、わからなくなります。ですから「24時間以上経過しました!!」という表示を読むまでは、リセットしたくありません。読み終わったら、ボタンを押して、リセット(カウントダウンの再スタート)をさせるのが、いちばんいいと思います。
元のソースコード
出来れば、動いている今のソースコードをそのまま流用したいです。ありがたいことに、機能ごとに、細かく関数に分けてくれています。この中のsetCookie関数を使えばクッキーはリセット出来ます。
<style type="text/css"> p#mess{ font-size:200%; } </style> <p id="mess">次回</p> <script type="text/javascript"> <!-- (function (doc) { function addDay ( day, date ) { if( 'number' !== typeof day ) day = 0; if( 'object' !== typeof date ) date = new Date; date.setDate( date.getDate() + day ); return date; } function getCookie ( name ) { name = encodeURIComponent( name ).replace( /([.*()]) /g, '\\$1' ); var value = doc.cookie.match( RegExp( name + '\\s*=\\s*(.*?)(?:[\\s;,]|$)' ) ); return value ? decodeURIComponent( value[1] ): ''; } function setCookie ( name, value, day, path, domain ) { return doc.cookie = encodeURIComponent (name) + '=' + encodeURIComponent (value) + '; ' + 'expires=' + ( addDay(day) ).toUTCString () + '; ' + (path ? 'path=' + encodeURI (path) + '; ': '') + (domain ? 'domain=' + encodeURI (domain) + '; ': ''); } function padding ( n ) { return n < 10 ? '0' + n: n; } var COOKIE_NAME = 'myCount'; var LIMIT_DAY = 1; var SHELF_LIFE = 10; var TIMEOUT_MESS = '24時間以上経過しました!!'; var node = doc.getElementById( 'mess' ); var targetDay = parseInt( getCookie( COOKIE_NAME ) ); if( !targetDay ) { targetDay = addDay( LIMIT_DAY ).getTime(); setCookie( COOKIE_NAME, targetDay + '', SHELF_LIFE ); } (function () { var text = '次回まで約'; var s = (targetDay - (new Date).getTime()) / 1000 |0; if (s < 0) text = TIMEOUT_MESS; else { text += padding( s % 86400 / 3600 |0) + '時間' + padding( s % 3600 / 60 |0) + '分' + padding( s % 60 |0) + '秒です。'; setTimeout (arguments.callee, 1000); } node.firstChild.nodeValue = text; })(); })(this.document); //--> </script>
しかし、弱ったことにソースは無名関数で括られているので、setCookie関数を使えません。無名関数の外に出してもいいのですが、この際、オブジェクト(クラス)化して、無名関数内のfunctionを、クラスのprototypeに移動させる事にします。
オブジェクト指向で書き換えたコード
関数の中身は、ほとんどいじっていませんし、スクリプトのロジックは一緒です。
<style type="text/css"> p#mess{ font-size:200%; } </style> <p id="mess"></p> タイマーを<input type="button" onclick="countdown.reset();" value="リセット" /> <script type="text/javascript"> <!-- var timerCookie=function(id){ this.id=id; this.COOKIE_NAME = 'myCount'+id; this.LIMIT_DAY = 1; this.SHELF_LIFE = 10; this.TIMEOUT_MESS = '24時間以上経過しました!!'; this.targetDay = parseInt( this.getCookie( this.COOKIE_NAME ) ); if( !this.targetDay ) { this.reset(); }else{ this.load(); } }; timerCookie.prototype.addDay=function( day, date ){ if( 'number' !== typeof day ) day = 0; if( 'object' !== typeof date ) date = new Date; date.setDate( date.getDate() + day ); return date; }; timerCookie.prototype.getCookie=function( name ){ name = encodeURIComponent( name ).replace( /([.*()]) /g, '\\$1' ); var value = document.cookie.match( RegExp( name + '\\s*=\\s*(.*?)(?:[\\s;,]|$)' ) ); return value ? decodeURIComponent( value[1] ): ''; }; timerCookie.prototype.setCookie=function( name, value, day, path, domain ){ return document.cookie = encodeURIComponent (name) + '=' + encodeURIComponent (value) + '; ' + 'expires=' + ( this.addDay(day) ).toUTCString () + '; ' + (path ? 'path=' + encodeURI (path) + '; ': '') + (domain ? 'domain=' + encodeURI (domain) + '; ': ''); }; timerCookie.prototype.padding=function( n ){ return n < 10 ? '0' + n: n; } timerCookie.prototype.reset=function(){ this.targetDay = this.addDay( this.LIMIT_DAY ).getTime(); this.setCookie( this.COOKIE_NAME, this.targetDay + '', this.SHELF_LIFE ); this.load(); }; timerCookie.prototype.load=function () { var text = '次回まで約'; var s = (this.targetDay - (new Date).getTime()) / 1000 |0; if (s < 0) text = this.TIMEOUT_MESS; else { text += this.padding( s % 86400 / 3600 |0) + '時間' + this.padding( s % 3600 / 60 |0) + '分' + this.padding( s % 60 |0) + '秒です。'; (function(obj){ setTimeout (function(){ obj.load(); }, 1000); })(this); } document.getElementById( this.id ).innerHTML = text; }; var countdown=new timerCookie('mess'); //--> </script>
これで、setCookie関数を使えるようになりました。setCookie関数を使って作ったのが、上のprototypeにあるreset関数です。元のコードは本来、例文として作られたものですし、1回しか使わないという前提に立ったものです。そういう場合は無名関数で括ってしまうのが便利ですし、使いまわす時はオブジェクト化が便利だと思います。
ちなみに、オブジェクトは表示領域のidを引数にとるようにしています。これで、同じページで時間の違う、複数のタイマーを走らせることも出来ます。
上のソースコードは目的どおりに動くことを24時間たって確認しました。機能的には今のまま使って問題ありません。
ただ、babu_babu_babooさんから、上のソースについていくつかご指摘を頂きました。その内容は、ここのコメント欄にありますが次のような事です。それに沿って、上のソースを修正しようかと思ったのですが、元のソースを残しておかないと、指摘された内容が分からなくなると思うので、このままにしておきます。
- 指摘された内容
- オブジェクト指向なら、始まりは大文字で始めるのが通例
- オブジェクトを作るときに、new を使わずに、var obj = TimeCookie.create ();のような形にした方がよい
- prototypeに入れるべき関数とは?
この記事の反省をふまえた続編もあります。この記事で私の書き換えたソースは、非常に無駄な事をしていますので、この記事だけだと恥ずかしいから、そちらもお読み頂ければ幸いです。
なんでもかんでもオブジェクト化すればいいってもんじゃないな。反省!