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

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

ただいまの
回答率

89.51%

jQueryで分からないことがあります。

解決済

回答 6

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,982

gomatan1258

score 41

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<title>テスト</title>
</head>
<body>
<ul id="test">
    <li class="change">aaa</li>
    <li class="change">bbb</li>
    <li class="change">ccc</li>
    <li class="change">ddd</li>
</ul>
<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script src="javascript1.js"></script>
</body>
</html>
$(function() {
    $('.change').on('click', function() {
        $selectedChara = $(this);
    });
});

3時間くらい躓いているので、教えてください。
当方がやりたいことは、例えばですが
<li class="change">aaa</li> と
<li class="change">ccc</li> をクリックイベントを使って、入れ替えたいです。
あと同様に、<li class="change">ccc</li> から
<li class="change">ddd</li> を入れ替えたりと、プラグインなど使わずに自由自在に入れ替えるようにしたいのですが、どう書けば実現できますでしょうか?
わかりにくい質問かもしれませんが、ご教授ください。よろしくお願いします。

追記します。例えば<li class="change">aaa</li>を一回クリックしたとして、選択状態にし、<li class="change">ccc</li>を2回目クリックしたとしたときに、この2つの要素を入れ替えたいです。

<li class="change" id="aaa">aaa</li>
<li class="change" id="bbb">bbb</li>
<li class="change" id="ccc">ccc</li>
<li class="change" id="ddd">ddd</li>
これを
<li class="change" id="ccc">ccc</li>
<li class="change" id="bbb">bbb</li>
<li class="change" id="aaa">aaa</li>
<li class="change" id="ddd">ddd</li>
これにしたいです。クラス名が同じでわかりにくいですが、よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • Kosuke_Shibuya

    2017/06/18 22:11

    どこに何を入れたいのですか?

    キャンセル

  • gomatan1258

    2017/06/19 08:04

    すごくわかりにくい質問だったので追記いたしました。

    キャンセル

回答 6

checkベストアンサー

+4

 ノードの参照を保持する

私の知識不足かもしれませんが、jQuery API でやろうとするといろいろと上手くいかないですね。
次の点に注意してコードを書いてみました。

  • DOMノードの参照を保持する(.replaceWith() や .clone() で新しい参照に入れ替えない)

.replaceWith() を使うと、jQuery('.change').on('click') すると同じ要素に続けてクリックしても発火しなくなりました。

 コード1 (jQuery+DOM混合型)

<ul id="test">
  <li class="change">aaa</li>
  <li class="change">bbb</li>
  <li class="change">ccc</li>
  <li class="change">ddd</li>
</ul>
<script>
'use strict';
jQuery('.change').on('click', function (event) {
  var currentTarget = jQuery(event.currentTarget);

  if (currentTarget.attr('aria-selected') === 'true') {
    currentTarget.attr('aria-selected', 'false');
  } else {
    var ul = event.currentTarget.parentNode;
    var li = jQuery('.change[aria-selected="true"]', ul);

    if (li.length) {
      var next = li.next()[0];

      if (next === currentTarget[0]) {
        ul.insertBefore(currentTarget[0], li[0]);
      } else {
        ul.replaceChild(li[0], currentTarget[0]);
        next ? ul.insertBefore(currentTarget[0], next) : ul.appendChild(currentTarget[0]);
      }

      li.attr('aria-selected', 'false');
    } else {
      currentTarget.attr('aria-selected', 'true');
    }
  }
});
</script>

 コード1の問題点

コード1にはli要素間のホワイトスペースノードを保持しないという問題がありました。
li要素のdisplayプロパティを変更していなければ問題になりませんが、 display: inline や display: inline-block を適用していた場合、ホワイトスペースノードによって開けられていた余白がつめられてしまう不具合を誘発させる事になります。

上記リンク先で要素を入れ替える事で余白がつめられる事象を確認できます。
これは jQuery API が基本的にテキストノードを読み飛ばして要素ノードのみを対象に取っている事に原因があります。
対処療法的には .next()[0] を nextSibling に修正してやれば回避できる問題ですが、jQuery API が要素ノードのみを扱う事を基本としている事にも問題があると思います。
例えば、.next().before().after() を使うだけでこの問題が発生し、jQuery ではテキストノードとテキストノードの間に要素ノードを挿入することが出来ません。
従って、「ノードの入れ替え処理は DOM のみで書いた方が良い」という結論に落ち着きます。

 コード2 (汎用型)

swap-node.js は下記リンク先(GitHub)からDLして下さい。
swap-node.js の使い方、制約はGitHubにまとめたのでそちらを参照して下さい。

<style>
.change[aria-selected="true"] {
  color: black;
  background-color: #efe;
  border: solid 1px #3a3;
}

.change {
  margin: 0.3em 0;
  color: black;
  background-color: #ddd;
  border: solid 1px #999;
  padding: 0.2em 0.8em;
}

#test2 .change {
  display: inline-block;
}
</style>
<body>
<ul id="test1">
  <li class="change">1a</li>
  <li class="change">1b</li>
  <li class="change">1c</li>
  <li class="change">1d</li>
</ul>

<ul id="test2">
  <li class="change">2a</li>
  <li class="change">2b</li>
  <li class="change">2c</li>
  <li class="change">2d</li>
</ul>

<script src="swap-node-1.0.2.js"></script>
<script>
'use strict';
jQuery('.change').on('click', function (event) {
  var currentTarget = event.currentTarget;

  if (currentTarget.getAttribute('aria-selected') === 'true') {
    currentTarget.setAttribute('aria-selected', 'false');
  } else {
    var li = currentTarget.ownerDocument.querySelector('.change[aria-selected="true"]');

    if (li) {
      swapNode(currentTarget, li);
      li.setAttribute('aria-selected', 'false');
    } else {
      currentTarget.setAttribute('aria-selected', 'true');
    }
  }
});
</script>

 更新履歴

  • 2017/06/19 12:49 2回目のクリックで1回目にクリックした次の要素を選択すると要素ご消失する不具合修正
  • 2017/06/20 16:40 「コード1の問題点」「コード2 (汎用型)」を追記
  • 2017/06/21 10:48 swap-node.js を更新(jsfiddleのリンク先を更新)
  • 2017/06/22 22:02 DOM API版のjsfiddleサンプルを追加

Re: gomatan1258 さん

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/22 14:59

    <!DOCTYPE html>
    <html lang="ja">
    <head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    </head>
    <style>
    .change[aria-selected="true"] {
    color: black;
    background-color: #efe;
    border: solid 1px #3a3;
    }

    .change {
    margin: 0.3em 0;
    color: black;
    background-color: #ddd;
    border: solid 1px #999;
    padding: 0.2em 0.8em;
    }

    #test2 .change {
    display: inline-block;
    }
    </style>
    <body>
    <ul id="test1">
    <li class="change">1a</li>
    <li class="change">1b</li>
    <li class="change">1c</li>
    <li class="change">1d</li>
    </ul>

    <ul id="test2">
    <li class="change">2a</li>
    <li class="change">2b</li>
    <li class="change">2c</li>
    <li class="change">2d</li>
    </ul>
    <script src="swap-node-1.0.2.js"></script>
    <script>
    'use strict';
    jQuery('.change').on('click', function (event) {
    var currentTarget = event.currentTarget;

    if (currentTarget.getAttribute('aria-selected') === 'true') {
    currentTarget.setAttribute('aria-selected', 'false');
    } else {
    var li = currentTarget.ownerDocument.querySelector('.change[aria-selected="true"]');

    if (li) {
    swapNode(currentTarget, li);
    li.setAttribute('aria-selected', 'false');
    } else {
    currentTarget.setAttribute('aria-selected', 'true');
    }
    }
    });
    </script>

    </body>
    </html>
    と丸写しですが、動きませんでした。

    キャンセル

  • 2017/06/22 15:02

    swap-node-1.0.2.jsもダウンロードして同じ階層にあります。

    キャンセル

  • 2017/06/22 15:06

    申し訳ございませんjQuery入れたら動きました。入れ忘れていました。

    キャンセル

+3

追記2: 自分のコードは修正すべき点があると思いますが、本来どう修正すべきかすぐに確信がもてない(難しい)ため、申し訳ありませんが「よい実装でない点があると思う」とのみコメントさせていただきます。

一例を挙げると、自分の実装で子ノードのindexを求めていますが、他の方の回答にあるようにDOMのnextSibling相当の機能を用いた方がより直接的で確実なコードではないかと思いました。


訂正:エレメントを入れ替えるようなコードに訂正してみました。


1回目にクリックされたエレメントをe1、2回目をe2として

  • 1回目はe1を覚えておく
  • 2回目でe1とe2を入れ替える

といったアプローチではいかがでしょう?

$(function() {
  var firstClick = false,
      e1 = null;
  $('.change').on('click', function() {
    firstClick = !firstClick;
    if (firstClick) {
      e1 = this;
    } else {
      swap(e1, this);
    }
  });

  function swap(e1, e2) {
    if (e1 === e2) return;
    var e1p = e1.parentNode,
        e2p = e2.parentNode;
    if (e1p === e2p) {
      swapUnderSameParent(e1p, e1, e2);
    } else if (isAncester(e1, e2) || isAncester(e2, e1)) {
      // e1p,e2pに親子(直接・間接)があった
    } else {
      // e1p,e2pに親子(直接・間接)がない
      swapUnderDifferentParent(e1p, e1, e2p, e2);
    }
  }

  function swapUnderSameParent(p, e1, e2) {
    var i1 = elementIndex(p, e1),
        i2 = elementIndex(p, e2),
        tmp;
    if (i1 > i2) {
      // e1がe2の前のノードであるようにする
      tmp = e1, e1 = e2, e2 = tmp;
      i1 = i2;
    }
    p.replaceChild(e1, e2);
    p.insertBefore(e2, p.childNodes[i1])
  }

  function swapUnderDifferentParent(e1p, e1, e2p, e2) {
    var i2 = elementIndex(e2p, e2),
        children;
    e1p.replaceChild(e2, e1);
    children = e2p.childNodes;
    if (i2 < children.length) {
      e2p.insertBefore(e1, children[i2]);
    } else {
      e2p.appendChild(e1);
    }
  }

  function elementIndex(p, c) {
    var children = p.childNodes;
    for (var i in children) {
      if (children[i] === c)
        return +i;
    }
  }

  function isAncester(p, c) {
    do {
      if (p === c) return true;
      c = c.parentNode;
    } while (c);
    return false;
  }
});

一応Chromeで動きましたが、かなり分かりにくい論理かもです。


追記1:自分のコードはDOM操作初心者のものと言えると思います。
yambejpさんのようなシンプルなコードと比べるとかなりどんくさい論理ですね。

蛇足ですが・・・
自分の論理には「選択状態」とする配慮は入ってません。
yambejpさんのようにclone()を使っておらず、ノードをまともに入れ替えているため末尾のノードかどうか場合分けしてappendChild, insertBeforeを使い分けるようにしています。
また親が同じか違うかで論理を切り替えており、親が違う場合、入れ替えようとするノードが親子関係にあったときは入れ替えないようにしています。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/20 17:40

    To: KSwordOfHaste さん
    すみません。思い込みで指摘して2点ほど間違いがありましたので、先のコメント欄を修正しました。
    childNodes は NodeList でしたので、iterable でした。
    for-in において prototype 拡張して困る場合は Element.prototype ではなく、NodeList.prototype に enumerable: true で拡張する場合でした。

    キャンセル

  • 2017/06/20 17:45

    いずれにせよ、Javascriptでのfor-inはオブジェクトの構造をよくわかった上で使わないといけなくてObject.keys()を使うべきかfor-inでよいか難しく感じていました。やはりちゃんと勉強しないとまともなスクリプトが書けないと思うのでよく注意しようと思います。コメント大変ありがとうございます。

    キャンセル

  • 2017/06/20 19:47

    皆様、いろいろコードを指摘していただいて、ありがごうございます。まだ当方はjsも初心者なので、console.log()などをところどころ入れて、確かめて行きたいと思います。コードを読んで理解したらコメントいれさせていただきます。時間かかると思いますが、ご了承願います。

    キャンセル

+3

親要素にclassを付けておいたほうがラクですね

.selected{background-Color:lime}
$(function(){
  $('.changeable').on('click','li',function(){
    if($(this).hasClass('selected') || $(this).closest('.changeable').has('li.selected').length==0){
      $(this).toggleClass('selected');
    }else{
      var obj1=$(this);
      var obj2=$(this).closest('.changeable').find('li.selected').removeClass('selected');
      obj1.after(obj2.clone());
      obj2.after(obj1.clone());
      obj1.remove();
      obj2.remove();
    }
  });
});
<ul class="changeable">
<li>aaa</li>
<li>bbb</li>
<li>ccc</li>
<li>ddd</li>
</ul>
<ul class="changeable">
<li>aaa</li>
<li>bbb</li>
<li>ccc</li>
<li>ddd</li>
</ul>

 cloneしないバージョン

$(function(){
  $('.changeable li').on('click',function(){
    if($(this).hasClass('selected') || $(this).closest('.changeable').has('li.selected').length==0){
      $(this).toggleClass('selected');
    }else{
      var obj1=$(this);
      var obj2=$(this).closest('.changeable').find('li.selected').removeClass('selected');
      var obj3=$('<li>');
      obj1.after(obj3);
      obj2.after(obj1);
      obj3.after(obj2);
      obj3.remove();
    }
  });
});

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/22 11:21

    当方は本当にわからないことが多いのですが、javascriptにjQueryのイベントなどを混ぜると思わぬところで、不具合が起こるという認識をしたほうがいいということで大丈夫でしょうか?この要素入れ替えのプログラムはjQueryを使いましたが、javascriptだけのほうが簡単に書けたりしますか?

    キャンセル

  • 2017/06/22 11:25

    自分としては、今のところ厳密さや拡張性のあるものを作ろうとは思っておらず、とにかく要素の中のIDや文字列がうごいてくれれば大丈夫なのですけど、やはり厳密なほうがいいのでしょうか?

    キャンセル

  • 2017/06/22 22:04 編集

    > javascriptにjQueryのイベントなどを混ぜると思わぬところで、不具合が起こるという認識をしたほうがいいということで大丈夫でしょうか?
    「当たらずと雖も遠からず」というところですが、正確には「DOMノードに関連付けられた機能の中で『.on, .data 以外のjQueryの機能』『素のJavaScriptの機能』が不具合を引き起こす」というところです。
    jQuery の中でも既に実行中の .animate() の処理は破棄されてしまうでしょう。他にも何かあるかもしれません。

    > javascriptだけのほうが簡単に書けたりしますか?
    簡単に感じるかは人それぞれですが、私が書いた「コード2 (汎用型)」を素のJavaScriptだけで書き直してみました。
    ほぼ、jQuery APIを使用していなかったので書き直す手間はそれ程かかっていません。
    https://jsfiddle.net/t6ds8Ljg/6/

    キャンセル

+3

replaceWithで置き換えるパターンです

$(function() {
    var e1 = null;
    $('#test').on('click', '.change', function() {
        if (e1) {
            var tmp = $('<li />');
            tmp.replaceWith(e1.replaceWith($(this).replaceWith(tmp)));
            e1 = null;
        } else {
            e1 = $(this);
        }
    });
});

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/23 15:49

    ごめんなさい、やはりちょっと当方の理解が怪しいです。最後のtmp.replaceWith(a);をするとliタグが消えるように思えてしまいます。結果は消えないということですが、なぜ消えないのでしょうか?

    キャンセル

  • 2017/06/23 15:52

    <li></li>タグが消えて、aに入っているli要素に変わります

    キャンセル

  • 2017/06/23 16:04

    ながながとおつきあいいただいてありがとうございます。おかげさまで理解できました。

    キャンセル

+3

DOM操作はすべてjQueryを使ってみます。
think49さんの言うように、replaceWithで取り除かれた要素はもう発火しなくなるみたいですね。

    $('.change').on('click', function(event) {
        var target = $(this);
        if (target.hasClass('selected')){
            target.toggleClass('selected');
            return;
        }

        var parent = target.parent();
        var selected = parent.find('.selected').toggleClass('selected');
        if (!selected.length) {
            target.toggleClass('selected');
            return;
        }

        var next = target.next().is(selected) ? target : target.next();
        selected.before(target);
        if (next.length) {
            next.before(selected);
        } else {
            parent.append(selected);
        }
    });

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/19 13:30

    書いておいてなんですが、DOM操作をしてしまうとイベントが発火しない云々の面倒ごとがあるので、やはりなるべく上から捕らえるのがいいでしょうね。
    $('#test').on('click', '.change', function(event) {

    キャンセル

+1

https://jsfiddle.net/o469aw0L/2/

$('#test').on('click', function(clicked){ return function(e){
  if (clicked){
    var x = $(clicked).index();
    var y = $(e.target).index();
    var target = Array.from($('li'));
    var temp = target[x];
    target[x] = target[y];
    target[y] = temp;
    $('#test').append(target);
    clicked = null;
  }
  else clicked = e.target;
}}(null))
 追記

https://jsfiddle.net/o469aw0L/1/
ヴァニラ版

const $id = document.getElementById.bind(document);
const $qs = (s) => Array.from(document.querySelectorAll(s));
$id('test').addEventListener('click',(clicked => e => {
  if (clicked) {
    let target = $qs('#test li');
    let x = target.indexOf(clicked);
    let y = target.indexOf(e.target);
    [target[x],target[y]] = [target[y],target[x]];
    $id('test').append(...target);
    clicked = null;
  }
  else clicked = e.target;
})())

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/25 16:18

    ご回答ありがとうございます。クロージャという便利なものを使ってやってるんですね?あとvar target = Array.from($('li'));のArray.fromが当方の調べ方ではわかりませんでした。その命令はなにをやっているかご教授お願いします。

    キャンセル

  • 2017/06/29 11:32

    コメント見逃してました。
    Arraylikeなオブジェクトを配列化しているのですが、試してみたところどうやら必要がなかったようですね。

    キャンセル

  • 2017/06/29 14:47

    書かなくても動くんですね。大変勉強になりました。ありがとうございます。

    キャンセル

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

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