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

Validate without request #717

Open
zaartix opened this issue Nov 4, 2023 · 10 comments
Open

Validate without request #717

zaartix opened this issue Nov 4, 2023 · 10 comments

Comments

@zaartix
Copy link

zaartix commented Nov 4, 2023

I want to make ajax validation durning form fill. So each input is listened by js and saving to database the value, not matter if it valid or not. After save i'm fetching all saved values from db and calling $form->setValue($value);

So, this form is always not valid without $form->setRequest($request);

How to validate in this case? Form filled by setValue without request, because request contains only one field, i don't want to submit whole form each time any field changed.

@zaartix
Copy link
Author

zaartix commented Nov 5, 2023

I've found this place laravel-form-builder/src/Kris/LaravelFormBuilder/Fields/FormField.php:622

if ($this->parent->getRequest()->hasSession()) {
    $errors = $this->parent->getRequest()->session()->get('errors');
}

So, it is impossible to render and validate form without submit?

I've tried to make something like this in my controller

$validator = Validator::make($data, $form->getRules());
$errors = new ViewErrorBag();
$errors->put($form->getErrorBag(), $validator->errors());
return view(
    'application', [
        'form'=> $form,
        'errors' => $errors,
    ]
);

But it's not working :(

@rudiedirkx
Copy link
Collaborator

Interesting case. What's not working? False positives, or false negatives, or exceptions, or what? Your code seems solid (although I didn't test).

@zaartix
Copy link
Author

zaartix commented Nov 5, 2023

Interesting case. What's not working? False positives, or false negatives, or exceptions, or what? Your code seems solid (although I didn't test).

according this place (laravel-form-builder/src/Kris/LaravelFormBuilder/Fields/FormField.php:622):

if ($this->parent->getRequest()->hasSession()) {
    $errors = $this->parent->getRequest()->session()->get('errors');
}

form_row(field) in template is "looking" for errors bag only in session

@zaartix
Copy link
Author

zaartix commented Nov 5, 2023

There is only one way to render form with errors, but without submit:

$validator = Validator::make($data, $form->getRules());
$errors = new ViewErrorBag();
$errors->put($form->getErrorBag(), $validator->errors());
Session::put('errors',$errors); // to put in session by hands
return view(
    'application', [
        'form'=> $form,
    ]
);

But this is bad idea, ErrorBag will be keept for next request.

upd. possible solution is patch for (laravel-form-builder/src/Kris/LaravelFormBuilder/Fields/FormField.php:622)

if ($this->parent->getRequest()->hasSession()) {
    $errors = $this->parent->getRequest()->session()->get('errors');
}
$errors = $this->parent->getRequest()->attributes->get('errors', $errors); // this line

In controller:

$validator = Validator::make($data, $form->getRules());
$errors = new ViewErrorBag();
$errors->put($form->getErrorBag(), $validator->errors());
$request->attributes->add(['errors'=>$errors]); // put errors to request attributes
return view(
    'application', [
        'form'=> $form,
    ]
);

But this is looks dirty

@rudiedirkx
Copy link
Collaborator

Ooooh you want to print the form too. Not just validate a form with previously saved input. Validation and printing the form always happen in a separate request in default Laravel/form-builder, so you'll need some tricks. Or a PR to make the form builder Form better. Your case is valid IMO, and other cases that would need this too, like printing the validated form in the POST request, instead of redirecting with errors in session. Maybe Form->validate() should remember the form's errors, and getErrors() should return those, and FormField should use those... I look forward to seeing a very beautiful PR!

@zaartix
Copy link
Author

zaartix commented Nov 7, 2023

Form->validate() does "not see" values was set by $form->setValue->($value);, i believe it is using only request

This is the reason why i used Validator::make() instead of form->validate()

@zaartix
Copy link
Author

zaartix commented Nov 10, 2023

I can make PR only with this part:

if ($this->parent->getRequest()->hasSession()) {
    $errors = $this->parent->getRequest()->session()->get('errors');
}
$errors = $this->parent->getRequest()->attributes->get('errors', $errors);

but only "as is", if current request have attribute "errors" it is more important then session, because only one way to make request have "errors" - is manually put it in.

part with form->validate() is much complicated, i'm afraid it will require to refactor a lot of code, and it is possible that request attribute "errors" will be used for it.

I'll try to do it

@zaartix
Copy link
Author

zaartix commented Nov 11, 2023

I've did a bit of research:

    // laravel-form-builder/src/Kris/LaravelFormBuilder/Form.php:1260
    public function validate($validationRules = [], $messages = [])
    {
        $fieldRules = $this->formHelper->mergeFieldsRules($this->fields);
        $rules = array_merge($fieldRules->getRules(), $validationRules);
        $messages = array_merge($fieldRules->getMessages(), $messages);

        $this->validator = $this->validatorFactory->make($this->getRequest()->all(), $rules, $messages); // used request
        $this->validator->setAttributeNames($fieldRules->getAttributes());

        $this->eventDispatcher->dispatch(new BeforeFormValidation($this, $this->validator));

        return $this->validator;
    }

So, $form->validate() use request.

I think, that it should be some kind of middle data layer (non validated raw data).

  1. It will be filled as part of $form->setRequest($request)
  2. It will be used for $form->validate()
  3. It will be updated by $field->setValue($value)
  4. I'm not sure about $form->getFieldValues() and similar methods
    // laravel-form-builder/src/Kris/LaravelFormBuilder/Fields/FormField.php:836
    protected function isValidValue($value)
    {
        return $value !== null;
    }

I can't understand why null is not valid value?

@rudiedirkx
Copy link
Collaborator

I think, that it should be some kind of middle data layer (non validated raw data).

I agree.

I can't understand why null is not valid value?

null is not a submittable value, only a default value? I don't know what isValidValue() does, so I don't know if that explains it.

@ukeloop
Copy link
Contributor

ukeloop commented Jan 29, 2024

Currently, this library only supports displaying errors on redirects.

If you want to redirect with separate management data, it's easy.

$data = [
    'username' => '',
];

$form = FormBuilder::plain()
    ->add('username', 'text', [
        'label' => 'User name',
        'rules' => 'required',
    ]);

$form->setRequest(new Request($data));
$form->redirectIfNotValid();

But you want to render forms without redirecting, it's hard.
Field view's $errors variable references shared data.
Field error classes are determined by session's data.
These data must be replaced in order for forms to render.

$data = [
    'username' => '',
];

$form = FormBuilder::plain()
    ->add('username', 'text', [
        'label' => 'User name',
        'rules' => 'required',
    ]);

$form->setRequest($form->getRequest()->duplicate($data));

if (!$form->isValid()) {
    $errors = $form->getRequest()->session()->get('errors', new ViewErrorBag);

    if (! $errors instanceof ViewErrorBag) {
        $errors = new ViewErrorBag;
    }

    $errors->put($form->getErrorBag(), $form->getValidator()->getMessageBag());
  
    $form->getRequest()->session()->now('errors', $errors);

    View::share('errors', $errors);
}

return view('foo', [
    'form' => $form,
]);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants