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

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

ただいまの
回答率

88.62%

jquery:増殖したフォーム内のonchangeが効かない

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 4,568

squirrel

score 18

下記のjqueryスニペットに機能追加して、増殖フォームを作成しています。
http://code.runnable.com/VZtbYFWhaIY5zFL-/add-remove-form-elements-dynamically-%5Bjquery-javascript%5D

追加機能は、
始めから表示されているフォームブロックを増やし、radio buttonの代わりにtext, checkbox, select要素を追加して、selectのonchangeをトリガーに、その値によって当該フォーム内のtextareaを表示/非表示にする

というものですが、なぜか増殖したフォームだけselectで値を変えてもなにも反応してくれません。
conlose.logでデバッグしたところ、$('select').change() で出力してるものが何も出力されていないので、clone時に問題がありそうですが、あまり詳しくないため勘所もなく手詰まりになっています。

そもそもこのスニペットを使用しての実現は難しいのかどうかなども含めてアドバイス頂けたら幸いです。
よろしくお願いします。

$(function () {

    var frm_cnt = 1;

    // [Default表示]プルダウンの値に合わせて選択肢テキストエリアの表示非表示をする
    for(var i=0; i<=frm_cnt; i++) {
        var val = $("[id=dish_type\\["+ i +"\\]]").val();

        if(val == 3 || val == 4 || val == 5) {
            $('#memos' + i).css('display', 'block');
        }
        else {
            $('#memos' + i).css('display', 'none');
        }
    }

    // [+]ボタンクリックされた際のアクション
    $(document).on('click', '.add', function(){
        var original = $('#form_block\\[' + frm_cnt + '\\]');
        var originCnt = frm_cnt;
        var originVal = $("input[name='attend\\[" + frm_cnt + "\\]']:checked").val();

        frm_cnt++;

        $original
            .clone()
            .hide()
            .insertAfter($original)
            .attr('id', 'form_block[' + frm_cnt + ']') // クローンのid属性を変更。
            .find("input[type='checkbox'][checked]").prop('checked', true)

            // textareaのdivブロック用追記
            .attr('id', 'memos' + frm_cnt)

            .end() // 一度適用する
            .find(':input').each(function(idx, obj) {
                $(obj).attr({
                    id: $(obj).attr('id').replace(/\[[0-9]+\]+$/, '[' + frm_cnt + ']'),
                    name: $(obj).attr('name').replace(/\[[0-9]+\]+$/, '[' + frm_cnt + ']')
                });

                var $type = obj.type || obj.tagName.toLowerCase(); // form element typeの取得

                //テキストとテキストエリアの中身をクリアにする
                if ($type == 'text') {
                    $(obj).val('');
                }
                else if($type == 'textarea'){
                    $(obj).val('');
                }
            });

        // clone取得
        var clone = $('#form_block\\[' + frm_cnt + '\\]');
        clone.children('span.close').show();

        clone.slideDown('slow');

        // originalチェックボックス復元
        $original.find("input[name='attend\\[" + originCnt + "\\]'][value='" + originVal + "']").prop('checked', true);
    });

    $(document).on('click', '.close', function(){
        var removeObj = $(this).parent();
        removeObj.fadeOut('fast', function() {
            removeObj.remove();
            // 番号振り直し
            frm_cnt = 0;
            $(".form-block[id^='form_block']").each(function(index, formObj) {
                if ($(formObj).attr('id') != 'form_block[0]') {
                    frm_cnt++;
                    $(formObj)
                        .attr('id', 'form_block[' + frm_cnt + ']') // id属性を変更。
                        .find(':input').each(function(idx, obj) {
                        $(obj).attr({
                            id: $(obj).attr('id').replace(/\[[0-9]+\]+$/, '[' + frm_cnt + ']'),
                            name: $(obj).attr('name').replace(/\[[0-9]+\]+$/, '[' + frm_cnt + ']')
                        });
                    });
                }
            });
        });
    });

    // selectorのvalueによってフォーム内容を変える
    $('select').change(function() {
        //idや値を変数に格納
        var id = $(this).attr('id');
        var val = $(this).val();
        var num = id.match(/\[([0-9]+)\]+$/)[1];

        console.log(id + '=' + val);
        console.log(num);

        //選択したvalue値をp要素に出力
        if(val == 3 || val == 4 || val == 5) {
            $('#memos' + num).css('display', 'block');
        }
        else {
            $('#memos' + num).css('display', 'none');
        }
    });
});
<form action="/confirm" id="InputForm" method="post" accept-charset="utf-8">

      <!--0-->
      <div class="form-block" id="form_block[0]">
        <span class="close" title="Close" style="display: none;">-</span>
        <p class="form-content">
          <span><input name="attend_flag[0]" id="attend_flag[0]" value="1" checked="" type="checkbox">参加</span>
          <span>お名前:<input name="atendee[0]" id="atendee[0]" value="お名前" type="text"></span>
          <span>ディッシュタイプ:
            <select name="dish_type[0]" id="dish_type[0]">
              <option value="">選択してください</option>
              <option value="1" selected="">和食</option>
              <option value="2">フレンチ</option>
              <option value="3">イタリアン</option>
              <option value="4">中華</option>
              <option value="5">その他</option>
            </select>
          </span>
          <span id="memos0" style="display: none;">オプショナルディッシュなど:<br>
              <textarea name="memo[0]" id="memo[0]" cols="80" rows="3"></textarea>
          </span>
        </p>
      </div>

      <!--1-->
      <div class="form-block" id="form_block[1]">
        <span class="close" title="Close" style="display: block;">-</span>
        <p class="form-content">
          <span><input name="attend_flag[1]" id="attend_flag[1]" value="1" checked="" type="checkbox">参加</span>
          <span>お名前:<input name="atendee[1]" id="atendee[1]" value="性別" type="text"></span>
          <span>ディッシュタイプ:
            <select name="dish_type[1]" id="dish_type[1]">
              <option value="">選択してください</option>
              <option value="1" selected="">和食</option>
              <option value="2">フレンチ</option>
              <option value="3">イタリアン</option>
              <option value="4">中華</option>
              <option value="5">その他</option>
            </select>
         </span>
         <span id="memos4" style="display: block;">オプショナルディッシュなど:<br>
             <textarea name="memo[1]" id="memo[1]" cols="80" rows="3"></textarea>
         </span>
        </p>
      </div>
      <div class="form-block" id="form_add">
        <span class="add" title="Add">+</span>
      </div>
      <div class="button_confirm"><a href="#" id="Confirm">入力内容の確認</a></div>
</form>
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 4

checkベストアンサー

+5

追加されたコードにはonchangeイベントは適用されていません。

DOMに対するJavascriptの操作はonChangeなどのイベントセットの記述を実行した瞬間にhtml上に存在するDOMに対して適応されます。

なので、javascript実行前にあるフォームに関してはonchangeイベントが設定されていますが、追加されたものに対してはonchangeイベントが設定されていないので、思い通りに動かないという事が起きます。

つまり、cloneして要素をInsertした後にもう一度onchangeイベントをセットしてあげると正しく動きます!

$original
  .clone()
  .hide()

...
  //テキストとテキストエリアの中身をクリアにする
  if ($type == 'text') {
    $(obj).val('');
  }else if($type == 'textarea'){
    $(obj).val('');
  }
});
// cloneした後にイベントを再設定すると動く
$('select').change(function() {
  // イベントを追加
})

しかし、それより一般的?なのは、皆様がおっしゃっているとおり、イベントをdocument自体に付与するパターンだと思います。

$(document).on('change', 'select', function(){
  // documentにeventが設定されているので問題なく動く
});

また、iphoneだと上の用にdocumentやbodyに付与するとイベントが動かないことがあったきがするので、body配下に一つdiv#rootみたいなものをいれて以下のようにするといいかもしれません。

$('#root').on('change', 'select', function(){
  // #rootに入れる
});

何にせよ大事なことは、.change.on('change', function(){})などは~したら~してねという動作をDOMに対して設定する、セットするという理解を持つことだと思います!
それを持っていると、後から追加された要素にはイベントがセットされていないので、現在のバグが出来ていると理解できるかと思います!

長々すいません!

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/05 10:41

    ありがとうございます。$(document).on('change', 'select', function(){ に変えたら解決できました。表示・非表示要素のずれは、memosの採番し直し処理がうまくいってなかったから起きていたようです。
    具体的には、.end()の下に以下のコードを足しました。
    .find("span[id=memos" + originCnt + "]").attr('id', 'memos' + frm_cnt)
    .end()
    いろいろとありがとうございました。

    キャンセル

+3

ほかのと同様にdocumentで捕捉してはどうでしょう。

////$('select').change(function() {
$(document).on('change', 'select', function() {

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/05 10:40

    ありがとうございます。$(document).on('change', 'select', function(){ に変えたら解決できました。表示・非表示要素のずれは、memosの採番し直し処理がうまくいってなかったから起きていたようです。
    具体的には、.end()の下に以下のコードを足しました。
    .find("span[id=memos" + originCnt + "]").attr('id', 'memos' + frm_cnt)
    .end()
    いろいろとありがとうございました。

    キャンセル

+2

$('select').change(function() {

$(document).on('change', 'select', function(){

のように変えてみてください。

【jQuery】.on clickイベントが効かない時の対処方法

  1. 動的に生成された要素にイベントをかける場合
    jQuery1.9の現象なのか?後から.appendや.html()などで追加した要素に効かない場合があります。
    動的に変化する要素へのイベントは、変化しない親の要素に仕込むと動きます。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/05 10:39

    ありがとうございます。$(document).on('change', 'select', function(){ に変えたら解決できました。表示・非表示要素のずれは、memosの採番し直し処理がうまくいってなかったから起きていたようです。
    いろいろとありがとうございました。

    キャンセル

+1

.clone()を呼び出すときに.clone(true)として、イベントも付けてやる必要があると思いますが。
dataも引き継いじゃうので、どうなんでしょう?

あと、こういうのをスニペットって言うんですかね?良く分からないけど。
Wikipediaでは、

スニペット(英語: snippet)とは、「断片」という意味である。情報処理の分野ではよく使う短いプログラムコードを統合開発環境から呼び出す機能の事である。 また、呼び出される短いコードの事をスニペットと呼ぶ場合もある。

って書いてあるので、ちょっと意味が違いそう?最近はそういう風に使うんですか?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/01 12:59

    ありがとうございます。出来ればテキスト類はクリアにしたいですが、データはオリジナルを引き継いでもかまいません。やってみましたが、onchangeは反応してくれたものの、テキストエリアではなくチェックボックスが表示・非表示となってしまいました。
    言語表現については、英語圏の友人がスニペット共有サイトだと教えてくれ、サイト自体もcode snippetsと書いてあるのでスニペットと書きました。

    キャンセル

  • 2017/06/01 14:50

    コードを精査したわけではなく、質問内容で回答してしまったので、見当違いでしたかね。
    他の方が言われているように、documentでイベントを補足する方がよさそうです。

    スニペットの件もすみません。差し出がましかったですね。
    日本語と英語の意味が少々乖離している良い例かもしれません。

    キャンセル

  • 2017/06/05 10:41

    ありがとうございます。$(document).on('change', 'select', function(){ に変えたら解決できました。表示・非表示要素のずれは、memosの採番し直し処理がうまくいってなかったから起きていたようです。
    具体的には、.end()の下に以下のコードを足しました。
    .find("span[id=memos" + originCnt + "]").attr('id', 'memos' + frm_cnt)
    .end()
    いろいろとありがとうございました。

    キャンセル

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

  • ただいまの回答率 88.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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