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

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

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

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

jQuery

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

Q&A

解決済

6回答

3459閲覧

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

gomatan1258

総合スコア67

JavaScript

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

jQuery

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

0グッド

0クリップ

投稿2017/06/18 13:01

編集2017/06/18 23:02

html

1<!DOCTYPE html> 2<html> 3<head> 4<meta charset="utf-8"> 5<link rel="stylesheet" href="style.css"> 6<title>テスト</title> 7</head> 8<body> 9<ul id="test"> 10 <li class="change">aaa</li> 11 <li class="change">bbb</li> 12 <li class="change">ccc</li> 13 <li class="change">ddd</li> 14</ul> 15<script src="https://code.jquery.com/jquery-3.1.0.min.js"></script> 16<script src="javascript1.js"></script> 17</body> 18</html>

javascript

1$(function() { 2 $('.change').on('click', function() { 3 $selectedChara = $(this); 4 }); 5});

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> これにしたいです。クラス名が同じでわかりにくいですが、よろしくお願いします。

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2017/06/18 13:11

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

2017/06/18 23:04

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

回答6

0

ベストアンサー

ノードの参照を保持する

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

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

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

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

HTML

1<ul id="test"> 2 <li class="change">aaa</li> 3 <li class="change">bbb</li> 4 <li class="change">ccc</li> 5 <li class="change">ddd</li> 6</ul> 7<script> 8'use strict'; 9jQuery('.change').on('click', function (event) { 10 var currentTarget = jQuery(event.currentTarget); 11 12 if (currentTarget.attr('aria-selected') === 'true') { 13 currentTarget.attr('aria-selected', 'false'); 14 } else { 15 var ul = event.currentTarget.parentNode; 16 var li = jQuery('.change[aria-selected="true"]', ul); 17 18 if (li.length) { 19 var next = li.next()[0]; 20 21 if (next === currentTarget[0]) { 22 ul.insertBefore(currentTarget[0], li[0]); 23 } else { 24 ul.replaceChild(li[0], currentTarget[0]); 25 next ? ul.insertBefore(currentTarget[0], next) : ul.appendChild(currentTarget[0]); 26 } 27 28 li.attr('aria-selected', 'false'); 29 } else { 30 currentTarget.attr('aria-selected', 'true'); 31 } 32 } 33}); 34</script>

コード1の問題点

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

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

コード2 (汎用型)

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

HTML

1<style> 2.change[aria-selected="true"] { 3 color: black; 4 background-color: #efe; 5 border: solid 1px #3a3; 6} 7 8.change { 9 margin: 0.3em 0; 10 color: black; 11 background-color: #ddd; 12 border: solid 1px #999; 13 padding: 0.2em 0.8em; 14} 15 16#test2 .change { 17 display: inline-block; 18} 19</style> 20<body> 21<ul id="test1"> 22 <li class="change">1a</li> 23 <li class="change">1b</li> 24 <li class="change">1c</li> 25 <li class="change">1d</li> 26</ul> 27 28<ul id="test2"> 29 <li class="change">2a</li> 30 <li class="change">2b</li> 31 <li class="change">2c</li> 32 <li class="change">2d</li> 33</ul> 34 35<script src="swap-node-1.0.2.js"></script> 36<script> 37'use strict'; 38jQuery('.change').on('click', function (event) { 39 var currentTarget = event.currentTarget; 40 41 if (currentTarget.getAttribute('aria-selected') === 'true') { 42 currentTarget.setAttribute('aria-selected', 'false'); 43 } else { 44 var li = currentTarget.ownerDocument.querySelector('.change[aria-selected="true"]'); 45 46 if (li) { 47 swapNode(currentTarget, li); 48 li.setAttribute('aria-selected', 'false'); 49 } else { 50 currentTarget.setAttribute('aria-selected', 'true'); 51 } 52 } 53}); 54</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/19 02:06

編集2017/06/22 13:02
think49

総合スコア18156

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

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

x_x

2017/06/19 02:18

原因まで探っていませんが、2回目に弟要素を選ぶと消えませんか?
think49

2017/06/19 03:06

ご指摘ありがとうございます。 aaa -> bbb のように2回目に次の要素をクリックすると要素が消失する不具合を確認しました。原因は分かっていますが、今は時間が取れない為、後程修正致します。 再現手順が他にあれば、ご指摘頂ければ調査します。
x_x

2017/06/19 03:15

next === currentTarget[0] のため、replaceChildの際にツリーからnextが消えているのではないかと
think49

2017/06/19 08:18

そうですね。私もそう思いましたので、親記事を修正しました。
think49

2017/06/20 07:45

ホワイトスペースノードを詰めてしまう問題を解消したコードを追記しました。
gomatan1258

2017/06/22 05: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> と丸写しですが、動きませんでした。
gomatan1258

2017/06/22 06:02

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

2017/06/22 06:06

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

0

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

JavaScript

1 $('.change').on('click', function(event) { 2 var target = $(this); 3 if (target.hasClass('selected')){ 4 target.toggleClass('selected'); 5 return; 6 } 7 8 var parent = target.parent(); 9 var selected = parent.find('.selected').toggleClass('selected'); 10 if (!selected.length) { 11 target.toggleClass('selected'); 12 return; 13 } 14 15 var next = target.next().is(selected) ? target : target.next(); 16 selected.before(target); 17 if (next.length) { 18 next.before(selected); 19 } else { 20 parent.append(selected); 21 } 22 });

投稿2017/06/19 03:09

x_x

総合スコア13749

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

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

x_x

2017/06/19 04:30

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

0

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

js

1$(function() { 2 var e1 = null; 3 $('#test').on('click', '.change', function() { 4 if (e1) { 5 var tmp = $('<li />'); 6 tmp.replaceWith(e1.replaceWith($(this).replaceWith(tmp))); 7 e1 = null; 8 } else { 9 e1 = $(this); 10 } 11 }); 12});

投稿2017/06/19 00:52

naga3

総合スコア1293

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

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

gomatan1258

2017/06/21 06:08

ご回答ありがとうございます。 <ul id="test"> <li id="change">aaa</li> <li id="change">bbb</li> <li id="change">ccc</li> <li id="change">ddd</li> </ul> として、実行したのですが、上手くいきませんでした。
gomatan1258

2017/06/21 06:21

申し訳ありません。id="change"をclass="change"にしました。 問題なく動いてました。
naga3

2017/06/21 08:01

良かったです。 他の方の回答にあるように、replaceWithで要素を入れ替えるとイベントが発火しなくなりますが、 そもそもこういう場合は親要素にイベントをデリゲートすることがほとんどなので 問題ないことが多いと思います。
gomatan1258

2017/06/22 01:16

勉強不足で大変もうしわけないのですが、発火しなくなるとは、具体的にどういう風になることなんでしょうか。あれでしたら次回の質問に回そうかと思います。
naga3

2017/06/22 02:47

gomatan1258さんが提示された最初のソースでは $('.change').on('click', function() {…} と書かれていると思いますが、これはclassがchangeのli要素すべてに対してclickイベントを割り当てています。 そしてreplaceWithでli要素が除かれると、このclickイベントが消えてしまいます。 その後、このli要素を追加しなおしても、clickイベントは消えているので動かない。それが発火しないということです。 自分のソースでは上記の部分を $('#test').on('click', '.change', function() {…} と変更しました。これはli要素のイベントを、その親要素であるidがtestのul要素が受け持つという意味です。 これにより、li要素のclickイベントが消えても、親要素のul要素でイベントを監視しているので問題ない、というわけです。
gomatan1258

2017/06/22 04:08

教えていただいてありがとうございます。そういった違いがあるのもわかりませんでした。当方は勉強不足だと思っています。とあるサイトで例えばですが$(.change).click(function() {});や、$('.change').on('click', function() {…} の違いの説明を見たのですがわらない程度で、悩んでいましたが、こちらでわかりやすく教わったので、読んでみたら理解できるかもしれません。本当にありがとうございます。
gomatan1258

2017/06/22 04:27

あと一つ申し訳ないですが教えてください。 tmp.replaceWith(e1.replaceWith($(this).replaceWith(tmp))); これがわかりません。e1は選択状態のliで$(this)は移動先ということで間違いないですか? replaceWithは置き換えるということですよね?$(this).replaceWith(tmp)これは移動先のliをtmpに置き換えてるという認識で大丈夫ですか?
naga3

2017/06/22 06:41

$('.change').click(function() {…} を呼び出してもjQuery内部で $('.change').on('click', function() {…} が呼び出されますので、ただの短い書き方だと思っておけばOKです。
naga3

2017/06/22 06:57

e1は選択状態のli要素で$(this)は移動先のli要素で合っています。 replaceWithの戻り値は入れ替えられたほうの除かれた要素です。よって tmp.replaceWith(e1.replaceWith($(this).replaceWith(tmp))); これを分割すると以下のようになります。 最初に選択した要素がa、移動先の要素がbに保存されると思ってください。 var b = $(this).replaceWith(tmp); // 移動先のli要素を空のli要素(tmp)で置き換え、消された移動先のli要素をbに保存する。 var a = e1.replaceWith(b); // 最初に選択したli要素を移動先のli要素(b)で置き換え、消された最初に選択したli要素をaに保存する。 tmp.replaceWith(a); // 移動先のli要素を最初に選択したli要素(a)に置き換える。 上書きを防ぐためにtmpという空要素をかませています。
gomatan1258

2017/06/23 05:15

>replaceWithの戻り値は入れ替えられたほうの除かれた要素です。 何回か考えたんですが、どうしてもこの部分がわからなくて躓いているので、教えてください。$(this).replaceWith(tmp); の戻り値は何ですか?
naga3

2017/06/23 05:26

$(this).replaceWith(tmp); の戻り値は$(this)に元々入っていた要素です。 日本語わかりにくくてすみません。
gomatan1258

2017/06/23 06:00

$(this).replaceWith(tmp); この命令一つでは<li></li>となって$(this)に入っていた要素がbに代入されるということですね?
naga3

2017/06/23 06:12

はいそうです。
gomatan1258

2017/06/23 06:31

ご丁寧に答えていただいてありがとうございます。なんとか理解できたと思います。
gomatan1258

2017/06/23 06:49

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

2017/06/23 06:52

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

2017/06/23 07:04

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

0

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

CSS

1.selected{background-Color:lime} 2

javascript

1$(function(){ 2 $('.changeable').on('click','li',function(){ 3 if($(this).hasClass('selected') || $(this).closest('.changeable').has('li.selected').length==0){ 4 $(this).toggleClass('selected'); 5 }else{ 6 var obj1=$(this); 7 var obj2=$(this).closest('.changeable').find('li.selected').removeClass('selected'); 8 obj1.after(obj2.clone()); 9 obj2.after(obj1.clone()); 10 obj1.remove(); 11 obj2.remove(); 12 } 13 }); 14}); 15

HTML

1<ul class="changeable"> 2<li>aaa</li> 3<li>bbb</li> 4<li>ccc</li> 5<li>ddd</li> 6</ul> 7<ul class="changeable"> 8<li>aaa</li> 9<li>bbb</li> 10<li>ccc</li> 11<li>ddd</li> 12</ul>

cloneしないバージョン

javascript

1$(function(){ 2 $('.changeable li').on('click',function(){ 3 if($(this).hasClass('selected') || $(this).closest('.changeable').has('li.selected').length==0){ 4 $(this).toggleClass('selected'); 5 }else{ 6 var obj1=$(this); 7 var obj2=$(this).closest('.changeable').find('li.selected').removeClass('selected'); 8 var obj3=$('<li>'); 9 obj1.after(obj3); 10 obj2.after(obj1); 11 obj3.after(obj2); 12 obj3.remove(); 13 } 14 }); 15}); 16

投稿2017/06/19 00:41

編集2017/06/19 02:26
yambejp

総合スコア114572

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

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

KSwordOfHaste

2017/06/19 00:54

なるほど・・・clone()とすればややこしい論理は必要ないのですね。
yambejp

2017/06/19 01:10

もちろんcloneしなくても目印をいれて、オブジェクトを入れ替え目印を取るという方法でもよいと思います。 インデックスで場所を覚える方法もありますがタグの出し入れをする順番などが多少煩雑になるかもしれません。
KSwordOfHaste

2017/06/19 01:20

自分の方法は「煩雑になる」方法そのものでした orz
think49

2017/06/19 02:08

一点だけ質問者へ注意を。 .clone() すると既存の <li class="change"> を参照するコードが動かなくなるのでご注意ください。 参照を保持したコードを別回答で書いてみました。
KSwordOfHaste

2017/06/19 02:24

clone()にはそういう落とし穴もあるのですね。自分のコードのテストの際に複数回動作していたのであまり意識できてませんでした。単に直接ノードを操作してためたまたまイベントハンドラーが消えずに済んでいたのですね。
yambejp

2017/06/19 02:27

たしかにcloneだと属性が引き継がれませんからね 前述した目じるしを置いて入れ替えるバージョンも追記しておきます
gomatan1258

2017/06/20 12:54

ご回答ありがとうございます。cloneを使った場合、cloneを使わなかった場合、どちらも自分が意図してるように動いています。この場合thik49さまがおっしゃる参照するコードが動かなくなるという意味がわかっていないので、調べさせていただきます。cloneしないバージョンでは要素の中でclassという文字が出てきますが、これは無視してもいいでしょうか?よろしくお願いします。
think49

2017/06/20 14:45

To: gomatan1258 さん 私が指摘した問題は「.on(), .data(), addEventListener, WeakMap(), 変数格納によるキャッシュ」のようなノードへの参照が関わる機能全般が .clone() によって機能しなくなる事です。 https://jsfiddle.net/5sj6dvr6/1/ x_x さんが提案した改善策ですが、.clone(true, true) を実行する事で子孫要素を含めて .on(), .data() にまつわる参照をコピーする為、この2つには対応できます。 https://jsfiddle.net/5sj6dvr6/2/ しかしながら、jQuery と関わりのない addEventListener や WeakMap、変数によるキャッシュには対応できませんし、公式サイトには「パフォーマンス上の理由から、特定のフォーム要素(テキストエリアに入力されたユーザーデータや選択されたユーザー選択など)の動的な状態は、複製された要素にコピーされません」とあります。 いろいろと管理が面倒になるので、.clone() しなくてすむコードが作れるならその方が無難だと思います。 https://jsfiddle.net/5sj6dvr6/3/ > cloneしないバージョンでは要素の中でclassという文字が出てきますが、 もう少し、具体的に書くと良いと思います。「要素の中で」とはどこのことですか?
gomatan1258

2017/06/21 02:17

ご回答ありがとうございます。なるほど、clone()しなくてすむコードの方が無難ということですね。 > cloneしないバージョンでは要素の中でclassという文字が出てきますが 説明が下手で申し訳ございません。要素の検証の際にエレメントを拝見しましたところ、aaaとcccを入れ替えたときに<li id="aaa" class>aaa</li>のように表示されます。ですがCopyElementしたところ<li id="aaa" class="">aaa</li>となっていました。一応念のため確認させてください。よろしくお願いします。
gomatan1258

2017/06/21 05:43

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(); すみませんん、もう一つ質問いいでしょうか?この最後のobj3.remove()はli要素を消しているのがわかったのですが、その上のafterをつかっているものをconsole.logで調べようとしてもどうなっているのかわかりません。簡単なコードかもしれませんが、当方がわかるような調べ方はありますか?よろしくお願いします。
yambejp

2017/06/21 05:49 編集

なんとなく図示します →xxx_obj1_xxx_xxx_obj2_xxx という構成だとして まずダミーでつくったobj3をobj1の後ろにいれます →xxx_obj1_obj3_xxx_xxx_obj2_xxx 次にobj1をobj2の後ろに移動します →xxx_obj3_xxx_xxx_obj2_obj1_xxx さらにobj2をobj3の後ろに移動します →xxx_obj3_obj2_xxx_xxx_obj1_xxx obj3はもういらないので削除します →xxx_obj2_xxx_xxx_obj1_xxx これでobj1とobj2が入れ替わったことがわかります
think49

2017/06/21 06:45 編集

> <li id="aaa" class>aaa</li>のように表示されます。 class属性は複数の値を空白文字区切りで持つことが出来ます。 .removeClass('selected') によって、class属性値から selected の値だけが class="selected" から削除された場合、class属性が持つ値がゼロになる為、class="" に変わります。 class="" は事実上、無害なので気にしなくとも良いと思います。 > var obj3=$('<li>'); 本来であれば、li要素の後ろにあるテキストノードの手前に挿入するのが最善ですが、jQuery はテキストノードを操作できません。 jQuery だけで操作する為の策として、該当要素ノードの後ろにダミーとなるli要素ノードを置き、ダミー要素を起点として置き換えるべき要素ノードを挿入した後にダミー要素を削除する事で実装されています。
gomatan1258

2017/06/21 06:36

yambejpさま ご丁寧に書いていただいてありがとうございます。すごく分かりやすかったです。obj1とobj2が入れ替わったということなのですね。
think49

2017/06/21 06:41

> jQuery だけで操作する為の策として、該当要素ノードの後ろにダミーとなるli要素ノードを置き、ダミー要素を起点として置き換えるべき要素ノードを挿入した後にダミー要素を削除する事で実装されています。 この実装の場合は一瞬だけダミーとなるli要素が見えてしまう可能性がありますが、手元の Google Chrome では上手く描画管理しているのか、見えませんでした。 ただし、古い実装だと一瞬だけ見えてしまうかもしれません。 テキストノードの手前で insertBefore すれば、ダミー要素を生成する必要がなくなる為、この問題は解消されます。
gomatan1258

2017/06/22 02:21

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

2017/06/22 02:25

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

2017/06/22 13:05 編集

> javascriptにjQueryのイベントなどを混ぜると思わぬところで、不具合が起こるという認識をしたほうがいいということで大丈夫でしょうか? 「当たらずと雖も遠からず」というところですが、正確には「DOMノードに関連付けられた機能の中で『.on, .data 以外のjQueryの機能』『素のJavaScriptの機能』が不具合を引き起こす」というところです。 jQuery の中でも既に実行中の .animate() の処理は破棄されてしまうでしょう。他にも何かあるかもしれません。 > javascriptだけのほうが簡単に書けたりしますか? 簡単に感じるかは人それぞれですが、私が書いた「コード2 (汎用型)」を素のJavaScriptだけで書き直してみました。 ほぼ、jQuery APIを使用していなかったので書き直す手間はそれ程かかっていません。 https://jsfiddle.net/t6ds8Ljg/6/
guest

0

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

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


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


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

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

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

javascript

1$(function() { 2 var firstClick = false, 3 e1 = null; 4 $('.change').on('click', function() { 5 firstClick = !firstClick; 6 if (firstClick) { 7 e1 = this; 8 } else { 9 swap(e1, this); 10 } 11 }); 12 13 function swap(e1, e2) { 14 if (e1 === e2) return; 15 var e1p = e1.parentNode, 16 e2p = e2.parentNode; 17 if (e1p === e2p) { 18 swapUnderSameParent(e1p, e1, e2); 19 } else if (isAncester(e1, e2) || isAncester(e2, e1)) { 20 // e1p,e2pに親子(直接・間接)があった 21 } else { 22 // e1p,e2pに親子(直接・間接)がない 23 swapUnderDifferentParent(e1p, e1, e2p, e2); 24 } 25 } 26 27 function swapUnderSameParent(p, e1, e2) { 28 var i1 = elementIndex(p, e1), 29 i2 = elementIndex(p, e2), 30 tmp; 31 if (i1 > i2) { 32 // e1がe2の前のノードであるようにする 33 tmp = e1, e1 = e2, e2 = tmp; 34 i1 = i2; 35 } 36 p.replaceChild(e1, e2); 37 p.insertBefore(e2, p.childNodes[i1]) 38 } 39 40 function swapUnderDifferentParent(e1p, e1, e2p, e2) { 41 var i2 = elementIndex(e2p, e2), 42 children; 43 e1p.replaceChild(e2, e1); 44 children = e2p.childNodes; 45 if (i2 < children.length) { 46 e2p.insertBefore(e1, children[i2]); 47 } else { 48 e2p.appendChild(e1); 49 } 50 } 51 52 function elementIndex(p, c) { 53 var children = p.childNodes; 54 for (var i in children) { 55 if (children[i] === c) 56 return +i; 57 } 58 } 59 60 function isAncester(p, c) { 61 do { 62 if (p === c) return true; 63 c = c.parentNode; 64 } while (c); 65 return false; 66 } 67});

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


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

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

投稿2017/06/18 13:29

編集2017/06/20 11:46
KSwordOfHaste

総合スコア18392

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

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

gomatan1258

2017/06/18 22:56

ご回答ありがとうございます。やってみたらちゃんと動いてくれるのですが、要素が動かないです。当方は要素も動かしたいです。説明不足で申し訳ございません。
gomatan1258

2017/06/18 23:22

このようなページがあるのですね。appendChildなどはドットインストールで見て一度使ったことがあります。jQueryでのappendやprependなどもよく使います。当方もこれを使うのじゃないかなと思っていました。
KSwordOfHaste

2017/06/18 23:40 編集

位置を入れ替えるので「元の位置が親に対して何番目か」を調べて「その位置へ挿入」とすることになると思います。 e1p = e1.parentNode e2p = e2.parentNode としたとしえt、e2をe1へ移動するのは「e2がe2pの何番目(=i)の子供かを調べ」「e2をe2pから削除」「e2をe1の後ろに挿入」「e1をe1pから削除」「e1をe2pのi番目となるよう挿入」という手順でできそうです。i=0なら「先頭要素の前に挿入」「iが最後ならe2pへappend」というふうに挿入処理も切り分けが必要かと思います。それより簡単な方法があるかどうか自分はわからないですが、素朴に実装するとなればそんな感じでしょうか --- e1pとe2pが同じだとe2を移動した後ではiの位置が変化するのでそういうことも気にしないといけないですね。論理を注意深く作らないとおかしな動きになりそうです。
think49

2017/06/20 08:40 編集

To: KSwordOfHaste さん 私はこのアプローチは好きです。 > } else if (isAncester(e1, e2) || isAncester(e2, e1)) { ちなみに、Node#replaceChild, Node#appendChild, Node#insertBefore には2つのノード間に親子/祖先関係が成立した場合に例外 "DOMException HIERARCHY_REQUEST_ERR" を返す規定があり、それを利用して「自前で例外処理を入れない」という選択肢もあります(私のGitHubのコードはそうしました)。 https://gist.github.com/think49/4cd2cfc82f308c7b5d696dc2a50af3d4#参考リンク 「例外を throw させたくなかったからこそ自前でチェック処理を入れて何も実行しない処理にした」ともとれるので、このコードが無駄とまでは思いませんが、参考までに。 > for (var i in children) { for-in は列挙順がランダムな仕様の為、0から順番に列挙される保証がありません。 また、プロトタイプ上のプロパティまで拾う為、(基本的にやらないとは思いますが) NodeList.prototype に enumerable: true で独自プロパティを定義するコードがあった場合、そのプロパティも拾ってしまいます。 この場合は for 文または for-of 文(NodeList が iterable な実装に限る)で列挙するのが妥当であろうと思います。 https://triple-underscore.github.io/DOM4-ja.html#dom-node-childnodes
KSwordOfHaste

2017/06/20 08:29

ご指摘ありがとうございます。 > HIERARCHY_REQUEST_ERR なるほど!その知識がないと自分のような冗長なコードをかいてしまう原因になると思います。そういう例外が起きることがわかっているとtry-catchにするより簡便・明快な論理が考えられると思いました。 > 0から順番に列挙される保証がありません。 大変ありがたいご指摘ありがとうございます。ArrayやCollectionをどのように列挙するべきか勉強しようと思います。
think49

2017/06/20 08:40

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

2017/06/20 08:45

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

2017/06/20 10:47

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

0

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

javascript

1$('#test').on('click', function(clicked){ return function(e){ 2 if (clicked){ 3 var x = $(clicked).index(); 4 var y = $(e.target).index(); 5 var target = Array.from($('li')); 6 var temp = target[x]; 7 target[x] = target[y]; 8 target[y] = temp; 9 $('#test').append(target); 10 clicked = null; 11 } 12 else clicked = e.target; 13}}(null))
追記

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

javascript

1const $id = document.getElementById.bind(document); 2const $qs = (s) => Array.from(document.querySelectorAll(s)); 3$id('test').addEventListener('click',(clicked => e => { 4 if (clicked) { 5 let target = $qs('#test li'); 6 let x = target.indexOf(clicked); 7 let y = target.indexOf(e.target); 8 [target[x],target[y]] = [target[y],target[x]]; 9 $id('test').append(...target); 10 clicked = null; 11 } 12 else clicked = e.target; 13})())

投稿2017/06/20 04:20

編集2017/06/21 06:33
Lhankor_Mhy

総合スコア35860

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

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

Lhankor_Mhy

2017/06/20 04:23

あ、分割代入使ってしまいました。 ES5の場合は、 let temp = target[x]; target[x] = target[y]; target[y] = temp; でお願いします。
Lhankor_Mhy

2017/06/20 04:24

というか、let使ってるし。
KSwordOfHaste

2017/06/20 04:25

なるほど、swapがスッキリ書けるんですね。
gomatan1258

2017/06/22 05:42

ご回答ありがとうございます。このあたりが当方は全然わかりません。function(clicked){ return function(e){ この部分は何をやってるんですか?よろしくお願いします。
Lhankor_Mhy

2017/06/22 06:05 編集

function(clicked){...} を即時実行し、その戻り値としてfunction(e){...}を返しています。 これの目的は、clicked変数を含むクロージャを作り、前回クリックした要素を記録することにあります。 var counter = function(c){ return function() { return ++c }}(0) counter(); //1が返る counter(); //2が返る counter(); //3が返る みたいなのと同じことをしています。
gomatan1258

2017/06/25 07:18

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

2017/06/29 02:32

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

2017/06/29 05:47

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問