質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

Q&A

解決済

5回答

14781閲覧

jQueryでクリックイベントを入れ子にした場合の動作

smiley-_-smiley

総合スコア26

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

1グッド

0クリップ

投稿2015/10/26 14:46

前回、テーブル内の任意のセルをクリックし、モーダルウィンドウ内で編集した内容を元のセルに反映させたいという質問をさせていただきました。

●Javascriptによるテーブル内の任意のセル内テキストの編集
https://teratail.com/questions/18536

いったんは自己解決とさせていただいたのですが、いくつか疑問が残ってしまいましたので質問させていただきます。

1.入れ子にした場合なぜループのような現象が発生するのか
2.unbindする場合どこに入れるのか
3.パラメータを引き回す場合、どのように実装するのがいいのか


1.入れ子にした場合なぜループのような現象が発生するのか

クリックイベント内にクリックイベントを作って動作させると、二回目、三回目と繰り返す度に、ループのような動作が発生してしまいます。
これはどのような理由なのでしょうか。

以下の例では、開く→閉じる→開く→閉じるを繰り返す度にアラートダイアログが開く回数が加算されてきます。
コンソールで見ると、e1はすべて同じ「開く」ボタンを指していますが、offsetなどの座標からすると、「開く」「閉じる」の位置を指しているようです。

HTML

1<!DOCTYPE html> 2<html> 3<head> 4<meta charset="UTF-8"> 5<title>クリックイベントの入れ子</title> 6<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> 7</head> 8<body> 9<div> 10<button id="btn_1">開く</button> 11</div> 12 13 14<div id="2" style="display:none;"> 15<button id="btn_2">保存</button> 16</div> 17</body> 18</html>

javascript

1$(function(){ 2 var i = 0; 3 //開く 4 $('#btn_1').on('click',function(e1){ 5 $('#2').css('display', 'block'); 6 console.log(e1); 7 //閉じる 8 $('#btn_2').on('click', function(e2){ 9 alert(i); 10 console.log(e1); //1回目:btn_1 / 2回目:btn_1 btn_1 offsetが異なる 11 $('#2').css('display', 'none'); 12 i++; 13 }); 14 }); 15});

2.unbindする場合どこに入れるのか

前回のコメントでunbindしてはどうかというご提案をいただきました。
しかし、unbindした場合、以降のイベントが発生しなくなってしまいますので、開く→閉じるを繰り返すことができません。
unbindする場合、どこで行うのがいいのでしょうか。


3.パラメータを引き回す場合、どのように実装するのがいいのか

もともと入れ子にしてしまった理由は、処理完了時に元の要素を処理するため、クリックイベントの発火元の要素を引き回したいためでした。
イベント間でパラメータを引き渡す方法がわからなかったため入れ子にしてみましたが、正常に動作せず、もとの要素を文字列としてモーダルウィンドウのHTMLに書き込み、保存処理で文字列を解析するという方法を取りました。

連続していないイベント間で何らかのパラメータを引き渡す場合、どのように実装するのがいいでしょうか。

今後の参考のため、ご教示いただければと思います。

oyatsu8👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

ベストアンサー

原因については皆さんの指摘される通り、開く度に click イベントを定義しており、開いた数だけ click ハンドラが実行される為です。
「開いた要素」を保存する機構ですが、「開いた要素」に class 属性を与えるのが HTML の意味論的に良いと思います。

HTML

1<style> 2[aria-hidden=true] { 3 display: none; 4} 5</style> 6<script src="../lib/other-work-lib/jquery-2.1.4.js"></script> 7</head> 8<body> 9 10<div id="sample1"> 11 <button id="open-button-1">開く</button> 12</div> 13 14<div id="sample2" aria-hidden="true"> 15 <button id="save">保存</button> 16</div> 17 18<script> 19'use strict'; 20jQuery('#open-button-1').on('click', function (event) { 21 jQuery(event.target.parentNode).addClass('open'); 22 jQuery('#sample2').attr('aria-hidden', 'false'); 23}); 24 25jQuery('#save').on('click', function (event) { 26 console.log(jQuery('.open')); 27 jQuery('.open').removeClass('open'); 28 jQuery(event.target.parentNode).attr('aria-hidden', 'true'); 29}); 30</script>

show(), hide() について

show(), hide() は jQuery 3.0 で小さくない変更が行われており、公式には状況に応じて addClass(), removeClass() を使用することを推奨しています。
他にも WAI-ARIA に aria-hidden 属性が存在する為、class 属性と aria-hidden 属性のどちらを使用するか検討してみると良いと思います。

id属性値は数字から始まってはならない

質問文では <div id="2" style="display:none;"> とありますが、id属性値には「数字から始まってはならない」という文法規則が存在します。
アルファベットから始まる論理構造上意味のある名前を付けるようにしてください。

投稿2015/10/27 03:23

編集2015/10/27 04:58
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

smiley-_-smiley

2015/10/28 00:19

ありがとうございます。 class属性を与えるのがシンプルでわかりやすい気がします。 その他補足についてのご指摘ありがとうございます。 idについての規則を知りませんでしたので、これから注意いたします。
smiley-_-smiley

2015/10/28 00:21

ベストアンサーは非常に悩みましたが、classを利用するというシンプルでわかりやすい回答をいただいたことから選択させていただきました。 他の皆様の回答も非常にわかりやすく、大変感謝しております。 ありがとうございました。
guest

0

既に回答があり、もう不要かと思いますが、
前回のコメントを書かさせていただいたので一応。

このサイトに慣れていないので、見難かったりしたらごめんなさい。


1.入れ子にした場合なぜループのような現象が発生するのか

前回の質問のコメントに書いた通り、"追加"しているからです。
"追加"した場合、元々存在するイベントを処理した後に"追加"したイベントを実行します。

同じ関数を何度もイベントに登録したため、ループしている(同じ処理が何度も実行される)ように見えます。

具体的な動きとしては、以下のようになっていると考えてくださって問題ないかと思います。

JavaScript

1<Script Language="JavaScript"><!-- 2var array = new Array(); 3function clickTest1() { 4 //テーブルセルのクリックイベントの代わり 5 6 //$('#do-save').on('click',function(){}の代わり 7 onClickAdd(function(){alert("アラート")}); 8} 9function onClickAdd(func) { 10 //onclickイベントを追加する 11 array.push(func); 12} 13 14//保存ボタンのクリックイベントの代わり 15function clickTest2() { 16 //登録されているonclickイベントを全て実行 17 for(var i = 0; i < array.length; i++){ 18 array[i](); 19 } 20} 21//--></script> 22<FORM name="f1"> 23 <input type="button" value="開く" onClick="clickTest1()"> 24 <input type="button" value="保存" onClick="clickTest2()"> 25</FORM>

2.unbindする場合どこに入れるのか

動作サンプルを見させていただきました。

テーブルのクリックイベントをunbindしていますが、
今回複数回実行されているのは保存ボタンのクリックイベントなので、
そちらをunbindしてみてください。

あと、ついでですが、
エディタのクローズイベントについては、
毎回何か変わるというわけでもないので、unbindをするしない以前に、
外に出してしまったほうがいいです。

JavaScript

1$(function(){ 2 3 //テーブルセル押下で編集モード 4 $('[id^=edit-table] > tbody > tr > td').on('click', function(e){ 5 var targetCell = $(this); 6 console.log(targetCell); 7 8 //disable-editorクラスがある場合は編集させない 9 if(!$('[id^=edit-table]').hasClass('disable-editor')){ 10 html = targetCell.html(); 11 console.log('セル内のHTML:'+html); 12 //エディタ起動 13 var target = '#td_editor_modal'; 14 activateTdEditor(target, html); 15 16 //エディタウィンドウの保存ボタン 17 // 前回追加したクリックイベントを削除 18 $('#do-save').unbind('click'); 19 // 新しくクリックイベントを追加 20 $('#do-save').on('click',function(){ 21 //エディタ内のコンテンツを取得 22 //本来はエディタからの戻り値が入る 23 var content = $('#edit_box').html(); 24 console.log('戻り値:'+content); 25 26 //モーダルウィンドウを閉じる 27 modalClose('#td_editor_modal'); 28 //元のセルの内容を置換 29 console.log(targetCell); 30 $(targetCell).html(content); 31 32 //デバッグ用に行列のインデックスを出力 33 debugPrintIndex(e); 34 }); 35 } 36 }); 37 38 //エディタウィンドウを閉じる 39 $('#modal-close').on('click', function(){ 40 modalClose('#td_editor_modal'); 41 }); 42});

3.パラメータを引き回す場合、どのように実装するのがいいのか

私がよくやるのは、受け渡し用の連想配列、もしくはクラスを定義することです。
(私が個人的にツールを作る際にやっていることなので、
参考程度に捉えてください。)

モーダルダイアログを開くときに連想配列かクラスに値を入れてやれば、
他の関数からその値を見ることができるので。

配列ではなく連想配列と書いたのは、添え字が数字であるより、
"doSaveOnclick"とかのほうが、後々分かりやすいからです。

投稿2015/10/27 02:49

編集2015/10/27 03:29
moredeep

総合スコア1507

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

smiley-_-smiley

2015/10/28 00:12

ありがとうございます。 1.2.→非常によくわかりました。jQueryの動作を理解できていないようなので、もう少し勉強してみます。 また、元のソースの添削についてもありがとうございます。 >エディタのクローズイベントについては、 >毎回何か変わるというわけでもないので、unbindをするしない以前に、 >外に出してしまったほうがいいです。 →ありがとうございます。ご指摘の通りでした。なぜこうしたのだろうか。 3.→当初は連想配列に入れてしまおうかと考えていたのですが、クリックイベントが発生したときに対象が確定するため、スコープ外に配列を受け渡す方法が思いつきませんでした。 皆様のご指摘をもとに、改めて考えなおしてみようと思います。
guest

0

他の回答者のとおりだと思いますが、2だけ補足します。
jQueryには1回限りのイベントリスナを設定するメソッドがありますので、そちらを使うという方法もあるかと思いますよ。
.one() | jQuery API Documentation

投稿2015/10/27 02:10

Lhankor_Mhy

総合スコア35865

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

smiley-_-smiley

2015/10/27 23:58

ありがとうございます。 確認して試してみたいと思います。
guest

0

  1. 前回の質問内で質問者さんがおっしゃったとおり、クリックイベントのハンドラ内で閉じる処理を定義していることが原因です。

まず、処理を定義することと、処理を実行することの違いを意識してください。

javascript

1var a = 1; 2var fn = function() {console.log(a)}; 3a = 2; 4fn();

上の処理は、処理を定義した時、aは1ですが、処理を実行した時aは2です。
結果的に、fn()によって出力されるのは2になります。

javascript

1var fn2 = undefined; 2var fn1 = function(){ 3 fn2 = function(){}; 4}; 5fn1(); 6fn1();

上記処理では、fn2を定義する処理を2回行っています。(fn2は実行していません。)
最終的にfn2に残る関数は1つですが、実際には2つの関数オブジェクトが生成されています。
この例では上書きされてしまうために一つしか残りませんが、もしこれが追加する処理だったら、fn1を実行するたびにfn2が増えていきます。
質問者さんがぶつかった問題はまさにこれです。

閉じるボタンを押した時に実行される関数が、開くたびに増えていたんです。

  1. 個人的には unbind をするまでもないと思いますが、あえて答えるなら、

新しい関数を追加する前に、現在存在する関数を unbind するのがいいでしょう。
前回の質問のコードで言うと、ここです。

javascript

1// ## 追記↓ ## 2//一旦保存ボタンのクリックイベントにバインドされた関数を削除する 3$('#do-save').off('click'); 4// ## 追記↑ ## 5//エディタウィンドウの保存ボタン 6$('#do-save').on('click',function(){.....});

unbindbindと対になるもので、onで追加したものはoffで消すのがいいと思います。

  1. JavaScript のスコープチェーンというものを調べてください。そこまで難しいものではありません。

簡単な例を挙げると、

javascript

1function fn1() { 2 var a = 1; 3 function fn2(){ 4 var a = 2; 5 console.log(a);// a が 2 つあるけど、この a はどっち? 6 } 7 console.log(a);// じゃあこっちは? 8}

みたいな問題を、JavaScript の処理系がどう解決するかのルールのことです。
ちなみに正解は上が2で下が1です。(ちなみにfn1の更に外側ではundefinedになります。)
変数を参照した時、その場所を囲っている関数のなかで、一番内側で宣言された変数を参照します。

代入した場所ではありませんよ。あくまで宣言した場所です。var ...;の場所です。

これを利用すると、以下のようにシンプルに実装できます。

html

1<!DOCTYPE html> 2<table> 3 <tr> 4 <td>A</td> 5 <td>B</td> 6 </tr> 7</table> 8<div id="dialog" hidden> 9 <input id="input"><button id="ok">OK</button><button id="cancel">CANCEL</button> 10</div> 11<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script> 12<script> 13!function(){ 14 // 以下で宣言された3つの変数は、 15 // on メソッドに渡されたイベントハンドラの内部からも参照できる。 16 /** @var {JQuery} 選択された要素 */ 17 var selected = null; 18 19 /** @var {JQuery} ダイアログ */ 20 var dialog = $("#dialog"); 21 22 /** @var {JQuery} ダイアログ内の入力欄 */ 23 var input = dialog.find("#input"); 24 25 $("td").on("click", function(event){ 26 selected = $(event.currentTarget); 27 dialog.show(); 28 input.val(selected.text()); 29 }); 30 31 $("#ok").on("click", function(){ 32 selected.text(input.val()); 33 dialog.hide(); 34 selected = null; 35 }); 36 37 $("#cancel").on("click", function(){ 38 dialog.hide(); 39 selected = null; 40 }); 41}(); 42</script>

投稿2015/10/27 01:57

編集2015/10/27 02:12
tozjp

総合スコア790

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

smiley-_-smiley

2015/10/27 23:57

ありがとうございます。 1.→イベントの定義が関数を追加している動作と同義ということでしょうか。 ご提示いただいた例が非常にわかりやすく理解できましたが、jQueryの動作を理解できていないようです。もう少し勉強してみます。 2.→offを定義するタイミングは、1の流れで非常によく理解できました。 3.→スコープについては理解していたつもりでした。 今回直面した問題ですと、セルをクリックしたスコープ内で対象が定義されるため、スコープ外に持ち出すことができないと考えたため、入れ子にしてしまいました。 ご提示いただいた例から、今回の件についての実装方法を考えてみます。
guest

0

http://www.jqref.net/event/on.php

jQuery( selector ).on() の2番目の引数はセレクタ

バインドする際に、セレクタを読むため関数を実行→その関数内にも同様の項目があるため もう一度実行

on は以前にバインドした関数を記憶しているため、繰り返す。

根本的に on の使い方が間違っている。

Javascript

1(function( doc ){ 2 jQuery( doc ).on( 'click', '#btn_1,#btn_2', function( e ) { 3 e.target.id == 'btn_1' ? jQuery( '#2' ).show() : jQuery( '#2' ).hide(); 4 } ) 5})( document );

on はイベントバブルを捉えることに意味がある。
因みに ID は数値から始めることはできない。
HTML も見直したほうがいい。

投稿2015/10/26 16:09

miya

総合スコア81

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tozjp

2015/10/27 02:05

on の第2引数のセレクターは省略可能です。 第1にイベント名、第2に関数オブジェクトを渡した場合、それは本来の第2引数のセレクターが省略されたとみなされます。 リンク先の jQuery( 対象要素 ).off( events [, selector ] [, handler ] ) というのは第2引数、第3引数ともに個別で省略可能であることを示していて、 もし第2引数を省略した場合に第3引数を渡すことができない場合は以下のように表現します。 jQuery( 対象要素 ).off( events [, selector [, handler ]] )
smiley-_-smiley

2015/10/27 23:47

onについての説明をいただき、ありがとうございます。 リンク先の説明をよく読んでみたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問