Abstract.TimedObserverクラス

【抜粋】一部省略
Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
(省略)
  },
(省略)
  registerCallback: function(element) {
(省略)
  }
}

Abstract名前空間に作成されています。名前の通り抽象クラスです。継承することで、なんらかの値を指定間隔で監視して、変化があった場合に、指定した関数を実行させるサブクラスを作成することができます。サブクラスでは、監視する値を返却するgetValueメソッドを実装する必要があります。

後述Form.Element.Observerクラス、Form.Observerクラスで継承していますが、プログラマが独自にクラスを作成することもできます。

監視対象はHTML要素に関するものを想定していますが、使い方によっては変数やインスタンスメソッドの返却値等を監視することもできます。

例は最後にまとめます。

initializeメソッド

【抜粋】
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

引数elementは監視する要素(ID指定可)、引数frequencyは監視間隔(秒数)、引数callbackは監視する値が変化した際に実行する関数が指定されます。callback関数には実行時に監視要素と現在値が引数として渡されます。

監視対象の現在値を、サブクラスで実装するgetValueメソッドで取得して、lastValueプロパティに格納しています。

その後、タイマーイベントに関数を登録するregisterCallbackメソッドを呼び出しています。

registerCallbackメソッド

【抜粋】
  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

setInterval関数により、onTimerEventメソッドをfrequencyプロパティに指定された間隔(秒数)で繰り返し実行します。イベントへのメソッド登録時にbindメソッド*1を使用するのはおなじみです。

onTimerEventメソッド

【抜粋】
  onTimerEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }

監視対象の現在値を、サブクラスで実装するgetValueメソッドで取得して、lastValueプロパティと比較します。変化があったら、callbackプロパティに登録された関数を、elementプロパティと現在値を引数にして呼び出します。その後、lastValueプロパティに現在値を格納します。

2006/09/15追記。このままだと値が配列だった場合、内容を検討せずにオブジェクト比較します。これは以降で問題が生じます。改修案を「Abstract.TimedObserver.onTimerEventメソッド改修検討 - Backstage of theater.js」に記載しました。

getValueメソッド(抽象メソッド扱い)

getValueメソッドはAbstract.TimedObserverクラスを継承するサブクラスで実装する必要があります。通常、elementプロパティを利用して、要素に関する値を返却させます。ですが、elementプロパティを利用せずに、要素とは関係のない値を返却させて使うこともできます。

【例】
<html>
<head>
<title></title>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script>
var gtest = 0;

var Observer1 = Class.create();
Observer1.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return this.element.innerHTML;
  }
});

var Observer2 = Class.create();
Observer2.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return this.element.style.backgroundColor;
  }
});

var Observer3 = Class.create();
Observer3.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return gtest;
  }
});

function init(){
  new Observer1('div', 1, test);
  new Observer2('div', 1, test);
  new Observer3(null, 1, test);
}

function test(element, value){
  var str = "";
  if(element){
    str += "elememt ID:" + element.id;
  }else{
    str += "parameter "
  }
  str += " changed to '" + value + "'";
  Element.update('test', str);
}
</script>
</head>
<body onload="init();">

<div id="div">default</div>

<button onclick="Element.update('div', (new Date()).toString());">
update div</button>

<button onclick="Element.setStyle('div',
 {'background-color':'rgb(255, 255, ' + Math.floor(Math.random()*255) + ')'});">
cahnge color of div</button>

<button onclick="gtest++;">increment parametar</button>

<div id="test">default</div>
</body>
</html>

自作したObserver1クラスのインスタンスは、idが'div'である要素のinnerHTMLを1秒間隔で監視しています。変更を検知するとメッセージを表示します。「update div」ボタンは、id:'div'要素のinnerHTMLを、現在時刻文字列に変更します。

Observer2クラスのインスタンスは、idが'div'である要素のsyle.backgroundColorプロパティを1秒間隔で監視しています。変更を検知するとメッセージを表示します。「cahnge color of div」ボタンは、id:'div'要素の背景色をランダムに変えます(黄色が濃くなったり、薄くなったりする)。

Observer3クラスのインスタンスは、要素ではなく、グローバル変数gtestを1秒間隔で監視します。変更を検知すると、メッセージを表示します。インスタンス生成時、第一引数(監視対象要素)はnullにしています。「increment parametar」ボタンは、gtestをインクリメントします。これはちょっとイレギュラーな使い方です^^; ご参考程度に。

それぞれボタンを押すと、だいたいちょっと間があってメッセージが表示されると思います。この間を短くしたければ、インスタンス生成時の第二引数を短く(「0.5」等)にします。ただ、あまり短くすると、リソースを圧迫してしまう可能性があるので注意が必要です。

*1:Functionクラス拡張メソッド