DHTMLのプルダウン、javascriptクラスのthis

[新着] Webテンプレートを仮オープンしました



0   名前: えろす : 2006/09/06(水) 21:56  ID:elQ/0TlY
IE6.0 

こんにちは。

javascriptとCSSでプルダウンを表示させたり、閉じたりするものを作っています。

下にソースを載せます。クラスを作って、それをニューしています。
this.ids;の部分が問題ありのようで、document.body.onclick=closePullDown;
でセットした関数が呼ばれるときに、this.idsの中身が入ってないのかundefindedとなります。
これをvar idsとしてあげると、問題なく動くのですが、この場合なぜthis.プロパティ形式だと、駄目なのでしょうか?
ご意見を待っております。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-31j">
<style type="text/css">
#menu1{
position:relative;
width:90;
visibility:hidden;
hieght:200;
background-color:#ffffcc;
text-align:center;

}

</style>
<script type="text/javascript">
<!--

var menu = new MenuUtil();

//メニュークラス
function MenuUtil(){

this.ids;

//プルダウン表示
function pullDown(id){

this.ids = id;
var elem = document.getElementById(id);

//alert('ids=' + this.ids);
elem.style.visibility='visible';
document.body.onclick=closePullDown;

}
MenuUtil.prototype.pulldown = pullDown;

//プルダウンを閉じる
function closePullDown(){

alert('this.id=' + this.ids);
var elem = document.getElementById(this.ids);
elem.style.visibility='hidden';
document.body.onclick = "";
ids = null;
}
MenuUtil.prototype.closePD = closePullDown;

return this;

}

//-->
</script>

<title>Menu練習</title>
</head>
<body>
<a href="#" onmouseover='menu.pulldown("menu1")'>menu</a>
<div id="menu1">
<a href="http://www.aol.com">menu1</a>
<a href="http://www.yahoo.co.jp">mennu2</a>
</div>
</body>
</html>

長くなりますが、よろしくお願いします。

1   名前: kimyans : 2006/09/06(水) 21:56  ID:jqZW6ZUI
これはプルダウンではないですね。

2   名前: えろす  : 2006/09/06(水) 21:56  ID:elQ/0TlY
どうもありがとうございます。

これはプルダウンではないですか?

じゃあドロップダウンでしょうか。。
ちょっと言葉を間違えていたかもしれません。

ごめんなさい。

いずれにせよ、thisだと駄目なので、その理由がわかったら教えてください。
よろしくお願いします。


3   名前: sevi- : 2006/09/06(水) 21:56  ID:PQukhZTw
ここに注目せよ.
document.body.onclick=closePullDown;

bodyオブジェクトのclickイベントにイベントハンドラとしてclosePullDownメソッドを割り当てているのだから,
イベント発生時呼び出されたイベントハンドラに代入されるthisオブジェクトはdocument.bodyとなる.
よって、設定されていないidsプロパティを参照しようとしてもできない.

よって,以下のように予めオブジェクト生成時点にて自身を指す変数を保持させて起き,それを参照するように
すれば良い.

//メニュークラス
function MenuUtil()
{
	var obj_this = this;

	//プルダウン表示
	function pullDown(id)
	{
		obj_this.ids = id;
		var elem = document.getElementById(id);
		elem.style.visibility='visible';
		document.body.onclick=closePullDown;
	}
	MenuUtil.prototype.pulldown = pullDown;

	//プルダウンを閉じる
	function closePullDown()
	{
		var elem = document.getElementById(obj_this.ids);
		elem.style.visibility='hidden';
		document.body.onclick = "";
		ids = null;
	}
	MenuUtil.prototype.closePD = closePullDown;

	return obj_this;
}

4   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
>>0
function pullDown(id){
	this.ids = id;
	var elem = document.getElementById(id);
	elem.style.visibility='visible';

	var me = this;
	document.body.onclick=function(e) {
		me.closePD();
		//closePullDown.call(me);
	}
}


いろいろ言いたい点はあるが作るうちに気付くだろうから置くとして
とりあえず、MenuUtilのreturn thisは原則不要。
new Constructorという構文は、戻り値がオブジェクトでなければ
新規生成されたオブジェクトを返す決まりになっている。

>>3
MenuUtilがSingletonという前提で書いてる?
別インスタンスをnewした時点で破綻してしまうけど。

5   名前: sevi- : 2006/09/06(水) 21:56  ID:9J5RKOHs
>>4
知らん.
document.body.onclickに代入する辺りの記述に対処が見られないようだからそのつもりなんでないのか.
(加えて突っ込むならそもそもids変数をプロパティとして保持する必要も,毎回document.getElementByIdを使用して
eleを取得する必要も無いぞ,これ)

取りあえず提出されたコードを前提に疑問点のみ解消するコードに変換しただけなのでそのツッコミは
>>0にしてやって頂きたい.当方にするのは筋違いだ.上記コードが突っ込みどころ満載なのは承知の上だ.

6   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
> そのツッコミは>>0にしてやって頂きたい
> そのツッコミは>>0にしてやって頂きたい
> そのツッコミは>>0にしてやって頂きたい

おぃおぃ、わざわざclass staticなobj_thisを仕立てたのはアンタだろう(苦笑
その間違いについては>>0の責任ではない。

うん、まぁわかった。

7   名前: sevi- : 2006/09/06(水) 21:56  ID:PQukhZTw
>>6
ん?ああ,成る程.

function XXX()
{
}
MenuUtil.prototype.XXX = XXX;

上記のような事をインスタンス初期化時に繰り返している為,
最後に生成したインスタンスが生成したメンバが最終的に全てのインスタンスに登録され参照するように
なっているな.これは気づかなかった私のミスだ.申し訳ない.

参照するメンバが同じ実体になる為,obj_thisも最後に生成したインスタンスを指す事になってしまい,
static fieldのような状態に陥っているようだ.

ちなみにこれを解消するには

function XXX()
{
}
this.XXX = XXX;

とすれば良い.こうする事で外部から接続できないprivate fieldのような変数を内部に記述する事が
できるようになる.これを前提にobj_thisを使えばいい.

8   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
>>7
Thanks! >>6の態度については謝る。

ちなみに>>4の場合、document.body.onclick内のmeは
最後にpullDownを呼び出したインスタンスを指す。はず。

9   名前: sevi- : 2006/09/06(水) 21:56  ID:9J5RKOHs
>>8
いや,理に適った間違いの指摘は逆に助かる.口調や態度なぞ私は気にしないので謝る必要は全く無い.

>>因みに>>4の場合〜〜
そうなると思われる.理由は7で語ったのと同じ理屈となる(設定さる階層が>>7の場合から一段下がっただけで実質は同じ事をしているので)

尚,複数の登録と複数のMenuUtilのインスタンスの生成&同時利用を真面目に考慮するとコードは例えば以下のように変化すると思われる.

以下のコードの場合、MenuUtilインスタンスを一つ生成し,複数の対象を同時にpullDownメンバに与えても,
また、複数のMenuUitlインスタンスを生成し単独〜複数の対象を同時にpullDownメンバに与えても,
破綻無く動作する.(因みにこのコードの場合,onclickでclosePullDownが呼び出された場合,登録したインスタンスに
関係なく全ての登録されたノードは非表示化し,
個別のインスタンスからclosePullDownを呼び出した場合はそのインスタンスに登録されたノードのみが非表示化する.)

また同時に,いちいち処理の度に文書全体をdocument.getElementByIdで探索するのはリソースの無駄遣いなので
登録は対象ノードを直接指定しても良い形に変更してある.(その方が結果的には使い勝手は向上すると思われるので).

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
	<style type="text/css">
#menu1,#menu2
{
	position:relative;
	width:90;
	visibility:hidden;
	hieght:200;
	background-color:#ffffcc;
	text-align:center;
}
</style>

	<script type="text/javascript">
<!--

var menu = new MenuUtil();

//メニュークラス
function MenuUtil()
{
	var list_elem = new Array();
	var fn_click_old = document.documentElement.onclick;
	document.documentElement.onclick = function()
	{
		if(fn_click_old != null)
			fn_click_old.apply(this, arguments);
		closePullDown();
	};

	//プルダウン表示
	function pullDown(elem)
	{
		if(typeof(elem) == "string")
			elem = document.getElementById(elem);
		elem.style.visibility='visible';
		list_elem.push(elem);
	}
	this.pulldown = pullDown;

	//プルダウンを閉じる
	function closePullDown()
	{
		var elem;
		while(elem = list_elem.pop())
			elem.style.visibility='hidden';
	}
	this.closePD = closePullDown;
}

//-->
	</script>

	<title>Menu練習</title>
</head>
<body>
	<a href="#" onmouseover='menu.pulldown("menu1")'>menu1</a>
	<div id="menu1">
		<a href="http://www.aol.com">menu1-1</a> <a href="http://www.yahoo.co.jp">mennu1-2</a>
	</div>
	<a href="#" onmouseover='menu.pulldown("menu2")'>menu2</a>
	<div id="menu2">
		<a href="http://www.aol.com">menu2-1</a> <a href="http://www.yahoo.co.jp">mennu2-2</a>
	</div>
</body>
</html>

10   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
>>9
いゃどうも。参考になる。

ただ、俺にはConstructorを呼び出すたびにメソッドを定義し直すのは
無駄だという強迫観念があるんで、動的にメンバ変更しない限りは
Constructorの外に出してやった方が良いように思う。

ネックになるのはdocument.body(documentElement)へのclick付加だが
IE5.5以上ならばattachEventを使うのが楽?な気がした。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<title>Menu練習</title>

<style type="text/css">
#menu1, #menu2 {
  position: relative;
  width: 90px;
  visibility: hidden;
  height: 200px;
  background-color: #ffc;
  text-align: center;
}
</style>

<ul>
<li onmouseover="menu1.pullDown('menu1')">menu1
  <ul id="menu1"><li>menu1-1</li><li>menu1-2</li></ul>
</li>
<li onmouseover="menu2.pullDown('menu2')">menu2
  <ul id="menu2"><li>menu2-1</li><li>menu2-2</li></ul>
</li>
</ul>

<script type="text/javascript">
function MenuUtil () {
    this.targetList = new Object;
    this.init();
}

MenuUtil.prototype.init = function() {/*@cc_on*/
    document.body.
        /*@if (@_jscript) attachEvent('on' + @else@*/ addEventListener(/*@end@*/
            'click',
            (function(thisObj) { return function(e) { thisObj.closePullDown(); } } )(this),
             false);
}

MenuUtil.prototype.pullDown = function(id) {
    if ( ! this.targetList[id])
        this.targetList[id] = document.getElementById(id);
    
    this.targetList[id].style.visibility = 'visible';
}

MenuUtil.prototype.closePullDown = function() {
    for (var id in this.targetList)
        this.targetList[id].style.visibility = 'hidden';
}

var menu1 = new MenuUtil;
var menu2 = new MenuUtil;
</script>

11   名前: NullPo : 2006/09/06(水) 21:56  ID:XoE1slt9
>>10
私が頭悪いだけかもしれないのだけど、menu1とmenu2で分けたのは一体どんな意味があるのでしょうか?
MenuUtilのインスタンスは一つだけにしないと、targetListがうまく働かないのでは?

12   名前: sevi- : 2006/09/06(水) 21:56  ID:PQukhZTw
>>11
>MenuUtilのインスタンスは一つだけにしないと、targetListがうまく働かないのでは?

いや,targetListはインスタンス初期化時に個別に別アドレスで新規生成&パブリックフィールドとして代入されるので,
その心配は杞憂だ.

>menu1とmenu2で分けたのは一体どんな意味があるのでしょうか?

提出されたコード上では意味を持ってないかもしれないが,複数生成しても破綻しない事の確認の意味もあるだろうし,
分ける事の効果として,closePullDownメソッド呼出時に非表示とする要素を一括でなくグループ化する効果があると思われる.


>>10

>Constructorを呼び出すたびにメソッドを定義し直すのは
>無駄だという強迫観念があるんで、動的にメンバ変更しない限りは
>Constructorの外に出してやった方が良いように思う。

確かに初期化時に新規関数をインスタンス毎に作成する事になるので,その辺りはメモリを無駄遣いしていると言える.
酷く多数のインスタンスを生成するような用途には向かない.がその代わり,

・prototype利用では実装不可能なプライベート・メンバの設定が可能.
・コーディングと実装内容がクラス記法のそれに近い為,可読性の向上が見込める.
・1により,不必要に多数のパブリック・メンバやメンバの引数を設ける事がなくなり,コードが簡素化&堅牢化する.
・継承もapplyやcallを使うことで上記3点の利点を失う事無く実装可能.

等のメリットが得られるのは個人的にはかなり魅力的だ.
prototypeだと全てのメンバが外部に公開されてしまうのが個人的には痛い.


>on〜〜の代わりに

addEventListenerやatattchEventを使うなら,
必然的に以下のような関数を外部用意する事になると思われる.

function addEventListener(obj, name_event, fn_handler/*, caputure*/)
{
if(obj.addEventListener != null)
obj.addEventListener(name_event, fn_handler, false/*, caputure*/);
else if(obj.attachEvent != null)
obj.attachEvent("on" + name_event.toLowerCase(), fn_handler);
else
obj["on"+name_event.toLowerCase()] = fn_handler;
};

function removeEventListener(obj, name_event, fn_handler)
{
if(obj.removeEventListener != null)
obj.removeEventListener(name_event, fn_handler, false);
else if(obj.detachEvent != null)
obj.detachEvent("on"+name_event.toLowerCase(), fn_handler);
else
delete obj["on"+name_event.toLowerCase()];
};

イベントリスナー等を利用すれば,on〜〜に設定するのと違い,唯一のイベントハンドラ設定箇所を
他の機能と取り合う問題が生じないので確かに便利だ.NS4.Xの悪夢ももう殆ど終焉の様相なので,
言われるように,そろそろ積極的に利用し始めても良い頃合いかもしれない.
短いコード内に上記処理を加えるのはちょっと大げさな気もするが.

13   名前: NullPo : 2006/09/06(水) 21:56  ID:XoE1slt9
>>12
んと、やっぱり私が頭悪いだけのような気がするのだけど、理解できないのでよかったら教えてください。
MenuUtil.prototype.init = function() {/*@cc_on*/
    document.body.
        /*@if (@_jscript) attachEvent('on' + @else@*/ addEventListener(/*@end@*/
            'click',
            (function(thisObj) { return function(e) { thisObj.closePullDown(); } } )(this),
             false);
}

document.body.onclick に挿入できる関数は、私の意識下ではひとつだけだったと思った。
だから、
var menu1 = new MenuUtil;
var menu2 = new MenuUtil;
この記述では、menu2のinitメソッドが実行された時、document.body.onclickが上書きされてmenu2のclosePullDownメソッドしか動かないのではないか、ということ。

ちょっと私の環境では>>10のコードは実行できなかったので検証してないけど。

14   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
>>11
> menu1とmenu2で分けたのは一体どんな意味があるのでしょうか?
インスタンスを複数作成しても大丈夫という例示。
それ以上の意味があるかと問われれば、ない(笑

> MenuUtilのインスタンスは一つだけにしないと、targetListがうまく働かないのでは?
一つのノードをmenu1、menu2で同時に管理する可能性がある、ということ?
確かにそうなんだが、今回の場合はノードの状態を保持しておく必要がない
(ノード自身が管理している)ので、管理担当がかぶっても齟齬は生じない。はず。

これ以外の理由で「うまく働かないのでは?」の理由が今思い浮かばないので
実際に問題が生じるようなら指摘してほしい。


なお、Menu.prototype.initは

MenuUtil.prototype.init = function() {
    var thisObj = this;
    document.body.attachEvent('onclick', function(x) { thisObj.closePullDown(); } );
}


で構わない。じゃぁ何であんな複雑に書いたのかと問われれば、あっちの方が
closureとして保持される実行コンテキストが小さい、ような気がしたから。
根拠はない。

また、俺は>>10で
> ネックになるのはdocument.body(documentElement)へのclick付加だが
と言ったが、これは>>9ですでに解決済なので誤解なきよう。

ただ>>9だと、document.documentElement.onclickに登録するたびに
古いfn_click_oldを保持するためのclosureが増えることになる。
しかも、closureとして保持される実行コンテキストはMenuUtil全体だから
メモリ効率としてどうなんだろう?とは思う。

だから俺は何となくattachEventを使ったんだが、まぁたぶんattchEventの方が
メモリ使用量は大きいかもしれない(笑。正直に言えば、気分的な問題。


誤解と言えば、念のため言っておくとJavaScriptにクラス機構は存在しない。
prototypeを、他の言語で言うところのクラスのように使っているだけ。
(JavaScript 2.0ならclassベースとprototypeベースを切り替えることが可能。Rhinoとか)

とりとめのない話になってすまない。

15   名前: NullPo : 2006/09/06(水) 21:56  ID:XoE1slt9
>>10のコード、ちょっと書き換えたら動いた。
やっぱり私が頭悪かっただけでとっても恐縮です。
>>10
失礼しました。
>>12
ありがとうございます。

>>13の「document.body.onclick に挿入できる関数は、私の意識下ではひとつだけだったと思った。」
これが間違ってたのかな。
attachEvent だと複数動作するの?

16   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
書いてる間にレスが沢山。

>>13
> document.body.onclick に挿入できる関数は、私の意識下ではひとつだけだったと思った。

>>4に対してならそれで正しい。
だがattachEvent(addEventListener)は複数のリスナを登録できる。
というか>>12の最後に同じことが書いてある。

node.onclick = FunctionというJavaScript1.1(ん、1.2だったか)の書式は
いい加減窓から投げ捨てるべきだ。とまでは言わないが。

> ちょっと私の環境では>>10のコードは実行できなかったので検証してないけど。
一応WinIE6、Firefox1.5、Opera9.0では検証しといたつもりだったが。
「ちょっと書き換えたら動いた。」ということは、俺何かミスったか?


>>12
>・prototype利用では実装不可能なプライベート・メンバの設定が可能.
>・コーディングと実装内容がクラス記法のそれに近い為,可読性の向上が見込める.

そう、そこが非常に魅力的なんだよな。俺は

var C_Name = (function(Base) {
    var private_static1 = 0;
    var private_static2 = 0;
    
    function main() { Base.call (this); }
    main.prototype = new base;
    return main;
    
    function private1() { ; }
    function private2() { ; }
} )(Base_C_Name);

var c = new C_Name;


とか考えてみたことはあったんだが、いまいちだった。


話が全然関係のないところに行ってるな(笑
個人的にはこういうのも好きなんだが。

17   名前: NullPo : 2006/09/06(水) 21:56  ID:XoE1slt9
>>16
>だがattachEvent(addEventListener)は複数のリスナを登録できる。
なるほどー。
調べてみたら常識レベルだったようで、顔から火が出そうです。
>というか>>12の最後に同じことが書いてある。
>>12の最後っていうのはそんなことが書いてあったのですか。
私が無能なせいで意味がわからなかった。。。

>「ちょっと書き換えたら動いた。」ということは、俺何かミスったか?
環境はWin2000+IE6。
詳細は不明だけど以下のように書き換えたら動作した。
>>10のコードはコンパイルエラーだった様子。
MenuUtil.prototype.init = function()
{
  var thisObj = this;
  document.documentElement./*@cc_on @if(@_jscript) attachEvent('on' + @else*/ addEventListener( /*@end@*/
   'click', function(){ thisObj.closePullDown(); }, false);
}


詳細なご説明ありがとうございました。

18   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
JScriptコードのコンパイル時にconditional compilationがonになっていれば
コメント内でコンパイル結果を変更することができる。
他のブラウザから見ればただのコメント。

コードが見づらくなるのが難点だが、if分岐するよりは早い。
普通はデバッグとかに使うんだけどね。

> @else*/

あれ、そうだっけ?Win2kのIEは微妙に挙動不審な気がする。
http://msdn.microsoft.com/library/en-us/jscript7/html/jsconConditionalCompilationStatements.asp
調べてみる。Thanx!

19   名前: NullPo : 2006/09/06(水) 21:56  ID:XoE1slt9
>>18
>> @else*/
そこは単純にタイプミスだった。
@else@*/で動作する。
でも>>10のコードは動かないので原因は他にあると思われる。

20   名前: 匿名 : 2006/09/06(水) 21:56  ID:i4s77xzC
あーわかった。/*@cc_on*/ -> /*@cc_on@*/ ね。ごめん。

21   名前: sevi- : 2006/09/06(水) 21:56  ID:9J5RKOHs
>>14
>ただ>>9だと、document.documentElement.onclickに登録するたびに
>古いfn_click_oldを保持するためのclosureが増えることになる。
>しかも、closureとして保持される実行コンテキストはMenuUtil全体だから
>メモリ効率としてどうなんだろう?とは思う。

>>12や過去に示したコードでは実装する機能に比して冗長であるし記述が面倒なので省いたが,

恐らく気づいてるとは思うが,
addEventListener等関数を自作し,イベントリスナ登録には全てそれを利用するという前提であるなら,
on〜〜フィールド(巷ではプロパティと呼ぶが,javascriptのプロパティは実質只のパブリックフィールドだと個人的には思う)
のみの利用で複数のリスナを効率的に登録する手段は一応構築可能だ.
それを用いれば効率はさほどnativeのものと大差ないと思われる.

fn_old等,変数に過去のイベントハンドラを保持し,新規設定したハンドラ内でそれを事前に呼び出すという
処理を採用したのは,単にコードが一番簡素で済み,説明も楽に済むからに過ぎない.

>>16
>話が全然関係のないところに行ってるな(笑

というか質問者が質問以降全く音沙汰無いので,脱線状態で伸びていくスレッドをどう扱えばいいのやら,正直困惑している(笑
面白い話題なので歓迎なのだが.

>>18
>コードが見づらくなるのが難点だが、if分岐するよりは早い。
>普通はデバッグとかに使うんだけどね。

確かに明らかに可読性や編集効率が著しく落ちているので,条件分岐を1〜数個減らす為の目的で採用するというのは
個人的にあまり感心しない.
(いっそ自作したaddEventListener内などの中で用いるなら,話は別だが)

一覧へ戻る