2025年9月28日日曜日

liタグをドラッグアンドドロップで入れ替える話

 タグの入替方法

交換自体はそれほど難しくないが、アイテム交換にアクションを付けるとちょっと面倒臭い。
交換処理はいかの手順を踏む

  1. 入替対象タグ内に「draggable="true"」を追加してドラッグ可能にする
  2. ドラッグを開始時の対象idを取得
  3. 交換対象をドラッグオーバーで特定し交換対象がわかる表示
  4. ドロップして実際に交換

処理的にはこうなります。

親タグの<ul>にはidを付与します。「<ul id="draggable-list">」
子タグの<li>にdraggableとidを付与「<li id="item-xxx" draggable="true" >」
親を取得してその中で入れ替えます
リスナーは「item.addEventListener(event, handler)」イベント別に分けます
イベントは「dragstart、dragover、dragend」この三つが必須

  • dragstart(ドラッグ中)
  • dragover(交換可能位置上)
  • dragend(マウスボタンを放した)

挙動抑止として「drop、dragenter、dragleave、dragend」

  • dragenter(ドラッグ対象先判定)
  • dragleave(対象外選択範囲)

抑止処理

「e.preventDefault();」でブラウザ上のデフォルト挙動表示を抑止します。
なので処理の頭に指定します。over中とenterの最初に記述します。
dragleaveは何もしないので処理を空で用意。

処理開始

dragstartにやる事は現在のidが何か?を取得します。
「e.dataTransfer.setData('text/plain', e.target.id);」を使えばdataTransferに掴んでいる対象を保存できます。
変数を使わないのでコード的には綺麗ですが問題はover中は中身を取得出来ません。

なので「draggedItem = e.target;」で対象別途保存します

over中

つまりは交換可能対象の上でドラッグ中の状態です
overされてる対象と現在掴んでる対象を比較交換するかの状態を表示させます
ただ、そのためには現在掴んでいる対象を知る必要があります。
const draggedId = e.dataTransfer.getData('text/plain');if (!draggedId) return;」
const dragitem=document.getElementById(draggedId )
とすれば、ドラッグ対象を得られるのですが、over中はdataTransferから空文字しか返ってきません。
つまり、放した瞬間しか取得出来ません。「if (!draggedId) return;」でその間無視する事ができるものの、それは交換できる状態の視覚効果の処理に到達出来ません。
一応放した瞬間に取得出来るのでAとBの対象交換自体は出来ます。

先に言った通り「交換可能視覚効果」を得るには「ドラッグ先の対象」と「ドラッグ中の対象」両方を知ってる必要があり、ドラッグ中の対象を事前に得るならば処理開始時に「draggedItem = e.target;」をしておいてdraggedItem を参照するようにすればOK
ただ、draggedItem は処理開始時とover中どちらからもアクセスできる必要があります
それはつまりdraggedItem をグローバル変数にせざる得ないという事です。

「 if (targetItem && targetItem !== draggedItem) {」空の場合と交換対象がドロップ中のアイテム自身ではいけないのでこんな感じの分岐になります。

ドロップ

対象にマウスボタンを放した時
ただ、over中に処理が完結する。

動的にリストを追加

function addlist(n){
    const smna = getSecondaryMaterialNames();
    const list = $i('draggable-list');
    const newItem = Object.assign(document.createElement('li'), {id: `item-${crypto.randomUUID()}`,draggable: true,innerHTML: `<span>${smna[n]}</span><span class="delete-btn">×</span>`});
    newItem.dataset.smc = n;
    list.appendChild(newItem); // add tag
    setlistael(newItem);       // set ael(drag)
}

リストに×ボタンを付けてリストを消す

✕ボタンのイベントリスナーは一度だけ定義して親のイベントから継承させる
なのでリスナー登録するのは✕ボタンではない

 $i('draggable-list').ael('click', (e) => {
if (e.target.classList.contains('delete-btn')) {const itemToRemove = e.target.closest('li');if (itemToRemove) {itemToRemove.remove();getUpdatedOrder();}}
});

 親のidにクリックイベントで登録。
クラスのdelete-btnに継承してliの現ターゲットを削除させる。

0 件のコメント:

コメントを投稿