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>を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 23:04
回答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: 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にまとめたのでそちらを参照して下さい。
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総合スコア18156
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/06/19 02:18
2017/06/19 03:06
2017/06/19 03:15
2017/06/19 08:18
2017/06/20 07:45
2017/06/22 05:59
2017/06/22 06:02
2017/06/22 06:06
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
総合スコア13749
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
総合スコア1293
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/06/21 06:08
2017/06/21 06:21
2017/06/21 08:01
2017/06/22 01:16
2017/06/22 02:47
2017/06/22 04:08
2017/06/22 04:27
2017/06/22 06:41
2017/06/22 06:57
2017/06/23 05:15
2017/06/23 05:26
2017/06/23 06:00
2017/06/23 06:12
2017/06/23 06:31
2017/06/23 06:49
2017/06/23 06:52
2017/06/23 07:04
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総合スコア114572
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/06/19 00:54
2017/06/19 01:10
2017/06/19 01:20
2017/06/19 02:08
2017/06/19 02:24
2017/06/19 02:27
2017/06/19 04:19
2017/06/20 12:54
2017/06/20 14:45
2017/06/21 02:17
2017/06/21 05:43
2017/06/21 05:49 編集
2017/06/21 06:45 編集
2017/06/21 06:36
2017/06/21 06:41
2017/06/22 02:21
2017/06/22 02:25
2017/06/22 13:05 編集
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総合スコア18392
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/06/18 22:56
2017/06/18 23:06
2017/06/18 23:22
2017/06/18 23:40 編集
2017/06/20 08:40 編集
2017/06/20 08:29
2017/06/20 08:40
2017/06/20 08:45
2017/06/20 10:47
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総合スコア35860
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/06/20 04:23
2017/06/20 04:24
2017/06/20 04:25
2017/06/22 05:42
2017/06/22 06:05 編集
2017/06/25 07:18
2017/06/29 02:32
2017/06/29 05:47
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。