1
votes

Am working on a Laravel 8 package that swaps the #TALL stack for what I call the #BALL stack, and its basically a Bootstrap, AlpineJs Laravel, Livewire stack. Bootstrap 5 is leaning towards utility classes and no longer makes use of JQuery which gives room for AlpineJs so I really don't see a lot of upsides with Tailwind, not to mention I've gotten really comfortable with Bootstrap which should be all that matters at the end right?

Now the problem is this, I've been able to make changes to a lot of the JetStream blade files and if youve install Jetstream then your familiar with this image:

enter image description here

(Yes!!!! Its the exact same thing but with Bootstrap!)

but one particular component has kept me up for days and its the Bootstrap Modal.

  1. Whenever I call the Bootstrap modal Livewire requests seem to make the Modal itself disappear leaving just the backdrop, forcing a page request before anything else can be clicked.

  2. The final request is never completed

  3. I have no way to hold the modal until a confirmation request is loaded and then programmatically disable the modal when finished.

The exact place I have this problem is in the views/api/api-token-manager.blade which is added by Jetstream when you install the livewire stack. My files looks like so:

My views/api/api-token-manager.blade

<!-- API Token List -->
// Inside a x-jet-action-section component
<x-slot name="content">
    <div class="space-y-6">
    @foreach ($this->user->tokens->sortBy('name') as $token)
        <div class="d-flex justify-content-between">
            <div>
                {{ $token->name }}
            </div>

            <div class="d-flex">
            @if ($token->last_used_at)
                <div class="text-sm text-muted">
                    Last used {{ $token->last_used_at->diffForHumans() }}
                </div>
            @endif

            @if (Laravel\Jetstream\Jetstream::hasPermissions())
                <button class="btn btn-link text-secondary" data-toggle="modal"
                                                wire:click="manageApiTokenPermissions({{ $token->id }})"
                                                data-target="#managingApiTokenPermissions-{{ $token->id }}">
                   Permissions
               </button>
            @endif

            <button class="btn btn-link text-danger text-decoration-none"
                                            data-toggle="modal"
                                            data-target="#confirmApiTokenDeletion-{{ $token->id }}">
                Delete
            </button>
            </div>
        </div>

                            <!-- API Token Permissions Modal -->
                            <x-jet-dialog-modal id="managingApiTokenPermissions-{{ $token->id }}">
                                <x-slot name="title">
                                    API Token Permissions
                                </x-slot>

                                <x-slot name="content">
                                    <div class="mt-2 row">
                                        @foreach (Laravel\Jetstream\Jetstream::$permissions as $permission)
                                            <div class="col-6">
                                                <div class="form-check">
                                                    <input class="form-check-input" type="checkbox" value="{{ $permission }}" wire:model.defer="updateApiTokenForm.permissions">
                                                    <label class="form-check-label">
                                                        {{ $permission }}
                                                    </label>
                                                </div>
                                            </div>
                                        @endforeach
                                    </div>
                                </x-slot>

                                <x-slot name="footer">
                                    <x-jet-secondary-button data-dismiss="modal">
                                        {{ __('Nevermind') }}
                                    </x-jet-secondary-button>

                                    <x-jet-button class="ml-2" wire:click="updateApiToken"
                                                  wire:click="$emit('updateApiToken', {{ $token->id }})"
                                                  data-dismiss="modal">
                                        {{ __('Save') }}
                                    </x-jet-button>
                                </x-slot>
                            </x-jet-dialog-modal>
                            @push('scripts')
                                <script>
                                    Livewire.on('updateApiToken', id => {
                                        @this.manageApiTokenPermissions(id)
                                        @this.updateApiToken()
                                    })
                                </script>
                            @endpush

                            <!-- Delete Token Confirmation Modal -->
                            <x-jet-confirmation-modal id="confirmApiTokenDeletion-{{ $token->id }}">
                                <x-slot name="title">
                                    Delete API Token
                                </x-slot>

                                <x-slot name="content">
                                    Are you sure you would like to delete this API token?
                                </x-slot>

                                <x-slot name="footer">
                                    <x-jet-secondary-button data-dismiss="modal">
                                        Nevermind
                                    </x-jet-secondary-button>

                                    <x-jet-danger-button class="ml-2"
                                                         wire:click="$emit('deleteApiToken', {{ $token->id }})"
                                                         data-dismiss="modal">
                                        Delete
                                    </x-jet-danger-button>
                                </x-slot>
                            </x-jet-confirmation-modal>
                            @push('scripts')
                                <script>
                                    Livewire.on('deleteApiToken', id => {
                                        @this.confirmApiTokenDeletion(id)
                                        @this.deleteApiToken()
                                    })
                                </script>
                            @endpush
                        @endforeach
                    </div>
                </x-slot>

My x-jet-dialog-modal component

@props(['id' => null, 'maxWidth' => null])

<x-jet-modal :id="$id" :maxWidth="$maxWidth" {{ $attributes }}>
    <div class="modal-content">
        <div class="modal-header">
            <h5 class="modal-title">{{ $title }}</h5>
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                <span aria-hidden="true">&times;</span>
            </button>
        </div>
        <div class="modal-body">
            {{ $content }}
        </div>
        <div class="modal-footer">
            {{ $footer }}
        </div>
    </div>
</x-jet-modal>

My x-jet-modal component

@props(['id', 'maxWidth', 'modal' => false])

@php
$id = $id ?? md5($attributes->wire('model'));

switch ($maxWidth ?? '') {
    case 'sm':
        $maxWidth = ' modal-sm';
        break;
    case 'md':
        $maxWidth = '';
        break;
    case 'lg':
        $maxWidth = ' modal-lg';
        break;
    case 'xl':
        $maxWidth = ' modal-xl';
        break;
    case '2xl':
    default:
        $maxWidth = '';
        break;
}
@endphp

<!-- Modal -->
<div class="modal fade" tabindex="-1" id="{{ $id }}" aria-labelledby="{{ $id }}" aria-hidden="true">
    <div class="modal-dialog{{ $maxWidth }}">
        {{ $slot }}
    </div>
</div>

I really don't want to make the change to Tailwind regardless of the hype so I would love any assistance the community can render.

To make a direct contribution to the project click here.

Thanks!!!

1
Awesome project... I think you can post your question on laracasts forum too: laracasts.com/discussTiago Silva Pereira
Ok, thanks...i will right awaytsommie
@TiagoSilvaPereira Its been 7 hours now, no response laracasts.com/discuss/channels/general-discussion/…tsommie
Hi @tsommie do you already solved the problem? I'll try to help you taking an eye on it todayTiago Silva Pereira
I was able to hack it by using Livewire $emit on the modal button, so when the button is clicked i could listen for it with livewire and then call the modal like this: new Bootstrap.Modal(document.getElementById('confirmingUserDeletionModal')).toggle(). It works well but am hoping for a better alternative. Ive come a long way since I posted this question and now am trying to add support for both B4 and B5. I think an alpha version would be ready this week. Thanks a lot for showing interest, any contribution is much appreciated.tsommie

1 Answers

1
votes

I was able to solve the above problem with Livewire Event. Its important to note that the reason for all this hassle was to create assets to replace Jetstream assets without affecting the business logic i.e. MODEL and CONTROLLER.

Here is what my solution looks like:

        <div class="mt-3">
            <x-jet-danger-button wire:click="$emit('confirmingUserDeletion')"
                                 wire:loading.attr="disabled">
                Delete Account
            </x-jet-danger-button>
        </div>

        <!-- Delete User Confirmation Modal -->
        <x-jet-dialog-modal id="confirmingUserDeletionModal">
            <x-slot name="title">
                Delete Account
            </x-slot>

            <x-slot name="content">
                Are you sure you want to delete your account? Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.

                <div class="mt-4 w-75" x-data="{}" x-on:confirming-delete-user.window="setTimeout(() => $refs.password.focus(), 250)">
                    <x-jet-input type="password" class="{{ $errors->has('password') ? 'is-invalid' : '' }}" placeholder="Password"
                                 x-ref="password"
                                 wire:model.defer="password"
                                 wire:keydown.enter="deleteUser" />

                    <x-jet-input-error for="password" />
                </div>
            </x-slot>

            <x-slot name="footer">
                <x-jet-secondary-button wire:click="$toggle('confirmingUserDeletion')"
                                        wire:loading.attr="disabled"
                                        data-dismiss="modal">
                    Nevermind
                </x-jet-secondary-button>

                <x-jet-danger-button wire:click="deleteUser" wire:loading.attr="disabled">
                    Delete Account
                </x-jet-danger-button>
            </x-slot>
        </x-jet-dialog-modal>

    @push('scripts')
        <script>
            Livewire.on('confirmingUserDeletion', () => {
                @this.confirmUserDeletion()
                new Bootstrap.Modal(document.getElementById('confirmingUserDeletionModal')).toggle()
            })
        </script>
    @endpush