Abstract.EventObserverクラス、Form.Element.EventObserverクラス改修検討
Form.Element.EventObserverクラスでは、Form.Element.TimeObserver等とは違い、multipe指定のselectタグでも問題は発生しません。しかし、複数の同名radioやcheckboxを扱えないのは同じです。扱えようになると便利なのですが、そのためにはかなり改造する必要があります。
まず、Abstract.EventObserverのinitializeメソッド。
【改修案-1】Abstract.EventObserver.initializeメソッド initialize: function(element, callback) { var tags; element = $(element); if(element.form){ tags = element.form.elements[element.name]; }else{ tags = document.getElementsByName(element.name); } if(tags.length != null && tags.length > 1 && tags.type == null){ this.element = tags; }else{ this.element = element; } },
引数elementで、複数同名要素の集合も指定可能にしてあります。単要素指定の場合でも、同名要素を探して、あれば対象とします。
次に、Abstract.EventObserver.onElementEventメソッド。
【改修案-2】Abstract.EventObserver.onElementEventメソッド onElementEvent: function() { var isChanged = false; var value = this.getValue(); if (this.lastValue instanceof Array && value instanceof Array){ if(this.lastValue.length != value.length || this.lastValue.any(function(last, i){return (last != value[i]);})){ isChanged = true; } }else{ if (this.lastValue != value) { isChanged = true; } } if(isChanged){ this.callback(this.element, value); this.lastValue = value; } },
「Abstract.TimedObserver.onTimerEventメソッド改修検討 - Backstage of theater.js」で示した改修案と同じです。this.getValueメソッドが返却する値が配列の場合でも対応します。ただ、これが役に立つのは、チェック済みラジオボタンを再度クリックしたときに、関数実行を抑制するくらいなのですが・・・。
次に、既存Abstract.EventObserver.registerCallbackメソッドの名前を「_registerCallback」に変更します。
【改修案-3】Abstract.EventObserver.registerCallbackメソッド改名 _registerCallback: function(element) { (省略 既存と同じ) },
registerCallbackメソッドは新たに以下を設定します。
【改修案-4】Abstract.EventObserver.registerCallbackメソッド再設定 registerCallback: function(element) { if (element.length && !element.type) { $A(element).each(this._registerCallback.bind(this)); }else{ this._registerCallback(element); } }
これで、複数の同名要素に対してもイベントが登録できるようになります。
次にForm.Element.EventObserverクラスのgetValueメソッド。
【改修案-5】Form.Element.EventObserver.getValueメソッド再設定 getValue: function() { return Form.Element.getValue2(this.element); }
前述Form.Elementクラスの改修案の追加メソッドgetValue2を使用してます。なので、getValue2メソッドも必要です。
【参考】Form.Element.getValue2(改修追加案) getValue2: function(element){ var retVal; element = $(element); if(element.length != null && element.type == null){ retVal = $A(element).collect(function(elem){ return this.getValue(elem); }.bind(this)).compact(); }else{ var tags; if(element.form){ tags = element.form.elements[element.name]; }else{ tags = document.getElementsByName(element.name); } if(tags.length != null && tags.length > 1 && tags.type == null){ retVal = $A(tags).collect(function(tag){ return this.getValue(tag); }.bind(this)).compact(); }else{ retVal = this.getValue(element); } } return retVal; }
これで、複数の同名要素の値を配列として受け取ることができます。
以下、長くなりますが例です。大半は各メソッドの上書きです。
【参考】改修案を用いた場合の例。 prototype.jsをそのまま読み込み後にメソッド追加・上書き。 <html> <head> <title></title> <script language="javascript" src="prototype.js" charset="utf-8"></script> <script> <!-- //initializeメソッド上書き用関数 function initialize(element, callback) { var tags; element = $(element); if(element.form){ tags = element.form.elements[element.name]; }else{ tags = document.getElementsByName(element.name); } if(tags.length != null && tags.length > 1 && tags.type == null){ this.element = tags; }else{ this.element = element; } this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName && this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); } //onElementEventメソッド上書き用関数 function onElementEvent() { var isChanged = false; var value = this.getValue(); if (this.lastValue instanceof Array && value instanceof Array){ if(this.lastValue.length != value.length || this.lastValue.any(function(last, i){return (last != value[i]);})){ isChanged = true; } }else{ if (this.lastValue != value) { isChanged = true; } } if(isChanged){ this.callback(this.element, value); this.lastValue = value; } } //_registerCallbackメソッド追加用関数 function _registerCallback(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; case 'password': case 'text': case 'textarea': case 'select-one': case 'select-multiple': Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } //registerCallbackメソッド上書き用関数 function registerCallback(element) { if (element.length && !element.type) { $A(element).each(this._registerCallback.bind(this)); }else{ this._registerCallback(element); } } //Form.Element.EventObserver メソッド上書き・追加 Form.Element.EventObserver.prototype = Object.extend(Form.Element.EventObserver.prototype, { initialize: initialize, onElementEvent: onElementEvent, _registerCallback: _registerCallback, registerCallback: registerCallback, getValue: function() { return Form.Element.getValue2(this.element); } }); //Form.EventObserver メソッド上書き・追加 Form.EventObserver.prototype = Object.extend(Form.EventObserver.prototype, { initialize: initialize, onElementEvent: onElementEvent, _registerCallback: _registerCallback, registerCallback: registerCallback }); //Form.Element.getValue2メソッド追加 Form.Element.getValue2 = function(element){ var retVal; element = $(element); if(element.length != null && element.type == null){ retVal = $A(element).collect(function(elem){ return this.getValue(elem); }.bind(this)).compact(); }else{ var tags; if(element.form){ tags = element.form.elements[element.name]; }else{ tags = document.getElementsByName(element.name); } if(tags.length != null && tags.length > 1 && tags.type == null){ retVal = $A(tags).collect(function(tag){ return this.getValue(tag); }.bind(this)).compact(); }else{ retVal = this.getValue(element); } } return retVal; } function init(){ new Form.Element.EventObserver(document.FORMNAME.TEXT, test1); new Form.Element.EventObserver(document.FORMNAME.CHECK, test1); new Form.Element.EventObserver(document.FORMNAME.RADIO, test1); new Form.Element.EventObserver(document.FORMNAME.SELECT, test1); new Form.Element.EventObserver(document.FORMNAME.TEXTALONE, test1); new Form.EventObserver(document.FORMNAME, test2); } function test1(element, value){ Element.update('test1', (element.name||element[0].name) + " cahnged to " + Object.inspect(value)); } function test2(element, value){ Element.update('test2', element.name + " cahnged to " + Object.inspect(value)); } //--> </script> </head> <body onload="init();"> <form name="FORMNAME" onsubmit="return false;"> <input type="text" name="TEXT" value="テキストボックス1"/> <input type="text" name="TEXT" value="テキストボックス2"/> <input type="checkbox" name="CHECK" value="CHEKED1" checked/> <input type="checkbox" name="CHECK" value="CHEKED2"/> <input type="radio" name="RADIO" value="CHEKED1" checked/> <input type="radio" name="RADIO" value="CHEKED2"/> <select size="3" name="SELECT" multiple> <option value="OPTION1" selected>オプション1</option> <option value="OPTION2">オプション2</option> <option value="OPTION3" selected>オプション3</option> <option value="OPTION4">オプション4</option> </select> <input type="text" name="TEXTALONE" value="同名他要素なし"/> </form> <div id="test1">element</div> <div id="test2">form</div> </body> </html>
かなり複雑になってしまった;;
余談ですが、prototype.js本体を改修せずに、自前のパッチファイルを作っておくといいかもしれません。そのうち、これまでの改修案をまとめたパッチファイルを作ってみようかと思います。