• Czas czytania ~18 min
  • 16.03.2023

Kilka tygodni temu zbudowałem tablicę Kanban przy użyciu Laravel i InertiaJS dla klienta. Jednym z wymagań dla tablicy było umożliwienie użytkownikom przeciągania / upuszczania kart w tej samej kolumnie i w innych kolumnach.

Wymóg ten polegał na napisaniu wydajnego kodu źródłowego, aby zapisać nową pozycję karty (kart) po każdym przeciągnięciu i upuszczeniu na planszy.

Istnieje wiele sposobów obsługi zapisywania tej zmiany kolejności. Oto kilka opcji, na które natknąłem się.

  • Ten adres URL wyjaśnia, w jaki sposób Trello oblicza pozycję każdej karty po udanym przeciągnięciu i upuszczeniu.

  • Algorytm zmiany kolejności ściskania może pomóc rozwiązać taki problem. Oto więcej wyjaśnień , jak działa ten algorytm.

  • Przy każdej operacji przeciągania i upuszczania zmień kolejność kart, których dotyczy problem, w ich kolumnach, wyślij do zaplecza karty z ich nowymi wartościami pozycji, a partia zapisuje zmiany.

W tym artykule zdecydowałem się użyć opcji 3. Ponieważ będę używał InertiaJS (Vue JS) na froncie, ponowne obliczanie nowych pozycji kart za pomocą JavaScript jest dziecinnie proste.

Istnieje wiele sposobów radzenia sobie z nową pozycją kart na zapleczu.

  • Jednym ze sposobów jest przygotowanie instrukcji SQL Update i użycie metody DB::update() do przeprowadzenia aktualizacji.
  • Innym sposobem jest użycie funkcji Eloquent upsert() do przeprowadzenia aktualizacji. Mimo że metoda upsert() może wykonywać zarówno operacje insert, jak i update, w naszym przypadku upsert() będzie używany tylko do wykonywania aktualizacji. Aktualizujemy pozycje kart przechowywane w bazie danych.

Przejdźmy od razu do kodu źródłowego aplikacji i zbadajmy implementację.

Warunki wstępne

  • Utworzyłem nową aplikację Laravel 10 przy użyciu opcji Docker. Można jednak wybrać dowolną inną metodę tworzenia nowej pustej aplikacji Laravel lokalnie.

  • Zainstalowałem zestaw startowy Laravel Breeze. Jest to opcjonalne, ale uwielbiam to, jak ten pakiet rusztuje wiele widoków i stron łatwo :-) Instalując Laravel Breeze, instalujemy i konfigurujemy InertiaJs oraz Tailwindcss.

Układ

Zacznijmy od zbadania ostatecznego układu tej aplikacji.
Kanban board in Laravel

Tablica Kanban składa się z co najmniej jednej kolumny.
Każda kolumna składa się z jednej lub więcej kart.

Użytkownik może:

  • Dodawanie nowych kart Edytowanie istniejących kart Usuwanie istniejących kart Dodawanie nowych kolumn Usuwanie istniejących kolumn
  • Przeciąganie i upuszczanie kart w tej samej kolumnie
  • Przeciąganie i upuszczanie kart
  • w wielu kolumnach.

Zbudowałem interfejs użytkownika w taki sposób, aby płytka nie pozwalała na przewijanie w pionie. Gdy nie ma miejsca na pokazanie wszystkich kart w kolumnie, lista kart zaczyna przewijać się w pionie. W ten sposób przycisk Dodaj kartę będzie nadal wyświetlany u dołu kolumny.

Gdy do tablicy dodawanych jest wiele kolumn i nie ma miejsca, aby pokazać je wszystkie, tablica umożliwia przewijanie w poziomie. Jest to również typowe zachowanie podczas przeglądania tej tablicy na urządzeniu mobilnym. Jedna kolumna pojawia się naraz. Następnie musisz przewinąć w prawo, aby zobaczyć pozostałe kolumny.

Nie będę poświęcał więcej czasu na wyjaśnianie interfejsu użytkownika ani tego, jak zbudowałem tę płytkę za pomocą Tailwind CSS. Możesz sam sprawdzić repozytorium, aby uzyskać więcej informacji. Laravel Kanban.

Komponenty

Budowanie aplikacji modułowych w Vue lub InertiaJS jest czymś, do czego dążę. Dlatego podzieliłem płytkę Kanban na niezależne komponenty Vue. Zbadajmy je razem.

Komponent

Kanban Jest to główny składnik aplikacji. Definiuje board on właściwość zapełnioną przez InertiaJS. Ten komponent jest archiwizowany wraz z trasąboards.

Plik routes/web.php definiuje wszystkie trasy dla tej aplikacji.

Trasa boards jest zdefiniowana w następujący sposób:

Route::get('/boards/{board}', [BoardController::class, 'show'])->name('boards');

Gdy użytkownik odwiedza /boards adres URL, show() metoda zdefiniowana na nim BoardController::class jest wykonywana. Odkryjmy metodęshow().

public function show(Request $request, Board $board)
{
    // Case when no board is passed over
    if (!$board->exists) {
        $board = $request->user()->boards()->first();
    }
    // eager load columns with their cards
    $board?->loadMissing(['columns.cards']);

    return inertia('Kanban', [
        'board' => $board ? BoardResource::make($board) : null,
    ]);
}

Metoda odbiera jako dane wejściowe niejawnie powiązaną Board instancję modelu. Możliwe jest jednak otwarcie tej trasy bez przechodzenia przez żadne deski. W tym przypadku postanowiłem załadować pierwszą płytkę do bazy danych.

Następnie ładuję brakujące columns i relacje zdefiniowane w Column modelach i cards Card.

Na koniec metoda renderuje komponent InertiaJS w /resources/js/Pages/Kanban.vue, przekazując mu board właściwość reprezentującą obiekt API Resource zawijający modelBoard.

Komponent definiuje pojedynczą właściwość:Definiuje obliczoną właściwość w Vue zawijając kolumny należące do tablicy:Następnie zapętla kolumny dostępne na bieżącej tablicy i renderuje komponent:Komponent Kanban Column emituje Column dwa główne zdarzenia:

const props = defineProps({
  board: Object,
});

const columns = computed(() => props.board?.data?.columns);

<Column
   v-for="column in columns"
   :key="column.title"
   :column="column"
   @reorder-change="onReorderChange"
   @reorder-commit="onReorderCommit"
/>

reorder-change i .reorder-commit Do obu wydarzeń wrócimy później, gdy omówimy przeciąganie i upuszczanie na planszy.

Na koniec dedykuje kolumnę do wyświetlania przyciskuAdd column. Komponent CreateColumn obsługuje tworzenie nowej kolumny na planszy.

<ColumnCreate :board="board.data" />

Następnie przyjrzymy się komponentowiColumnCreate.

Składnik

ColumnCreate Ten komponent ma dwa tryby pracy. Na początku renderuje się jako z etykietą Button Add another list. Użytkownik klika ten przycisk, a pojawia Form się, aby umożliwić użytkownikowi określenie a name dla nowej kolumny.

  <div>
   <form
      v-if="isCreating"
      @keydown.esc="isCreating = false"
      @submit.prevent="onSubmit"
      class="p-3 bg-gray-200 rounded-md"
      >
      <input
         v-model="form.title"
         type="text"
         placeholder="Column name ..."
         ref="inputColumnNameRef"
         class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
         />
      <div class="mt-2 space-x-2">
         <button
            type="submit"
            class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
            >
         Add column
         </button>
         <button
            @click.prevent="isCreating = false"
            type="button"
            class="inline-flex items-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-gray-700 hover:text-black focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
            >
         Cancel
         </button>
      </div>
   </form>
   <button
      v-if="!isCreating"
      @click.prevent="showForm"
      type="button"
      class="flex items-center p-2 text-sm rounded-md font-medium bg-gray-200 text-gray-600 hover:text-black hover:bg-gray-300 w-full"
      >
      <PlusIcon class="w-5 h-5" />
      <span class="ml-1">Add another list</span>
   </button>
</div>

Obsługuję logikę pokazywania/ukrywania formularza za pomocą zmiennej isCreating

const showForm = async () => {
  isCreating.value = true;
  await nextTick(); // wait for form to be rendered
  inputColumnNameRef.value.focus();
};

Vue ref(). Metoda showForm() jest uruchamiana, gdy użytkownik kliknie przycisk, aby dodać nową kolumnę.

Przełącza na isCreating true, czeka, aż Vue JS wykona zmiany DOM za pomocą metody nextTick() i ustawia fokus na danych wejściowych kolumnyname.

const onSubmit = () => {
    form.post(route('boards.columns.store', {
        board: props?.board
    }), {
        onSuccess: () => {
            form.reset();
            isCreating.value = false;
        },
    });
};

Przesłanie formularza powoduje wysłanie żądania POST do trasyboards.columns.store. Trasa routes/web.php ta jest definiowana w następujący sposób:

Route::post('/boards/{board}/columns', BoardColumnCreateController::class)
        ->name('boards.columns.store');

Jest to BoardColumnCreateController::class kontroler invokable w Laravel, który obsługuje tworzenie nowej kolumny w bazie danych.

public function __invoke(StoreColumnRequest $request, Board $board): RedirectResponse
{
$board->columns()->save(Column::create($request->all()));
        return back();
}

Kontroler najpierw sprawdza poprawność żądania przy użyciu żądania formularzaStoreColumnRequest::class. Następnie zapisuje nową kolumnę w bazie danych i powraca do widoku tablicy.

Składnik

kolumny Komponent Column jest powiązany z pojedynczą kolumną na tablicy. Definiuje dwie właściwości:

  • Identyfikator
  • tablicy Obiekt kolumnowy

Definiuje zdarzenia, które może emitować do komponentu nadrzędnego.

const emit = defineEmits(['reorder-commit', 'reorder-change']);

Ponadto komponent ten definiuje właściwość reaktywną cards za pomocą funkcji Vue JSref(). Następnie używa tej właściwości do renderowania kart wewnątrz Draggable listy.

<Draggable
   v-model="cards"
   group="cards"
   item-key="id"
   tag="ul"
   drag-class="drag"
   ghost-class="ghost"
   class="space-y-3"
   @change="onReorderCards"
   @end="onReorderEnds"
   >
   <template #item="{ element }">
      <li>
         <Card :card="element" />
      </li>
   </template>
</Draggable>

Użyłem słynnej biblioteki pakietów Vue.Draggable Vue3.

KomponentDraggable:Renderuje jako Powiązany

  • ul z właściwością
  • cards Uruchamia dwa zdarzenia:
  • change i Renderuje każdą kartę wewnątrz <lielementu > przy użyciu komponentu
  • Card Wrócimy później, aby dokładniej omówić procedury obsługi zdarzeń przeciągania i end

upuszczania.

Aby usunąć kolumnę, kliknij ... przycisk ustawień w prawym górnym rogu każdej kolumny. Użyłem rozwijanego komponentuMenu, oferowanego przez bibliotekę pakietów @headless/vue, aby zbudować menu rozwijane z żądanymi opcjami. Dodałem opcję Delete a column w tym przypadku.

! [Usuń kolumnę] (https://user-images.githubusercontent.com/1163421/222558878-916be407-a2c4-4ca3-8f9c-d4d6c9aa16f8.png "Usuwanie kolumny")

Przed usunięciem kolumny należy najpierw zweryfikować tę operację. W tym celu użyłem komponentu Modal oferowanego przez bibliotekę pakietów @headless/vue.

! [Potwierdzenie usunięcia kolumny] (https://user-images.githubusercontent.com/1163421/222558897-46a9f689-7fd3-4877-a317-9edef07d016d.png "Potwierdzenie usunięcia kolumny")

Po potwierdzeniu usunięcia ten wiersz kodu jest odpowiedzialny za wysłanie żądania DELETE do zaplecza Laravel w celu usunięcia kolumny.

outer.delete(route('columns.destroy', { column: props?.column?.id }));

Trasa columns.destroy jest mapowana ColumnDestroyController::class do wnętrza routes/web.php pliku.

Route::delete('/columns/{column}', ColumnDestroyController::class)->name('columns.destroy');

W ColumnDestroyController::class invokable kontroler, który usuwa daną kolumnę i ponownie ładuje stronę.

public function __invoke(Column $column): RedirectResponse
{
    $column->delete();
    return back();
}

Składnik Column odwołuje się również do składnikaCardCreate, który obsługuje dodanie nowej karty do kolumny. Przyjrzymy się temu wkrótce.

Składnik

CardCreate Ten komponent ma dwa tryby pracy. Najpierw renderuje się jako Button oznaczony Add card. Użytkownik klika go i Form pojawia się, umożliwiając użytkownikowi określenie content dla nowej karty.

Użyłem tej samej logiki, co do tworzenia nowej kolumny.

Komponent karty Komponent

Card jest prosty. Ma dwa tryby pracy. Jeden do edycji zawartości, a drugi do wyświetlania zawartości karty wewnątrz kolumny.

! [Interfejs użytkownika] (https://user-images.githubusercontent.com/1163421/222906920-a07b8968-7ef2-4875-9de5-ac0022299ee2.png "CardUI")

Po najechaniu kursorem na kartę pojawiają się dwie ikony. Jeden do edycji zawartości karty, a drugi do jej usunięcia.

  • Edycja karty odbywa się w miejscu za pomocą Form elementu.
  • Kliknięcie ikony kosza otwiera okno dialogowe modalne z prośbą o potwierdzenie usunięcia.

Używa ConfirmDialog komponentuDialog, który zbudowałem, zawijając a DialogPanel z biblioteki pakietów @headlessui/vue.

Komponent Dialog używa gniazd Vue JS dla tytułu, treści i operacji okna dialogowego.

Jeśli chcesz rozszerzyć tę tablicę Kanban o inny typ modalny, możesz użyć komponentu Dialog i dostosować interfejs użytkownika w dowolny sposób.

Chciałbym zwrócić waszą uwagę na technikę lub sztuczkę, której użyłem, aby zagwarantować, że jedna karta jest w trybie edycji na raz. Oznacza to, że teraz, jeśli klikniesz ikonę edycji, wszystkie karty wymienione w tej kolumnie będą w trybie edycji. To coś, czego chcemy uniknąć. Jak?

W swoim projekcie, jeśli używasz Pinia lub Vuex, zapisujesz w identyfikator sklepie za każdym razem, gdy użytkownik kliknie, aby edytować kartę.

Jednak w tym przypadku użycie jest Store przesadą. Zachowamy tę samą koncepcję, z wyjątkiem użycia Vue JS ref() do stworzenia kompozycyjnego, który oferuje współdzielony magazyn danych do przechowywania aktualnie identyfikator edytowanego materiału.

Utwórz nowy folder w witrynie resource/js/Composables/. Dodaj nowy plik useEditCard.js JavaScript do komponowania o następującej zawartości.

import { ref } from 'vue';

export const useEditCard = ref({
  currentCard: null,
});

Opcja komponowalna eksportuje useEditCard odwołanie zawijające obiekt o pojedynczej currentCardwłaściwości .

Teraz wróćmy do komponentu Card i użyjmy tego komponowalnego.

Najpierw zacznij od zaimportowania komponowania.

import { useEditCard } from '@/Composables/useEditCard';

Wewnątrz programu obsługi zdarzeń, aby wyświetlić formularz edycji, ustaw wartość currentCard na komponowalnym.

const showForm = async () => {

  useEditCard.value.currentCard = props?.card?.id;

  // …
};

Komponent Card już akceptuje card obiekt. Umieszczamy id klikniętą kartę wewnątrz useEditCard.value.currentCard obiektu.

Ta technika gwarantuje, że zawsze jest jedna karta w trybie edycji.

Ważne jest również, aby obsługiwać procedurę obsługi zdarzenia anulowania formularza edycji. Należy zresetować wartość, currentCard aby to odzwierciedlić.

const onCancel = () => {
  useEditCard.value.currentCard = null;
};

To tyle, jeśli chodzi o Card komponent. Przejdźmy do ostatniej prostej i omówmy obsługę przeciągania i upuszczania na zapleczu.

Przeciągnij / upuść karty

In the section under Składnik, I’ve shown the markup required to allow dragging and dropping cards within the same column or across other columns.

<Draggable
   v-model="cards"
   group="cards"
   item-key="id"
   tag="ul"
   drag-class="drag"
   ghost-class="ghost"
   class="space-y-3"
   @change="onReorderCards"
   @end="onReorderEnds"
   >
   <template #item="{ element }">
      <li>
         <Card :card="element" />
      </li>
   </template>
</Draggable>

Implementacja przeciągania/upuszczania w Vue 3 nigdy nie była łatwiejsza przy użyciu biblioteki pakietów Vue Draggable.

Wiążę Draggable komponent ze zmiennącards. Sprawdźmy, gdzie ta zmienna jest zdefiniowana.

const cards = ref(props?.column?.cards);

Jest ref() to zawijanie tablicy kart na właściwości columninput .

Podpinasz się również pod dwa zdarzenia emitowane przez komponent: Draggable change oraz end.

Przyjrzyjmy się onReorderCards() procedurze obsługi zdarzeń.

const onReorderCards = () => {
    const cloned = cloneDeep(cards?.value);

    const cardsWithOrder = [
        ...cloned?.map((card, index) => ({
            id: card.id,
            position: index * 1000 + 1000,
        })),
    ];
    emit('reorder-change', {
        id: props?.column?.id,
        cards: cardsWithOrder,
    });
};

Metoda rozpoczyna się od sklonowania zmiennej reaktywnejcards. W ten sposób upewniamy się, że nie dotykamy oryginalnych kart.

Kod przebiega przez sklonowaną wersję cardspliku . Odwzorowuje każdą kartę na obiekt o dwóch właściwościach: id i position.

Pamiętaj, że zawsze trzymaj cards najnowszą re-ordered wersję kart kolumn. W związku z tym powyższy kod resetuje position właściwość dla wszystkich kart i przypisuje im sekwencyjne pozycje reprezentujące rzeczywistą kolejność tych kart w kolumnie.

Następnie metoda emituje zdarzenie reorder-change przekazujące jako ładunek zdarzenia obiekt zawierający identyfikator kolumny i nowo pozycjonowane karty.

Ta metoda jest uruchamiana raz dla każdej kolumny związanej z operacją przeciągania/upuszczania. Na przykład, jeśli przeciągasz/upuszczasz w tej samej kolumnie, ten program obsługi jest uruchamiany raz. Ale wyobraź sobie, że przeciągasz / upuszczasz kartę z jednej kolumny do drugiej. Następnie ten program obsługi jest uruchamiany raz dla każdej kolumny.

Omówmy onReorderEnds() program obsługi zdarzeń.

const onReorderEnds = () => {
  emit('reorder-commit');
};

Program obsługi zdarzeń po prostu emituje inne zdarzenie, zdarzeniereorder-commit. Komponent Draggable uruchamia end zdarzenie po zakończeniu operacji przeciągania/upuszczania. W związku z tym możemy użyć tego programu obsługi zdarzeń, aby zapisać zmiany w bazie danych. Zobaczmy, jak to robimy.

Teraz chcemy sprawdzić, w jaki sposób przekazujemy oba wydarzenia i reorder-change reorder-commit.

Wracając do komponentuKanban, widzieliśmy, jak renderujemy kolumny.

<Column
   v-for="column in columns"
   :key="column.title"
   :column="column"
   @reorder-change="onReorderChange"
   @reorder-commit="onReorderCommit"
/>

Zauważ, jak podpinasz się pod oba wydarzenia. Przyjrzyjmy się obu procedurom obsługi zdarzeń w kolejności.

Program onReorderchange() obsługi zdarzeń jest zdefiniowany w następujący sposób:

const onReorderChange = column => {
  columnsWithOrder.value?.push(column);
};

Jest Vue columnsWithOrder JSref(), który otacza tablicę. Ta metoda wypycha ładunek zdarzenia do tej zmiennej reaktywnej.

W przypadku przeciągania/upuszczania w obrębie tej samej kolumny program obsługi zdarzeń jest uruchamiany raz. Podczas gdy w przypadku przeciągania/upuszczania karty przez dwie kolumny ten program obsługi zdarzeń jest uruchamiany dwukrotnie. W związku z tym pod koniec operacji zawierałby dwa elementy, columnsWithOrder po jednym dla każdej kolumny.

Z drugiej strony onReorderCommit() program obsługi zdarzeń jest zdefiniowany w następujący sposób:

const onReorderCommit = () => {
  if (!columnsWithOrder?.value?.length) {
    return;
  }
  router.put(route('cards.reorder'), {
    columns: columnsWithOrder.value,
  });
};

Ten program obsługi zdarzeń wykonuje żądanie POST do cards.reorder trasy. Ładunek jest tablicą wszystkich kolumn, na które wpłynęła operacja przeciągania/upuszczania, w tym kart każdej kolumny i nowych pozycji kart w kolumnie.

Przełączmy się na kod po stronie serwera i przejrzyjmy kod, który faktycznie wykonuje aktualizację w bazie danych.

Plik routes/web.php definiuje trasę cards.reorder w następujący sposób:

Route::put('/cards/reorder', CardsReorderUpdateController::class)->name('cards.reorder');

Uchwyty CardsReorderUpdateController::class aktualizacji kart w bazie danych.

public function __invoke(CardsReorderUpdateRequest $request): RedirectResponse
{
    $data = collect($request->columns)
                ->recursive() // make all nested arrays a collection
                ->map(function ($column) {
                    return $column['cards']->map(function ($card) use ($column) {
                        return ['id' => $card['id'], 'position' => $card['position'], 'column_id' => $column['id']];
                 });
            })
            ->flatten(1)
            ->toArray();

    // Batch
    Card::query()->upsert($data, ['id'], ['position', 'column_id']);

    return back();
}

Weźmy to krok po kroku, spójrzmy na ten kod.

Przede wszystkim korzystamy z CardsReorderUpdateRequest::class [Formularz zapytania] (https://laravel.com/docs/10.x/validation#form-request-validation).

Przyjrzyjmy się rules() funkcji obiektu żądania formularza.

public function rules(): array
{
    return [
        'columns.*.id' => ['integer', 'required', 'exists:\App\Models\Column,id'],
        'columns.*.cards' => ['required', 'array'],
        'columns.*.cards.*.id' => ['required', 'integer', 'exists:\App\Models\Card,id'],
        'columns.*.cards.*.position' => ['numeric', 'required'],
    ];
}

Laravel umożliwia nam sprawdzanie poprawności zagnieżdżonych tablic za pomocą interfejsu API walidacji.

W tym przypadku sprawdzamy poprawność dwupoziomowej tablicy zagnieżdżonej, aby upewnić się, że identyfikator kolumny wszystkie i identyfikator istnieją w bazie danych. Możesz pominąć tę weryfikację, ale jest to gwarancja, że nikt nie może złamać twojej tablicy. Pamiętaj tylko, że dla każdej kolumny i karty w ładunku żądania pojawi się żądanie bazy danych, aby zweryfikować odpowiednie id istniejące w bazie danych.

Wróćmy do kontrolera.

Zaczynamy od zawinięcia ładunku tablicy w kolekcję Laravel.

$data = collect($request->columns)

Zaraz potem konwertuję wszystkie tablice PHP na kolekcje Laravel, na wszystkich poziomach przychodzącego ładunku, używając niestandardowego makra Laravel o nazwierecursive, które zdefiniowałem w AppServiceProvider.

$data = collect($request->columns)->recursive()

Po prostu łatwiej mi zajmować się kolekcjami niż tablicami. W ten sposób mogłem połączyć funkcje w bardziej spójny i wydajny kod.

Kontynuujmy!

Następnie konwertuję przychodzące rekordy na kolekcję obiektów. Każdy obiekt ma następujące właściwości:

  • identyfikator
  • karty pozycja
  • identyfikator kolumny
->map(function ($column) {
    return $column['cards']->map(function ($card) use ($column) {
            return ['id' => $card['id'], 'position' => $card['position'], 'column_id' => $column['id']];
        }
    );
})

Na koniec spłaszczam kolekcję, aby uzyskać węzły liści i przekonwertować je na tablicę.

At the end, we will have an array of objects. Each object specifies the identyfikator, the new card position, and the new identyfikator kolumny in case the card was dragged/dropped to another column.

Na koniec używam metody Laravel Eloquent upsert() do wykonania operacji aktualizacji wsadowej.

Metoda upserts() może wykonywać wstawianie i aktualizacje w tym samym czasie. Jednak w naszym przypadku, ponieważ mamy weryfikację, aby upewnić się, że kolumny i karty naprawdę istnieją w bazie danych, zawsze wykona operację aktualizacji. Chcemy zaktualizować nowe pozycje i kolumny karty.

Z definicji upsert() metoda przyjmuje trzy parametry:

  • $values parametr przechowuje dane, które mają zostać zaktualizowane/wstawione do bazy danych$uniqueBy parametr, aby spróbować najpierw pobrać rekordy (jeśli istnieje)
  • $update parametr jest tablicą kolumn bazy danych
  • do aktualizacji
Card::query()->upsert($data, ['id'], ['position', 'column_id']);

W naszym przypadku $values parametr zawiera tablicę obiektów kart z ich nową pozycją i nową kolumną (jeśli została zmieniona).

The $unique parameter is the card id. Therefore, the upsert() checks if the record exists in the cards database table based on the identyfikator.

Na $update koniec parametr przechowuje tablicę kolumn do aktualizacji w bazie danych. W naszym przypadku chcemy zaktualizować zarówno kolumnyposition, jak i column_id w cards tabeli bazy danych.

Powyższa instrukcja aktualizuje wszystkie karty w ładunku za pomocą jednej instrukcji bazy danych.

insert into `cards` (`column_id`, `created_at`, `id`, `position`, `updated_at`) values (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?) on duplicate key update `position` = values(`position`), `column_id` = values(`column_id`), `updated_at` = values(`updated_at`)",[/* … */]

This way, no matter how many cards are involved, there will be a single database operation to update the new position and identyfikator kolumnys.

Odwołania

Chciałbym wyrazić uznanie dla kilku zasobów, z których korzystałem podczas pisania tego artykułu i budowania jego kodu źródłowego.

Wniosek

Pozostawiłem wiele szczegółów związanych z Vue JS bez wyjaśnienia. Powodem tego jest to, że chciałem skupić się na funkcji przeciągania / upuszczania kart i na tym, jak skutecznie aktualizujemy rekordy z powrotem w bazie danych.

Jeśli chcesz zapytać o kod źródłowy, napisz do mnie swoje pytania i zapytania.

Możesz pobrać repozytorium na GitHub Laravel Kanban i wypróbować go lokalnie.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297