mzur / kirby-uniform

A versatile Kirby plugin to handle web form actions.
https://kirby-uniform.readthedocs.io
MIT License
251 stars 40 forks source link

Dynamic form fields #110

Closed mzur closed 5 years ago

mzur commented 7 years ago

Currently the form does not process form data that is not specified in the constructor array. But there are possible use cases where form fields are added dynamically (via JS), e.g. with a shopping cart. One solution might be allowing wildcard form fields like item-* in the constructor array. Another idea is to use array form fields for this.

schnti commented 7 years ago

There are already news?

mzur commented 7 years ago

Nope. Do you have a specific use case?

schnti commented 7 years ago

Add dynamically family members to an youth hostel apply now form.

screenshot 2017-05-20 16 56 42

Currently I have five fixed fields as workaround.

    // family
    'familyFirstName-0' => [],
    'familyFirstName-1' => [],
    'familyFirstName-2' => [],
    'familyFirstName-3' => [],
    'familyFirstName-4' => [],
    'familyLastName-0' => [],
    'familyLastName-1' => [],
    'familyLastName-2' => [],
    'familyLastName-3' => [],
    'familyLastName-4' => [],
    'familyBirthday-0' => [],
    'familyBirthday-1' => [],
    'familyBirthday-2' => [],
    'familyBirthday-3' => [],
    'familyBirthday-4' => [],
    'familyGender-0' => [],
    'familyGender-1' => [],
    'familyGender-2' => [],
    'familyGender-3' => [],
    'familyGender-4' => [],
    'familyBed-0' => [],
    'familyBed-1' => [],
    'familyBed-2' => [],
    'familyBed-3' => [],
    'familyBed-4' => [],
mzur commented 7 years ago

Have you looked into array form fields? You can have as many fields as you want e.g. with familyFirstName[] or familyBed[]. I haven't had a use case where array form fields couldn't be used instead of something like the wildcard form fields mentioned above, that's why this issue is still dormant.

schnti commented 7 years ago

The problem is to access the old() and error() method dynamically. I have found a solution, but this is quite hacky. What do you think?

<?php
$familyFirstName = !empty($formConference->old('familyFirstName')) ? $formConference->old('familyFirstName') : array_fill_keys(range(0, 4), '');
?>

<?php for ($i = 0; $i < 5; $i++): ?>
    <div class="form-group" <?php e($formConference->error('familyFirstName'), 'has-error'); ?>>
        <label for="familyFirstName-<?= $i; ?>"><?= l::get('firstName', 'firstName'); ?></label>
        <input type="text" class="form-control" id="familyFirstName-<?= $i; ?>" name="familyFirstName[]" value="<?= $familyFirstName[$i]; ?>">
        <?php snippet('forms/error', ['form' => $formConference, 'field' => 'familyFirstName']) ?>
    </div>
<?php endfor; ?>

Also, the mandatory check does not work

'familyFirstName' => [
            'rules' => ['required'],
            'message' => 'required',
        ],

Dynamic JavaScript forms are also becoming difficult.

mzur commented 7 years ago

Sorry for answering so late. Here is what you could do:

Implement the form so family members can be added dynamically via JS. The form field names get unique names with incremental IDs just like you've shown.

Extend the controller so it dynamically creates the validation rules array based on the data in the POST request. Something like this:

$rules = [
   'roomtype' => [
      'rules' => ['required'],
      'message' => 'required',
   ],
   'firstName' => [/* ... */],
   /* ... */
];

$postData = r::postData();

$familyFirstNames = array_filter($postData, function ($key) {
   return str::startsWith($key, 'familyFirstName-');
}, ARRAY_FILTER_USE_KEY);

foreach ($familyFirstNames as $name => $value) {
   $rules[$name] = [
      'rules' => ['required'],
      'message' => 'required',
   ];
}

$form = new Form($rules);

/* ... */

return compact('form', 'familyFirstNames', /* ... */);

If the form validation failed, you can recreate the dynamically generated form fields by looping over the familyFirstNames array in the template.

This is definitely a use case that should be made easier with this issue. Unfortunately I'm currently too busy to take care of this.

mzur commented 6 years ago

Instead of implementing this as a new feature, write an example that shows how to dynamically generate the form fields as shown above. This should work for most use cases.

illycz commented 2 years ago

I'm trying use this solution within ajax example, but emails are not sent. Form return success, but email actions not working.

here is my code:

$rules = [
    'name' => [
        'rules' => ['required'],
        'message' => 'Vyplňte, prosím, jméno',
    ],
    'surname' => [
        'rules' => ['required'],
        'message' => 'Vyplňte, prosím, příjmení',
    ],
    'birth' => [
        'rules' => ['required'],
        'message' => 'Vyplňte, prosím, rok narození',
    ],
    'phone' => [
        'rules' => ['required'],
        'message' => 'Vyplňte, prosím, telefon',
    ],
    'email' => [
        'rules' => ['required', 'email'],
        'message' => 'Vyplňte, prosím, platnou emailovou adresu',
    ],
    'gdpr' => [
        'rules' => ['required'],
        'message' => 'Musíte souhlasit se zpracováním osobních údajů',
    ],
];

$people = array_filter($kirby->request()->body()->toArray(), function($key) {
    return strpos($key, 'people-') === 0;
}, ARRAY_FILTER_USE_KEY);

foreach ($people as $item => $value) {
    $rules[$item] = [];
}

$form = new \Uniform\Form($rules);

// Perform validation and execute guards.
$form->withoutFlashing()->withoutRedirect()->guard();

if (!$form->success()) {
    // Return validation errors.
    return Response::json($form->errors(), 400);
}

// If validation and guards passed, execute the action.
$form->emailAction([
    'to' => 'patrik.illy@gmail.com',
    'from' => 'noreply@example.com',
    'subject' => 'Registrace na Rodinnou párty na zámku',
    'template' => 'zameckaparty-admin',
    'replyTo' => $form->data('email'),
])
->emailAction([
    'to' => $form->data('email'),
    'from' => 'noreply@example.com',
    'subject' => 'Registrace na Rodinnou párty na zámku',
    'template' => 'zameckaparty-user',
    'replyTo' => 'patrik.illy@gmail.com',
]);

if (!$form->success()) {
    // This should not happen and is our fault.
    return Response::json($form->errors(), 500);
}

// Return code 200 on success.
return Response::json([], 200);

When I comment array_filter and adding dynamic fields to $rules, everything working as expected.

Thanks

illycz commented 2 years ago

Bingo! I don't remember why, but $kirby is not available. Solution below working.

$kirby = kirby();
$kirby->impersonate('kirby');