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.
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<li
elementu > 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 currentCard
wł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 column
input .
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ę cards
pliku . 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.