Livewire

Laravel Sortable & Trello Clone

Hi @alex,

I've watched the course on creating a Trello clone which I'm building for our custom platform.

I've created a custom package and created all the files and it's working perfectly, except for livewire sortable.

I've updated the NPM package as it's been renamed and I've also tried implementing their CDN version but it just doesn't seem to work.

The script is loading and initialising in that it's available in the window but wire:sortable does not seem to work at all.

Since this is a package I use the livewire layout definition to load the core package admin interface, which works fine. I also created a component layout so that we can use that within the package itself to give us more control over the layout and add an additional admin menu specifically for the package. Even with that removed the package loads into the core package fine and I can see the sortable script being added to the js stack but it just doesn't work.

Any ideas?

Cheers

Derek

derekbuntin Member
derekbuntin
0
7
211
alex Member
alex
Moderator

Hey Derek

Any errors/messages in the console? Also, if you could add any relevant code snippets here that would be super helpful.

I'm guessing by your own package, you're creating an installable package?

derekbuntin Member
derekbuntin

Hi Alex,

Yes, it's an installable package, just like any other one but I added code to the core Cms package we have and that brings in admin views etc so it should work fine.

There are no errors on the console and I've installed this via both NPM and CDN but to no avail.

Here is the board index:

<x-boards::layouts.admin>
    <x-slot name="header">
        <div class="md:flex md:items-center md:justify-between py-4">
            <h1 class="text-2xl font-semibold text-gray-900">
                {{ $workspace->title }}
            </h1>
        </div>
    </x-slot>

    <div>
        <div class="sm:px-6 lg:px-8 xl:p-0 grid grid-cols-4 md:grid-cols-6 gap-6">
            @foreach($boards as $board)
                <a href="{{ route('boards.admin.show', ['workspace' => $workspace->slug, 'board' => $board->slug]) }}" class="bg-white overflow-hidden shadow-sm sm:rounded-lg h-36 flex items-end p-6 text-gray-900 text-lg">
                    {{ $board->title }}
                </a>
            @endforeach

            <button class="bg-gray-200 overflow-hidden shadow-sm sm:rounded-lg h-36 flex items-center justify-center p-6 text-gray-900 text-lg space-x-1"
                    wire:click="$dispatch('openModal', { component: 'modals.create-board', arguments: { workspace_id: {{ $workspace->id }}}})">
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
                </svg>
                <span>New board</span>
            </button>
        </div>
    </div>

@push('js')
    @livewire('wire-elements-modal')
@endpush
</x-boards::layouts.admin>

when I remove the layouts.admin view it removes the view and places it directly into our core Cms package admin template.

These views are nested but that shouldn't make a difference.

Everything else works, just not the livewire sortable script.

Is there anything else you need to see?

derekbuntin Member
derekbuntin

This is the boards.show view, obviously need to see this as that's where the ordering happens, lol:

<x-boards::layouts.admin>
    <div>
        <x-slot name="header">
            <div class="flex items-center justify-between w-full">
                <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                    {{ $board->title }}
                </h2>

                <x-boards::dropdown>
                    <x-slot name="trigger">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
                        </svg>
                    </x-slot>
                    <x-slot name="content">
                        <x-boards::dropdown-button x-on:click="Livewire.dispatch('openModal', { component: 'modals.card-archive', arguments: { board: '{{ $board->slug }}' } })">
                            Archived cards
                        </x-boards::dropdown-button>
                        <x-boards::dropdown-button x-on:click="Livewire.dispatch('openModal', { component: 'modals.column-archive', arguments: { board: '{{ $board->slug }}' } })">
                            Archived columns
                        </x-boards::dropdown-button>
                    </x-slot>
                </x-boards::dropdown>
            </div>
        </x-slot>

        <div class="w-full p-6 overflow-x-scroll">
            <div
                class="flex w-max space-x-6 h-[calc(theme('height.screen')-64px-73px-theme('padding.12'))]"
                wire:sortable="sorted"
                wire:sortable-group="moved"
                wire:sortable.options="{ ghostClass: 'opacity-20' }"
            >
                @foreach ($columns as $column)
                    <div wire:key="column-{{ $column->id }}" wire:sortable.item="{{ $column->id }}">
                        <livewire:column :key="$column->id" :column="$column" />
                    </div>
                @endforeach

                <div
                    x-data="{ adding: false }"
                    x-on:column-created.window="adding = false"
                >
                    <template x-if="adding">
                        <form wire:submit="createColumn" class="bg-white shadow-sm px-4 py-3 rounded-lg w-[260px]">
                            <div>
                                <x-boards::input-label for="title" value="Title" class="sr-only" />
                                <x-boards::text-input id="title" placeholder="Column title" class="w-full" wire:model="createColumnForm.title" x-init="$el.focus()" />
                                <x-boards::input-error :messages="$errors->get('createColumnForm.title')" class="mt-1" />
                            </div>

                            <div class="flex items-center space-x-2 mt-2">
                                <x-boards::primary-button>
                                    Create
                                </x-boards::primary-button>
                                <button x-on:click="adding = false" type="button" class="text-sm text-gray-500">Cancel</button>
                            </div>
                        </form>
                    </template>

                    <button x-show="!adding" x-on:click="adding = true" class="bg-gray-200 shadow-sm px-4 py-3 flex items-center space-x-1 rounded-lg w-[260px]">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
                        </svg>
                        <span>Add a column</span>
                    </button>
                </div>
            </div>
        </div>
    </div>
</x-boards::layouts.admin>
derekbuntin Member
derekbuntin

Here are the card and columns views:

Card:

<div
    wire:sortable-group.handle
    wire:click="$dispatch('openModal', { component: 'modals.edit-card', arguments: { card: {{ $card->id }} } })"
    class="cursor-pointer"
>
    <div class="bg-gray-200 rounded-lg px-3 py-1.5 flex items-center justify-between">
        <div>{{ $card->title }}</div>

        @if ($card->notes)
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
                 stroke="currentColor" class="size-4 text-gray-500">
                <path stroke-linecap="round" stroke-linejoin="round"
                      d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/>
            </svg>
        @endif
    </div>
</div>

Column:

<div class="w-[260px] bg-gray-50 self-start shrink-0 rounded-lg shadow-md max-h-full flex flex-col">
    <div class="flex items-center justify-between">
        <div
            x-data="{ editing: false }"
            x-on:click.outside="editing = false"
            class="h-8 w-full flex items-center px-4 pr-0 min-w-0"
        >
            <button
                class="text-left w-full font-medium"
                x-on:click="editing = true"
                x-on:column-updated.window="editing = false"
                x-show="!editing"
            >
                {{ $column->title }}
            </button>

            <template x-if="editing">
                <form wire:submit="updateColumn" class="-ml-[calc(theme('margin[1.5]')+1px)] grow">
                    <x-boards::text-input value="Column title" class="h-8 px-1.5 w-full" wire:model="editColumnForm.title" x-init="$el.focus()" />
                </form>
            </template>
        </div>
        <div class="px-3.5 py-3">
            <x-boards::dropdown>
                <x-slot name="trigger">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
                    </svg>
                </x-slot>
                <x-slot name="content">
                    <x-boards::dropdown-button wire:click="archiveColumn">
                        Archive
                    </x-boards::dropdown-button>
                </x-slot>
            </x-boards::dropdown>
        </div>
    </div>
    <div
        class="p-3 space-y-1.5 pt-0 overflow-y-scroll"
        wire:sortable-group.item-group="column-group-{{ $column->id }}"
        wire:sortable-group.options="{ ghostClass: 'opacity-20' }"
    >
        @foreach ($cards as $card)
            <div wire:key="card-{{ $card->id }}" wire:sortable-group.item="{{ $card->id }}">
                <livewire:card :key="$card->id" :card="$card" />
            </div>
        @endforeach
    </div>
    <div
        class="p-3"
        x-data="{ adding: false }"
        x-on:card-created.window="adding = false"
    >
        <template x-if="adding">
            <form wire:submit="createCard">
                <div>
                    <x-boards::input-label for="title" value="Title" class="sr-only" />
                    <x-boards::text-input id="title" placeholder="Column title" class="w-full" wire:model="createCardForm.title" x-init="$el.focus()" />
                    <x-boards::input-error :messages="$errors->get('createCardForm.title')" class="mt-1" />
                </div>

                <div class="flex items-center space-x-2 mt-2">
                    <x-boards::primary-button>
                        Create
                    </x-boards::primary-button>
                    <button x-on:click="adding = false" type="button" class="text-sm text-gray-500">Cancel</button>
                </div>
            </form>
        </template>

        <button x-show="!adding" x-on:click="adding = true" class="flex items-center space-x-2">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5">
                <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
            </svg>
            <span>Add a card</span>
        </button>
    </div>
</div>
alex Member
alex
Moderator

Thanks for these, I’ll experiment today and see what might be going wrong here!

alex Member
alex
Moderator

Just checking back in on this — could you let me know where you're adding the sortable script in your package?

derekbuntin Member
derekbuntin

I add that to the component layout at the bottom and it is added to the main template, the modals work perfectly and I've added code to test that the script is loading too, which it seems to be fine.

@push('js')
    @once
        <script src="https://unpkg.com/@wotz/livewire-sortablejs@1.0.0/dist/livewire-sortable.js"></script>
        @livewire('wire-elements-modal')
    @endonce
@endpush

I have also tried placing this on the main template view on the core package but to no avail.

I did develop my own version of this for our admin index pages for ordering so I could likely extend that to work with nested elements.