filamentphp / filament

A collection of beautiful full-stack components for Laravel. The perfect starting point for your next app. Using Livewire, Alpine.js and Tailwind CSS.
https://filamentphp.com
MIT License
16.16k stars 2.57k forks source link

Modal Actions #10581

Open tareq-alqadi opened 6 months ago

tareq-alqadi commented 6 months ago

Package

filament/filament

Package Version

v3.1

Laravel Version

v10

Livewire Version

v3

PHP Version

PHP 8.2

Problem description

When the modal (any modal) opened and closed the all modals in the same component will become unresponsive until the first modal completed. This problem will appear obviously if you have slow connection or your server is slow, and this will be very annoying.

I have reported this issue before and was told that there is no solution and that appropriate indicators have been used.

But when the modal close the button to open the modal will be disabled but if i am clicked directly on this button it will give me loading indicator after a while and nothing will happened because the network requests not finished at this moment (this will happen if connection is slow).

Expected behavior

Why we need two network requests after modal closing? Is that because the first request using to dispatch event from livewire and livewire will dispatch the event from the Alpinejs which will send the second request? If this case we can send the event from Alpinejs directly.

And we need more robust and expressive indicator in this case.

Finally why you need to stop opening modals when previous modals closing? is this for refreshing table because some records may deleted or updated? can we disable actions only on these records?

Steps to reproduce

demo with set throttle on network

Reproduction repository

https://github.com/filamentphp/demo

Relevant log output

No response

Donate 💰 to fund this issue

Fund with Polar

danharrin commented 6 months ago

Which modal in the demo reproduces this problem? Can you send a screen recording?

tareq-alqadi commented 6 months ago

https://github.com/filamentphp/filament/assets/70425045/a1897ab3-d6a8-4668-9526-e8ad33261abf

https://demo.filamentphp.com/blog/posts

This not happened only in this page any other page with modal have same behavior.

eico4te commented 5 months ago

I have recorded the steps of the issue I'm facing. Basically after closing a modal window I need to click the button/link twice to work or wait a couple of seconds before trying to click again. The issue is present on a localhost so I don't think it is related with network speed issue.

Recorded Steps.pdf

To replicate issue: Click View. Click Close Button. Click View (immediately) or Click Edit. Click Cancel Button. Click Edit (immediately) or Click Delete. Click Cancel Button. Click Delete (immediately)

ronylicha commented 4 months ago

i have the same problem, on local or on server

Sprunkas commented 2 months ago

I have same problem here. When "close-modal" event is fired, but not finished to execute and you want to open again modal - modal won't open and you need to click second time. And in request you have two events: "open-modal" and "close-modal": image

phattarachai commented 2 months ago

Yes, I have the same problem on the production server with a bit slower network.

joao-antonio-gomes commented 1 month ago

I have same problem, but in my case even on the first time I click the button, I need to click a second time to modal open.

https://github.com/filamentphp/filament/assets/66046251/06af56a9-4cc3-4cbf-87e1-f76d94a579aa

Table.php

<?php

namespace App\Livewire\ProposalHistoric;

use App\Models\ProposalHistoric;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Tables;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Table;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Livewire\Component;

class ProposalHistoricTable extends Component implements HasForms, HasTable {
    use InteractsWithForms;
    use InteractsWithTable;

    public $record;

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

    public function table(): Table {
        return $this
            ->makeTable()
            ->searchable(false)
            ->query(ProposalHistoric::query()->where('proposal_id', $this->record->id)->orderBy('date', 'desc'))
            ->columns([
                Tables\Columns\TextColumn::make('user.name')
                    ->width('15%')
                    ->label('Usuário'),
                Tables\Columns\TextColumn::make('observation')
                    ->label('Observação')
                    ->width('30%')
                    ->wrap(),
                Tables\Columns\TextColumn::make('forwarded_to')
                    ->width('15%')
                    ->label('Encaminhado para')
                    ->searchable(),
                Tables\Columns\TextColumn::make('date')
                    ->width('15%')
                    ->label('Data')
                    ->dateTime('d/m/Y H:i')
                    ->sortable(),
                Tables\Columns\TextColumn::make('next_contact_date')
                    ->width('15%')
                    ->label('Próximo contato')
                    ->dateTime('d/m/Y H:i')
                    ->sortable(),
            ])
            ->filters([
                //
            ])
            ->headerActions([

                Tables\Actions\Action::make('newHistoric')
                    ->label('Novo histórico')
                    ->modal()
                    ->form([
                        Textarea::make('observation')
                            ->label('Observação')
                            ->rows(4)
                            ->required()
                            ->columnSpanFull(),
                        DateTimePicker::make('date')
                            ->label('Data')
                            ->maxDate(now())
                            ->required()
                            ->displayFormat('DD/MM/YYYY HH:mm')
                            ->seconds(false)
                            ->default(now())
                            ->columnSpan(1),
                        DateTimePicker::make('next_contact_date')
                            ->label('Próximo contato')
                            ->displayFormat('DD/MM/YYYY HH:mm')
                            ->seconds(false)
                            ->columnSpan(1),
                    ])
                    ->action(function (array $data, ProposalHistoric $historic) {
                        $historic->fill($data);
                        $historic->user_id = auth()->id();
                        $historic->proposal_id = $this->record->id;
                        $historic->save();
                    }),
            ])
            ->actions([
                Tables\Actions\EditAction::make()
                    ->recordTitle('histórico')
                    ->form([
                        Textarea::make('observation')
                            ->label('Observação')
                            ->rows(4)
                            ->required()
                            ->columnSpanFull(),
                        DateTimePicker::make('date')
                            ->label('Data')
                            ->maxDate(now())
                            ->required()
                            ->displayFormat('DD/MM/YYYY HH:mm')
                            ->seconds(false)
                            ->columnSpan(1),
                        DateTimePicker::make('next_contact_date')
                            ->label('Próximo contato')
                            ->displayFormat('DD/MM/YYYY HH:mm')
                            ->seconds(false)
                            ->columnSpan(1),
                    ]),
                Tables\Actions\DeleteAction::make()
                    ->recordTitle('histórico'),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    //
                ]),
            ]);
    }

    public function render(): View {
        return view('livewire.proposal-historic.proposal-historic-table');
    }

    protected function getTableQuery(): Builder {
//        return ProposalHistoric::query();
        return ProposalHistoric::query()
            ->where('proposal_id', $this->record->id);
    }
}

Resource.php

/// ...etc
                        Tabs\Tab::make('Histórico')
                            ->schema([
                                $referenceField,
                                View::make('forms.components.proposal-historic-table')
                                    ->columnSpanFull(),
                            ])
                            ->columns(2)
                            ->hidden($isCreation),
/// ...etc

I also realize this difference between two requests. In 1st request, on montedTableActionsData, doesnt have any informations, but in 2nd request on same property, has informations relative with opened modal.

1st request

    "mountedTableActionsData": [
      [],
      {
        "s": "arr"
      }
    ],
    "mountedTableActionsArguments": [
      [],
      {
        "s": "arr"
      }
    ],

2nd request

    "mountedTableActionsData": [
      [
        [
          {
            "observation": null,
            "date": "2024-05-22 01:03",
            "next_contact_date": null
          },
          {
            "s": "arr"
          }
        ]
      ],
      {
        "s": "arr"
      }
    ],
    "mountedTableActionsArguments": [
      [
        [
          [],
          {
            "s": "arr"
          }
        ]
      ],
      {
        "s": "arr"
      }
    ],

Edit: After the modal finally opens, the modal did not close if I click away (expected behavior), only if I click on close/cancel, it's nice! But, if I try to open modal again (without refresh), now modal open on 1st click and close if I click away (I did not expect this behavior, I'm not using closeModalByClickingAway property If I add the closeModalByClickingAway property, reload page and try to click away on 1st open, modal wont close

tareq-alqadi commented 1 month ago

@danharrin

When a user closes a modal, the close function runs, which closes the modal and fires the modal-closed event. This happens in vendor\filament\support\resources\views\components\modal\index.blade.php. image

Then the modal-closed listener runs and makes a request to the server using $wire.unmountTableAction(false). This occurs in vendor\filament\actions\resources\views\components\modals.blade.php. image

The unmountTableAction function will dispatch the close-modal event, which will run the close function again. image

This does not run indefinitely because of the following code in the modal-closed listener: ` if (! mountedTableActionShouldOpenModal) { return }

if ($wire.mountedFormComponentActions.length) {
    return
}

`

We can remove the repeated requests after closing the modal by not reclosing the modal in $wire.unmountTableAction(false), or by checking the isOpen flag before firing the modal-closed event again in vendor\filament\support\resources\views\components\modal\index.blade.php: ` close: function () { if(this.isOpen) this.isOpen = false

        this.$refs.modalContainer.dispatchEvent(
            new CustomEvent('modal-closed', { id: '{{ $id }}' }),
        )
    }

    {{-- this.$nextTick(() => {
        if (document.getElementsByClassName('fi-modal-open').length) {
            return
        }

        window.clearAllBodyScrollLocks()
    }) --}}
},

` Alternatively, by adding the once modifier to x-on:modal-closed.stop.

After any of these changes, only a single request will be sent to the server after closing the modal. However, any modals that contain forms will still have the problem.

bgurney commented 2 weeks ago

Hey @danharrin, I am keen to resolve this issue as it is causing us a few issues. Happy to fund it and also input on it if needed as I'm using this in a paid project.

For context, these are the symptoms I'm experiencing:

  1. Notifications that display on action modal close seem to be triggered twice (and cause the animation to restart) due to the additional request that is sent.
  2. Attempts to reopen the action modal, before the additional request is finished, results in nothing happening.
  3. Livewire requests that are sent, before the additional request is finished, don't seem to update the DOM properly.
danharrin commented 2 weeks ago

Hi @bgurney, gonna be honest, this issue isn't a top priority for me right now, I am super busy with getting v4 ready. I don't really experience these problems myself / they haven't been reported by my users to degrade the UX too much. I may be able to put time into it if it is funded a couple of months after v4 is released.

If you fund the issue before then, another member of the team might be willing to do some research themselves into the problem and look into optimisations. If this does not happen, Polar will refund you if the issue is not completed in 6 months.

bgurney commented 2 weeks ago

Hey @danharrin thanks for the detailed reply, appreciate it. Looking forward to some of the great new features you guys are working on! I'll take a look into this issue myself and maybe get in touch with one of the other guys in the team as you've suggested.