Lekcja 19: jQuery UI - Interakcje (Draggable, Droppable, Resizable, Sortable)

Oprócz widgetów, jQuery UI oferuje zestaw potężnych interakcji, które pozwalają na dodanie dynamicznego zachowania do elementów DOM, takich jak przeciąganie, upuszczanie, zmiana rozmiaru czy sortowanie. Są one często podstawą do budowy bardziej złożonych interfejsów użytkownika.

Podstawowe Interakcje

Draggable (Przeciąganie)

Umożliwia użytkownikowi przesuwanie elementu po stronie za pomocą myszy.

<div id="element-do-przeciagania" style="width: 100px; height: 100px; background: lightblue; border: 1px solid blue; cursor: move;">
  Przeciągnij mnie!
</div>

<script>
$(function() {
  $("#element-do-przeciagania").draggable({
    axis: "x", // Ogranicz ruch tylko do osi X (możliwe też "y")
    containment: "parent", // Ogranicz ruch do elementu nadrzędnego
    cursor: "grabbing", // Zmień kursor podczas przeciągania
    handle: "p", // Pozwól na przeciąganie tylko za pomocą elementu 

wewnątrz opacity: 0.7, // Zmniejsz przezroczystość podczas przeciągania revert: true, // Element wróci na swoje miejsce po upuszczeniu (może być "invalid" lub "valid") snap: true, // Przyciągaj do krawędzi innych elementów // Zdarzenia: start, drag, stop start: function(event, ui) { console.log("Rozpoczęto przeciąganie"); }, stop: function(event, ui) { console.log("Zakończono przeciąganie na pozycji:", ui.position.left, ui.position.top); } }); }); </script>

Droppable (Upuszczanie)

Pozwala elementowi reagować na upuszczenie na niego innego elementu (typu Draggable).

<div id="cel-upuszczenia" style="width: 150px; height: 150px; background: lightyellow; border: 1px solid orange;">
  Upuść tutaj
</div>

<script>
$(function() {
  $("#cel-upuszczenia").droppable({
    accept: "#element-do-przeciagania", // Akceptuj tylko elementy pasujące do tego selektora
    activeClass: "ui-state-highlight", // Klasa dodawana, gdy kompatybilny element jest przeciągany
    hoverClass: "ui-state-active", // Klasa dodawana, gdy kompatybilny element jest nad celem
    tolerance: "fit", // Kiedy uznać, że element jest nad celem ("intersect", "pointer", "touch")
    // Zdarzenia: activate, deactivate, over, out, drop
    drop: function(event, ui) {
      $(this).css("background-color", "lightgreen").text("Upuszczono!");
      // ui.draggable odnosi się do przeciągniętego elementu jQuery
      ui.draggable.hide(); 
    },
    over: function(event, ui) {
      console.log("Element nad celem");
    }
  });
});
</script>

Resizable (Zmiana Rozmiaru)

Umożliwia użytkownikowi zmianę rozmiaru elementu poprzez przeciąganie jego krawędzi lub rogów.

<div id="element-do-zmiany-rozmiaru" style="width: 150px; height: 100px; background: lightcoral; border: 1px solid red; overflow: hidden; position: relative;">
  Zmień mój rozmiar!
  <!-- Uchwyty do zmiany rozmiaru są dodawane automatycznie przez jQuery UI -->
</div>

<script>
$(function() {
  $("#element-do-zmiany-rozmiaru").resizable({
    alsoResize: "#inny-element", // Zmieniaj rozmiar innego elementu jednocześnie
    animate: true, // Animuj zmianę rozmiaru
    aspectRatio: true, // Zachowaj proporcje
    containment: "parent", // Ogranicz zmianę rozmiaru do rodzica
    ghost: true, // Pokazuj "ducha" podczas zmiany rozmiaru
    handles: "n, e, s, w, ne, se, sw, nw", // Które uchwyty pokazać (domyślnie wszystkie oprócz "n, e, s, w")
    maxHeight: 300,
    maxWidth: 400,
    minHeight: 50,
    minWidth: 80,
    // Zdarzenia: create, start, resize, stop
    stop: function(event, ui) {
      console.log("Nowy rozmiar:", ui.size.width, "x", ui.size.height);
    }
  });
});
</script>

Sortable (Sortowanie)

Pozwala na zmianę kolejności elementów na liście (lub w innym kontenerze) poprzez przeciąganie.

<ul id="lista-sortowalna" style="list-style-type: none; margin: 0; padding: 0; width: 60%;">
  <li class="ui-state-default" style="margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; cursor: move;">Element 1</li>
  <li class="ui-state-default" style="margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; cursor: move;">Element 2</li>
  <li class="ui-state-default" style="margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; cursor: move;">Element 3</li>
  <li class="ui-state-default" style="margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; cursor: move;">Element 4</li>
</ul>

<script>
$(function() {
  $("#lista-sortowalna").sortable({
    axis: "y", // Ogranicz sortowanie do osi Y
    containment: "parent",
    cursor: "move",
    items: "li", // Które elementy wewnątrz kontenera są sortowalne
    opacity: 0.6,
    placeholder: "ui-state-highlight", // Klasa dla miejsca, gdzie element zostanie upuszczony
    revert: true, // Animacja powrotu elementu
    // Zdarzenia: create, start, sort, change, beforeStop, stop, update
    update: function(event, ui) {
      // Wywoływane po zakończeniu sortowania i zmianie pozycji w DOM
      let kolejnosc = $(this).sortable("toArray"); // Pobierz tablicę ID elementów w nowej kolejności
      console.log("Nowa kolejność (wymaga nadania ID elementom li):", kolejnosc);
    }
  });
  // $("#lista-sortowalna").disableSelection(); // Zapobiega zaznaczaniu tekstu podczas przeciągania
});
</script>

Łączenie Interakcji

Interakcje można łączyć, np. tworząc listy, między którymi można przeciągać elementy (łączenie Sortable z opcją connectWith).

<ul id="lista1" class="polaczone-listy">
  <li class="ui-state-default">Element A1</li>
  <li class="ui-state-default">Element A2</li>
</ul>
 
<ul id="lista2" class="polaczone-listy">
  <li class="ui-state-highlight">Element B1</li>
  <li class="ui-state-highlight">Element B2</li>
</ul>

<style>
  .polaczone-listy { border: 1px solid #ccc; width: 150px; min-height: 40px; list-style-type: none; margin: 10px; padding: 5px; float: left; }
  .polaczone-listy li { margin: 5px; padding: 5px; cursor: move; }
</style>

<script>
$(function() {
  $("#lista1, #lista2").sortable({
    connectWith: ".polaczone-listy", // Selektor list, z którymi można wymieniać elementy
    placeholder: "ui-state-highlight",
    revert: true
  }).disableSelection();
});
</script>

Zadanie praktyczne

Stwórz prosty interfejs, gdzie użytkownik może przeciągnąć obrazek (<img>) do "kosza" (<div>), a po upuszczeniu obrazek znika z animacją, a kosz zmienia wygląd.

  1. Dodaj element <img> z ID "obrazek" i <div> z ID "kosz". Nadaj im odpowiednie style.
  2. Dołącz jQuery i jQuery UI (Draggable, Droppable, Effects - jeśli chcesz użyć efektów UI do ukrywania).
  3. Zainicjuj .draggable() na obrazku. Ustaw opcję revert: "invalid".
  4. Zainicjuj .droppable() na koszu. Ustaw accept: "#obrazek".
  5. W handlerze zdarzenia drop dla kosza:
  6. Zmień tło lub dodaj klasę do kosza, aby zasygnalizować upuszczenie.
  7. Użyj ui.draggable.hide("explode", 500); (lub inny efekt jQuery UI, np. "puff", lub proste .fadeOut()), aby ukryć obrazek z animacją.
  8. Opcjonalnie: W handlerze over dla kosza zmień jego wygląd (np. obramowanie), a w handlerze out przywróć poprzedni wygląd.
Pokaż rozwiązanie

HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Przeciągnij do Kosza</title>
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
    <style>
        #obrazek { width: 80px; height: 80px; cursor: move; border: 1px solid gray; display: block; margin-bottom: 20px; }
        #kosz { width: 120px; height: 120px; border: 2px dashed #ccc; background-color: #f8f8f8; text-align: center; line-height: 110px; color: #999; transition: all 0.3s ease; }
        #kosz.nad { border-color: orange; background-color: #fffacd; }
        #kosz.upuszczono { border-style: solid; border-color: green; background-color: #e9fce9; color: green; }
    </style>
</head>
<body>

<h2>Przeciągnij obrazek do kosza</h2>

<img id="obrazek" src="https://via.placeholder.com/80" alt="Obrazek">

<div id="kosz">
  Kosz
</div>

<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<!-- Dołącz jQuery UI (w tym efekty, jeśli używasz np. explode) -->
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
<script>
$(function() {
  $("#obrazek").draggable({
    revert: "invalid"
  });

  $("#kosz").droppable({
    accept: "#obrazek",
    classes: {
      "ui-droppable-hover": "nad" // Użyj klasy CSS zamiast hoverClass dla płynniejszej zmiany
    },
    /* Można też użyć zdarzeń over/out:
    over: function(event, ui) {
      $(this).addClass("nad");
    },
    out: function(event, ui) {
      $(this).removeClass("nad");
    },
    */
    drop: function(event, ui) {
      $(this).removeClass("nad").addClass("upuszczono").html("Usunięto!");
      // Ukryj obrazek z efektem "explode" (wymaga dołączenia komponentu Effects z jQuery UI)
      // lub użyj prostszego efektu, np. fadeOut
      ui.draggable.hide("explode", { pieces: 16 }, 500);
      // ui.draggable.fadeOut(500);
    }
  });
});
</script>

</body>
</html>

Zadanie do samodzielnego wykonania

Stwórz listę zadań (<ul> z elementami <li>), którą można sortować za pomocą jQuery UI Sortable.

  1. Stwórz listę HTML z kilkoma elementami <li> reprezentującymi zadania.
  2. Dołącz jQuery i jQuery UI (Sortable).
  3. Zainicjuj .sortable() na elemencie <ul>.
  4. Dodaj stylizację (np. z klas ui-state-default) dla elementów listy, aby wyglądały jak elementy do przeciągania.
  5. Dodaj placeholder (np. klasę ui-state-highlight) dla miejsca, gdzie element zostanie upuszczony.
  6. Opcjonalnie: W zdarzeniu update pobierz nową kolejność elementów (np. ich ID, jeśli je nadasz) i wyświetl ją w konsoli.

FAQ - Interakcje jQuery UI

Jaka jest różnica między widgetem a interakcją w jQuery UI?

Widgety (np. Datepicker, Dialog) to gotowe komponenty interfejsu z własnym wyglądem i złożoną funkcjonalnością. Interakcje (np. Draggable, Sortable) dodają określone zachowania (przeciąganie, zmiana rozmiaru, sortowanie) do istniejących elementów HTML, dając większą elastyczność w budowie niestandardowych interfejsów.

Czy mogę ograniczyć obszar przeciągania (Draggable)?

Tak, służy do tego opcja containment. Można podać selektor elementu nadrzędnego (np. "parent", "#kontener"), dokument ("document"), okno przeglądarki ("window") lub tablicę współrzędnych [x1, y1, x2, y2] definiującą prostokątny obszar.

Jak sprawić, by element Draggable był akceptowany tylko przez niektóre elementy Droppable?

Użyj opcji accept w konfiguracji .droppable(). Można tam podać selektor CSS, który musi pasować do przeciąganego elementu (ui.draggable), aby został on zaakceptowany. Można również podać funkcję, która zwróci true, jeśli element ma być zaakceptowany.

Czy Sortable działa tylko na listach <ul>/<ol>?

Nie, Sortable może działać na dowolnym kontenerze (np. <div>), a sortowalne elementy mogą być dowolnymi elementami potomnymi (np. inne <div>). Ważne jest, aby w opcji items podać odpowiedni selektor dla sortowalnych elementów (domyślnie > *, czyli wszystkie bezpośrednie dzieci).

Jak zapisać nową kolejność elementów po sortowaniu (Sortable)?

W handlerze zdarzenia update (lub stop) można użyć metody .sortable("toArray", { attribute: "id" }). Zwraca ona tablicę wartości atrybutu (domyślnie id) sortowanych elementów w nowej kolejności. Tę tablicę można następnie wysłać do serwera za pomocą AJAX, aby zapisać zmiany.