Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

livewireValue is not defined #105

Closed
swarakaka opened this issue Feb 2, 2022 · 7 comments
Closed

livewireValue is not defined #105

swarakaka opened this issue Feb 2, 2022 · 7 comments
Labels
bug Something isn't working fixed The issue is resolved.

Comments

@swarakaka
Copy link
Contributor

Returns an error when changing a select several times.

module.esm.js?027e:1656 Uncaught ReferenceError: livewireValue is not defined
    at eval (eval at safeAsyncFunction (module.esm.js:1:1), <anonymous>:3:32)
    at eval (module.esm.js?027e:1718:1)
    at tryCatch (module.esm.js?027e:1645:1)
    at HTMLInputElement.el._x_forceModelUpdate (module.esm.js?027e:2974:1)
    at eval (module.esm.js?027e:2985:1)
    at reactiveEffect (module.esm.js?027e:484:1)
    at Object.effect3 [as effect] (module.esm.js?027e:459:1)
    at effect (module.esm.js?027e:1299:1)
    at eval (module.esm.js?027e:2392:1)
    at wrappedEffect (module.esm.js?027e:1315:1)
<?php

namespace App\Http\Livewire\Forms\Persons;

use App\Models\Person;
use App\Models\Staff;
use Illuminate\Support\Collection;
use phpDocumentor\Reflection\Types\Boolean;
use Tanthammar\TallForms\Input;
use Tanthammar\TallForms\Select;
use Tanthammar\TallForms\TallFormComponent;
use Tanthammar\TallFormsSponsors\DatePicker;
use Tanthammar\TallFormsSponsors\Email;
use Tanthammar\TallFormsSponsors\Tel;

class CreateOrUpdatePerson extends TallFormComponent
{
    public Collection $breads;
    public string  $formTitle = '';
    public array $staffs = [];
    public bool $show_birthday_and_certificate = true;
    public function mount(?Person $person)
    {
        $this->formTitle = $person->exists ?
            __('Edit :item ', ['item'=> $person->name])
            : __('Add :model ', ['model'=> __('Person')]);

        $this->breads = new Collection([
            ['text'=> __('Persons'), 'url'=> 'persons'],
            ['text'=> __($this->formTitle)],

        ]);

        //Gate::authorize()
        $this->mount_form($person); // $person from hereon, called $this->model
    }

    protected function formAttr(): array
    {
        return [
            'wrapWithView' => true,
            'showDelete' => false,
        ];
    }

    // OPTIONAL methods, they already exist
    protected function onCreateModel($validated_data)
    {
        $this->model = Person::create($validated_data);
    }

    protected function onUpdateModel($validated_data)
    {
        $this->model->update($validated_data);
    }

    protected function onDeleteModel()
    {
        $this->defaultDelete();
    }

    protected function beforeFormProperties()
    {
        // set the checkboxes options
        $this->staffs = Staff::orderBy('id','desc')->pluck('id', 'name')->all();
    }
    protected function fields(): array
    {
        return [
            Select::make(__('Staff'), 'staff_id')
                ->options($this->staffs)
                ->rules('required')
                ->colspan(6),

            Input::make(__('Name'),'name')
                ->rules('required')
                ->autocomplete('new-text')
                ->colspan(6),

            Email::make(__('Email'),'email')
                ->autocomplete('new-email')
                ->colspan(6),

            Tel::make(__('Phone'),'phone')
                ->autocomplete('new-tel')
                ->colspan(6),

           $this->show_birthday_and_certificate ? DatePicker::make(__('Birthday'),'birthday')
                ->locale('en')
                ->placeholder('Select a date...')
                ->includeExternalScripts() //only included once, if multiple DatePicker fields
                ->colspan(6) : null,

           $this->show_birthday_and_certificate ? Input::make(__('Certificate'),'certificate')
                ->rules('nullable')
                ->autocomplete("off")
                ->colspan(6) : null,

            Input::make(__('Address'),'address')
                ->rules('nullable')
                ->autocomplete('new-address'),
        ];
    }

    public function updatedStaffId($value)
    {
        if(filled($value) && ($value === "2" || $value === "5")){
            $this->show_birthday_and_certificate = false;
        }else{
            $this->show_birthday_and_certificate = true;
        }
    }

}
@tanthammar
Copy link
Owner

tanthammar commented Feb 2, 2022

There are two problems.
It is impossible to use "includeExternalScripts" with a conditional field.
The external script is pushed to 'scripts' if the value is true, when Livewire mounts the component, and removed when the value becomes false, (and it can't be pushed later).
The other problem is that the component won't load "birthday" and "certificate" if the condition is false when the component is mounted.

To get around it you will have to

  1. Bundle the Flatpickr script in your app.js and remove the "includeExternalScripts" in your field declaration. This also applies to Flatpickr locales.
  2. Manually set "birthday" and "certificate" in your updatedStaffId method
    public function updatedStaffId($value)
    {
        if(filled($value) && ($value === "2" || $value === "5")){
            $this->show_birthday_and_certificate = false;
        } else {
            data_set($this, 'form_data.birthday', ($this->model->birthday ?? "") );
            data_set($this, 'form_data.certificate', ($this->model->certificate ?? "") );
            $this->show_birthday_and_certificate = true;
        }
    }

@swarakaka
Copy link
Contributor Author

Thank you for your answer, the problem wasn't solved, but it's the same.

@tanthammar
Copy link
Owner

@swara-mohammed
I tested your code and there is a bug.
Will work on it today.

@tanthammar tanthammar added bug Something isn't working In progress Working on this issue labels Feb 3, 2022
@tanthammar
Copy link
Owner

tanthammar commented Feb 3, 2022

Hello again @swara-mohammed

I figured out what is going wrong here :)

When Livewire hides the DatePicker, FlatPickr doesn't destroy the instance, so "half" of the html is still in the DOM.
Alpine cant find it's x-data, because it is removed.

I have a wire:ignore set on the FlatPickr div, which I can't remove because Livewire will destroy the instance on each render...

So the only way I can figure out at this moment is to add class hidden to the field root.
This means that you can use ->includeExternalScripts().
Also please install latest version, I found a bug when merging rootAttr with an empty array.

OBSERVE

When hiding the field with classes, the data properties will always exist in $form_data and $validated_data
This is important for you to know in onCreateModel($validated_data) and onUpdateModel($validated_data)

You might want to filter out those properties when saving your component. Depending on the state of $show_birthday_and_certificate

Suggestion for your component.

<?php

namespace App\Http\Livewire\Forms\Persons;

use App\Models\Person;
use App\Models\Staff;
use Illuminate\Support\Collection;
use Tanthammar\TallForms\Input;
use Tanthammar\TallForms\Select;
use Tanthammar\TallForms\TallFormComponent;
use Tanthammar\TallFormsSponsors\DatePicker;
use Tanthammar\TallFormsSponsors\Email;
use Tanthammar\TallFormsSponsors\Tel;

class CreateOrUpdatePerson extends TallFormComponent
{
    public Collection $breads;
    public string $formTitle = '';
    public array $staffs = [];
    public bool $show_birthday_and_certificate = true;

    public function mount(?Person $person)
    {
        //Gate::authorize()

        $this->formTitle = $person->exists ?
            __('Edit :item ', ['item' => $person->name])
            : __('Add :model ', ['model' => __('Person')]);

        $this->breads = new Collection([
            ['text' => __('Persons'), 'url' => 'persons'],
            ['text' => __($this->formTitle)],

        ]);

        // set the checkboxes options
        $this->staffs = Staff::orderBy('id', 'desc')->pluck('id', 'name')->all();

        $this->mount_form($person); // $person from hereon, called $this->model
    }

    protected function formAttr(): array
    {
        return [
            'formTitle' => $this->formTitle,
            'wrapWithView' => true,
            'showDelete' => false,
            'inline' => true
        ];
    }

    protected function onCreateModel($validated_data): void
    {
        Person::create($this->filterValidatedData($validated_data));
    }

    protected function onUpdateModel($validated_data): void
    {
        $this->model->update($this->filterValidatedData($validated_data));
    }

    protected function filterValidatedData($validated_data): array
    {
        return $this->show_birthday_and_certificate ? $validated_data : \Arr::except($validated_data, ['birthday', 'certificate']);
    }

    //optional method, identical to parent
    protected function onDeleteModel()
    {
        $this->defaultDelete();
    }

    public function updatedStaffId($value): void
    {
        if (($value === "2" || $value === "5") && filled($value)) {
            $this->show_birthday_and_certificate = false;
        } else {
            $this->show_birthday_and_certificate = true;
        }
    }

    protected function fields(): array
    {
        return [
            Select::make(__('Staff'), 'staff_id')
                ->options($this->staffs, false) //I think your data is [ $id => $label ]? If not, remove false, or change pluck()
                ->rules('required'),

            Input::make(__('Name'), 'name')
                ->rules('required')
                ->autocomplete('new-text'),

            Email::make(__('Email'), 'email')
                ->autocomplete('new-email'),

            Tel::make(__('Phone'), 'phone')
                ->autocomplete('new-tel'),

            DatePicker::make(__('Birthday'), 'birthday')
                ->rootAttr($this->show_birthday_and_certificate ? [] : ['class' => 'hidden'], true)
                ->locale('en')
                ->placeholder('Select a date...')
                ->includeExternalScripts(), //only included once, if multiple DatePicker fields

            Input::make(__('Certificate'), 'certificate')
                ->rootAttr($this->show_birthday_and_certificate ? [] : ['class' => 'hidden'], true)
                ->rules('nullable')
                ->autocomplete("off"),

            Input::make(__('Address'), 'address')
                ->rules('nullable')
                ->autocomplete('new-address'),
        ];
    }
}

@tanthammar tanthammar added fixed The issue is resolved. and removed In progress Working on this issue labels Feb 3, 2022
@tanthammar
Copy link
Owner

I will add this to the documentation as well.

@swarakaka
Copy link
Contributor Author

swarakaka commented Feb 3, 2022

Thank you for your solution, but I felt another bug. I'll write the solution below.

implode(): Argument #2 ($array) must be of type ?array, string given

protected function mergeClasses(string $key, array $custom): void
    {
        $merged = array_merge_recursive($this->attributes[$key], $custom);
        if (Arr::has($merged, 'class')) {
            $merged['class'] = implode(" ", $merged['class']);
        }
        $this->attributes[$key] = $merged;
    }

solved:

protected function mergeClasses(string $key, array $custom): void
    {
        $merged = array_merge_recursive($this->attributes[$key], $custom);
        if (Arr::has($merged, 'class') && is_array($merged['class'])) {
            $merged['class'] = implode(" ", $merged['class']);
        }
        $this->attributes[$key] = $merged;
    }

@tanthammar
Copy link
Owner

@swara-mohammed Yes, that is why I wrote you need to upgrade to the latest version. I discovered that bug when I tested your component. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed The issue is resolved.
Projects
None yet
Development

No branches or pull requests

2 participants