alpinejs / alpine

A rugged, minimal framework for composing JavaScript behavior in your markup.
https://alpinejs.dev
MIT License
28.19k stars 1.23k forks source link

Dynamic modal with Alpine.js and Livewire #267

Closed oliverbj closed 4 years ago

oliverbj commented 4 years ago

Hi all

I am trying to create a simple modal using Alpine.js. I have a simple foreach loop in my Blade view:

@foreach($records as $record)
<div x-data="{ open: false }">
    <button x-on:click="open = true">Edit Record</button>
    <div x-show="open">
        <div class="modal fixed w-full h-full top-0 left-0 flex items-center justify-center">
            <div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"
                 x-on:click="open = false"></div>
            <div
                class="modal-container bg-white w-5/6 md:max-w-2xl mx-auto rounded shadow-lg z-50 overflow-y-auto cursor-auto">
                <div
                    class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50">
                </div>

                <div class="modal-content py-4 text-left px-6">

                    <!--Body-->
                    @livewire('consols.edit-record', $record->id, key($record->id))

                </div>
            </div>
        </div>
    </div>

</div>
@endforeach

So the above loop creates a new modal for each record that exists in my model. For each of these modals, it will load an unique Livewire view, using the @livewire directive.

This method is causing some weird behavoir, and I think it is because I just check if open = true, but this will be the case for all modal created in the loop.

Does anyone have any experience using Alpine.js with dynamic models? Preferable with Livewire to control the actual database update.

HugoDF commented 4 years ago

That's odd, each div with x-data on it should be creating a new Alpine component with its own instance data

oliverbj commented 4 years ago

Maybe the opening div with x-data should not be included in the loop?

HugoDF commented 4 years ago

I would expect the behaviour you're seeing if it wasn't inside the for loop actually.

ryatkins commented 4 years ago

Is the blade file you've posted a Livewire component? If so, I would put a wire:key on the top level div so that Livewire can differentiate the div within the DOM.

<div wire:key="{{ $record->id }}" x-data="{ open: false }">

Edit: Just realized you have a key() on the Livewire component include. Perhaps add an ID on the top level div?

bbcreation commented 4 years ago

I have the same problem. Have you solved the problem? I will add form myself: https://github.com/livewire/livewire/issues/726

calebporzio commented 4 years ago

Please describe exactly what is happening here versus what you expect to happen. It's unclear to me what your problem is

bbcreation commented 4 years ago

client-table.blade.php

                    @foreach ($clients as $key => $client)
                                @can('delete client')
                                    <livewire:remove-modal :modal="$client" :key="$client->id">
                                @endcan
                    @endforeach

ClientTable.php

public function render()
    {
        $clients = Client::search($this->search)
            ->ofType($this->type)
            ->ofVip($this->vip)
            ->ofPesel($this->pesel)
            ->ofNip($this->nip)
            ->ofLang($this->lang)
            ->ofNumber($this->number)
            ->orderBy($this->sortField, $this->sortAsc ? 'asc' : 'desc')
            ->paginate($this->perPage);

        $this->totalPages = $clients->lastPage();
        $types = ClientTypes::all();

        return view('livewire.client-table', [
            'clients' => $clients,
            'types' => $types,
        ]);
    }

remove-modal.blade.php

<div x-data="{ open: false }" style="float:right" class="ml-1">
    <button x-on:click.prevent="open = !open" class="inline-flex items-center px-2.5 py-2.5 border-transparent text-xs leading-4 font-medium rounded text-white bg-red-400 hover:bg-red-500 focus:outline-none focus:border-red-600 focus:shadow-outline-red active:bg-red-600 transition ease-in-out duration-150"><i class="fas fa-trash"></i></button>

    <div x-show="open" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center hidden">

        <div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity">
            <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
        </div>

        <div x-show="open" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100" x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6">
            <div class="sm:flex sm:items-start">
                <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
                    <svg class="h-6 w-6 text-red-600" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
                    </svg>
                </div>
                <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
                    <h3 class="text-lg leading-6 font-medium text-gray-900">
                        {{__('Confirmation of remove')}}
                    </h3>
                    <div class="mt-2">
                        <p class="text-sm leading-5 text-gray-500">
                            {{__('Are you sure you want to delete the data? All your information will be deleted from our servers forever. This action cannot be undone.')}}
                        </p>
                    </div>
                </div>
            </div>
            <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
      <span class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
        <button @click="open = false" wire:click="remove" type="button" class="inline-flex justify-center w-full rounded-md border border-transparent px-4 py-2 bg-red-600 text-base leading-6 font-medium text-white shadow-sm hover:bg-red-500 focus:outline-none focus:border-red-700 focus:shadow-outline-red transition ease-in-out duration-150 sm:text-sm sm:leading-5">
          {{__('I confirm remove')}}
        </button>
      </span>
                <span class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto">
        <button @click="open = false" type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-base leading-6 font-medium text-gray-700 shadow-sm hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline transition ease-in-out duration-150 sm:text-sm sm:leading-5">
          {{__('Cancal')}}
        </button>
      </span>
            </div>
        </div>
    </div>
</div>

RemoveModal.php

class RemoveModal extends Component
{
    public $modal = null;

    public function mount($modal)
    {
        $this->modal = $modal;
    }

    public function remove()
    {
        try {
            $this->modal->delete();
            Flash::message('The row has been remove correctly.')->success();
        } catch (Exception $exception) {
            Flash::message('The row has not been remove.')->error();
        }
            $this->redirect('#');
    }

    public function render()
    {
        return view('livewire.remove-modal');
    }
}

Searching, paging works correctly. He writes something to look for and finds records for me. Everything works. I start to delete two characters from the search engine and a modal pops up for me. Depending on whether I use @livewire or <livewire>, instead of modal an error is shown in the console as below. Sometimes the same happens as I switch pages. I expect there would be no error or modal would not pop up. As I enter, instead of the pop-up modal, let's static text, e.g. <div> TEST </div>, everything works fine. The problem is when alpinejs is added.

index.js:20 Uncaught (in promise) TypeError: Cannot read property 'data' of null
    at new Component (index.js:20)
    at onNodeAdded (index.js:325)
    at callHook (morphdom.js:35)
    at handleNodeAdded (morphdom.js:168)
    at handleNodeAdded (morphdom.js:187)
    at handleNodeAdded (morphdom.js:187)
    at morphdom.js:430
    at morphEl (morphdom.js:242)
    at morphdom.js:355
    at morphEl (morphdom.js:242)
calebporzio commented 4 years ago

Can you possibly distill this down to the smallest-possible, most-generic, bit of code so others can easily copy and paste to reproduce? This will help pin-point the exact issue. Thanks!

bbcreation commented 4 years ago

Minimized. I just pasted all the code because it happens in this case. See it there, someone pasted GIF. https://github.com/livewire/livewire/issues/726 These are related problems. I don't know where to look for the problem. I sat for two-three days to find a problem.

HugoDF commented 4 years ago

Looks like the related LiveWire issue got resolved, I'll close this for now.

Feel free to reopen this issue if the issues persist.