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

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

ただいまの
回答率

90.51%

  • JavaScript

    19308questions

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

  • HTML

    10742questions

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

  • jQuery

    7765questions

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

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

解決済

回答 5

投稿

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

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

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

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

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

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

以下の例では、開く→閉じる→開く→閉じるを繰り返す度にアラートダイアログが開く回数が加算されてきます。
コンソールで見ると、e1はすべて同じ「開く」ボタンを指していますが、offsetなどの座標からすると、「開く」「閉じる」の位置を指しているようです。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>クリックイベントの入れ子</title>
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</head>
<body>
<div>
<button id="btn_1">開く</button>
</div>


<div id="2" style="display:none;">
<button id="btn_2">保存</button>
</div>
</body>
</html>
$(function(){
    var i = 0;
    //開く
    $('#btn_1').on('click',function(e1){
        $('#2').css('display', 'block');
        console.log(e1);
        //閉じる
        $('#btn_2').on('click', function(e2){
            alert(i);
            console.log(e1);  //1回目:btn_1  / 2回目:btn_1 btn_1 offsetが異なる
            $('#2').css('display', 'none');
            i++;
        });
    });
});
----------------
2.unbindする場合どこに入れるのか

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

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

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

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

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



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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

checkベストアンサー

+2

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

<style>
[aria-hidden=true] {
  display: none;
}
</style>
<script src="../lib/other-work-lib/jquery-2.1.4.js"></script>
</head>
<body>

<div id="sample1">
  <button id="open-button-1">開く</button>
</div>

<div id="sample2" aria-hidden="true">
  <button id="save">保存</button>
</div>

<script>
'use strict';
jQuery('#open-button-1').on('click', function (event) {
  jQuery(event.target.parentNode).addClass('open');
  jQuery('#sample2').attr('aria-hidden', 'false');
});

jQuery('#save').on('click', function (event) {
  console.log(jQuery('.open'));
  jQuery('.open').removeClass('open');
  jQuery(event.target.parentNode).attr('aria-hidden', 'true');
});
</script>

show(), hide() について

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

jQuery 3.0 and jQuery Compat 3.0 Alpha Versions Released | Official jQuery Blog
【翻訳まとめ】jQuery 3.0 alpha リリースノート - Qiita

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/28 09:19

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

    キャンセル

  • 2015/10/28 09:21

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

    ありがとうございました。

    キャンセル

+1

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

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/27 11:05

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

    キャンセル

  • 2015/10/28 08:47

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

    キャンセル

+1

1. 前回の質問内で質問者さんがおっしゃったとおり、クリックイベントのハンドラ内で閉じる処理を定義していることが原因です。
まず、処理を定義することと、処理を実行することの違いを意識してください。
var a = 1;
var fn = function() {console.log(a)};
a = 2;
fn();
上の処理は、処理を定義した時、aは1ですが、処理を実行した時aは2です。
結果的に、fn()によって出力されるのは2になります。
var fn2 = undefined;
var fn1 = function(){
  fn2 = function(){};
};
fn1();
fn1();
上記処理では、fn2を定義する処理を2回行っています。(fn2は実行していません。)
最終的にfn2に残る関数は1つですが、実際には2つの関数オブジェクトが生成されています。
この例では上書きされてしまうために一つしか残りませんが、もしこれが追加する処理だったら、fn1を実行するたびにfn2が増えていきます。
質問者さんがぶつかった問題はまさにこれです。

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

2. 個人的には unbind をするまでもないと思いますが、あえて答えるなら、
新しい関数を追加する前に、現在存在する関数を unbind するのがいいでしょう。
前回の質問のコードで言うと、ここです。
// ## 追記↓ ##
//一旦保存ボタンのクリックイベントにバインドされた関数を削除する
$('#do-save').off('click');
// ## 追記↑ ##
//エディタウィンドウの保存ボタン
$('#do-save').on('click',function(){.....});
unbindbindと対になるもので、onで追加したものはoffで消すのがいいと思います。

3. JavaScript のスコープチェーンというものを調べてください。そこまで難しいものではありません。
簡単な例を挙げると、
function fn1() {
  var a = 1;
  function fn2(){
    var a = 2;
    console.log(a);// a が 2 つあるけど、この a はどっち?
  }
  console.log(a);// じゃあこっちは?
}
みたいな問題を、JavaScript の処理系がどう解決するかのルールのことです。
ちなみに正解は上が2で下が1です。(ちなみにfn1の更に外側ではundefinedになります。)
変数を参照した時、その場所を囲っている関数のなかで、一番内側で宣言された変数を参照します。

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

これを利用すると、以下のようにシンプルに実装できます。
<!DOCTYPE html>
<table>
    <tr>
        <td>A</td>
        <td>B</td>
    </tr>
</table>
<div id="dialog" hidden>
    <input id="input"><button id="ok">OK</button><button id="cancel">CANCEL</button>
</div>
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script>
!function(){
    // 以下で宣言された3つの変数は、
    // on メソッドに渡されたイベントハンドラの内部からも参照できる。
    /** @var {JQuery} 選択された要素 */
    var selected = null;
    
    /** @var {JQuery} ダイアログ */
    var dialog = $("#dialog");
    
    /** @var {JQuery} ダイアログ内の入力欄 */
    var input = dialog.find("#input");
    
    $("td").on("click", function(event){
        selected = $(event.currentTarget);
        dialog.show();
        input.val(selected.text());
    });
    
    $("#ok").on("click", function(){
        selected.text(input.val());
        dialog.hide();
        selected = null;
    });
    
    $("#cancel").on("click", function(){
        dialog.hide();
        selected = null;
    });
}();
</script>

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/28 08:57

    ありがとうございます。
    1.→イベントの定義が関数を追加している動作と同義ということでしょうか。
    ご提示いただいた例が非常にわかりやすく理解できましたが、jQueryの動作を理解できていないようです。もう少し勉強してみます。

    2.→offを定義するタイミングは、1の流れで非常によく理解できました。

    3.→スコープについては理解していたつもりでした。
    今回直面した問題ですと、セルをクリックしたスコープ内で対象が定義されるため、スコープ外に持ち出すことができないと考えたため、入れ子にしてしまいました。
    ご提示いただいた例から、今回の件についての実装方法を考えてみます。

    キャンセル

+1

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/28 08:58

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

    キャンセル

+1

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

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

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

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

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

具体的な動きとしては、以下のようになっていると考えてくださって問題ないかと思います。
<Script Language="JavaScript"><!--
var array = new Array();
function clickTest1() {
    //テーブルセルのクリックイベントの代わり
    
    //$('#do-save').on('click',function(){}の代わり
    onClickAdd(function(){alert("アラート")});
}
function onClickAdd(func) {
    //onclickイベントを追加する
    array.push(func);
}

//保存ボタンのクリックイベントの代わり
function clickTest2() {
    //登録されているonclickイベントを全て実行
    for(var i = 0; i < array.length; i++){
        array[i]();
    }
}
//--></script>
<FORM name="f1">
  <input type="button" value="開く" onClick="clickTest1()">
  <input type="button" value="保存" onClick="clickTest2()">
</FORM>
---------------- 
2.unbindする場合どこに入れるのか 

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

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

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

$(function(){
    
    //テーブルセル押下で編集モード
    $('[id^=edit-table] > tbody > tr > td').on('click', function(e){
    var targetCell = $(this);
    console.log(targetCell);

        //disable-editorクラスがある場合は編集させない
        if(!$('[id^=edit-table]').hasClass('disable-editor')){
            html = targetCell.html();
            console.log('セル内のHTML:'+html);
            //エディタ起動
            var target = '#td_editor_modal';
            activateTdEditor(target, html);

            //エディタウィンドウの保存ボタン
            // 前回追加したクリックイベントを削除
            $('#do-save').unbind('click');
            // 新しくクリックイベントを追加
            $('#do-save').on('click',function(){
                //エディタ内のコンテンツを取得
                //本来はエディタからの戻り値が入る
                var content = $('#edit_box').html();
                console.log('戻り値:'+content);

                //モーダルウィンドウを閉じる
                modalClose('#td_editor_modal');
                //元のセルの内容を置換
                console.log(targetCell);
                $(targetCell).html(content);

                //デバッグ用に行列のインデックスを出力
                debugPrintIndex(e);
            });
        }
    });
    
    //エディタウィンドウを閉じる
    $('#modal-close').on('click', function(){
        modalClose('#td_editor_modal');
    });
});

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

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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/28 09:12

    ありがとうございます。
    1.2.→非常によくわかりました。jQueryの動作を理解できていないようなので、もう少し勉強してみます。
    また、元のソースの添削についてもありがとうございます。

    >エディタのクローズイベントについては、
    >毎回何か変わるというわけでもないので、unbindをするしない以前に、
    >外に出してしまったほうがいいです。
    →ありがとうございます。ご指摘の通りでした。なぜこうしたのだろうか。

    3.→当初は連想配列に入れてしまおうかと考えていたのですが、クリックイベントが発生したときに対象が確定するため、スコープ外に配列を受け渡す方法が思いつきませんでした。
    皆様のご指摘をもとに、改めて考えなおしてみようと思います。

    キャンセル

同じタグがついた質問を見る

  • JavaScript

    19308questions

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

  • HTML

    10742questions

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

  • jQuery

    7765questions

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