Repeating Form Fields

How to copy a field by clicking on a button and submit it with Kirby inside the form? The number of repeated fields can be endless.

Example of what I would like to have

Controller

<?php

return function ($kirby, $page) {

    if($kirby->user()) {
        
    } else {
        go('/login');
    }
    

    // if the form has been submitted…
    if ($kirby->request()->is('POST') && get('submit')) {

        // check the honeypot and exit if is has been filled in
        if(empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        $data = [
            'title' => get('title'),
            'text' => get('text'),
            'problems' => get('problems'),
            'tipp' => get('tipp'),
            'place' => get('place'),
            'state' => get('state'),
            'financetext' => get('financetext'),
            'financeneeds' => get('financeneeds'),
            'initiatortext' => get('initiatortext'),
            'website' => get('website'),
            'author' => Data::encode($kirby->user()->id(), 'yaml')
        ];

        $rules = [
            'title'  => ['required'],
            'text' => ['required'],
            'place' => ['required'],
            'state' => ['required'],
            'website' => ['url'],
            'initiatortext' => ['required', 'max' => 200]

        ];

        $messages = [
            'title'  => 'Please enter your (link: #title text: title)',
            'text' => 'Please enter a valid (link: #email text: email address)',
            'place' => 'Bitte gib einen Ort ein.',
            'state' => 'Bitte wähle ein Bundesland aus.',
            'website' => 'Bitte gib eine URL ein.',
            'initiatortext' => 'Bitte beschreibe die Mitwrikenden.'
        ];

        // some of the data is invalid
        if ($invalid = invalid($data, $rules, $messages)) {
            $alert = $invalid;

        } else {

            // Get all files from the request
            $uploads = $kirby->request()->files()->get('file');

            // authenticate as almighty
            $kirby->impersonate('kirby');

            // everything is ok, let's try to create a new registration
            try {
                // we store registrations as subpages of the current page
                $project = page('projekte')->createChild([
                    'slug'     => str::slug($data['title'] . '-' . $data['place']),
                    'template' => 'projekt',
                    'content'  => $data
                ])->changeStatus('unlisted');

                //uploading files right here
                //they will be arranged in the new project
                foreach ($uploads as $upload) {
                    try {
                        $name = $upload['name'];
                        $file = $project->createFile([
                            'source'   => $upload['tmp_name'],
                            'filename' => $name,
                            'template' => 'upload',
                            'content' => [
                                'date' => date('Y-m-d h:m')
                            ]
                        ]);
                    } 
                    catch (Exception $e) {
                        $alerts[$upload['name']] = $e->getMessage();
                    }
                }

                // encode the files to yml format and update the field
                $fileArray = A::wrap($file->filename());

                $page = $project->update([
                    'gallery' => Data::encode($fileArray, 'yaml')
                ]);

                if ($project) {
                    // store referer and name in session
                    $kirby->session()->set([
                        'referer' => $page->uri(),
                        'regName'  => esc($data['title'])
                    ]);
                    go('projekte/danke');
                }

            } catch (Exception $e) {
                $alert = ['Your registration failed: ' . $e->getMessage()];
            }
        }
    }

    // return data to template
    return [
        'alert' => $alert ?? null,
        'data'  => $data ?? false,
    ];
};

You mean how to fetch the data from these fields in the controller?

Yes, because the number of repeated fields can be endless.

That depends on how you set this up. If all these fields have the same name and return an array using [] after the name, then you can fetch them by name, otherwise, you would have to fall back to the $_POST variable.

1 Like

I hope it is not too much to ask. Maybe can you show me how to do it with [] after the name?

1 Like

I am not sure what I am doing wrong :confused: It works when the field is a text field in the blueprint (type: text)

HTML

<textarea class="py-2 px-4 border-2 block rounded-lg w-full mt-1 focus:border-primary-600 focus:outline-none focus:ring-4 focus:ring-primary-100" id="problems" name="problems[]" value="<?= $data['problems'] ?? null ?>"></textarea>
<textarea class="py-2 px-4 border-2 block rounded-lg w-full mt-1 focus:border-primary-600 focus:outline-none focus:ring-4 focus:ring-primary-100" id="problems" name="problems[]" value="<?= $data['problems'] ?? null ?>"></textarea>

Blueprint

problems:
    label: Probleme
    type: structure
    fields:
        problem: 
            label: Problem
             type: text

Controller

<?php

return function ($kirby, $page) {

    if($kirby->user()) {
        
    } else {
        go('/login');
    }
    

    // if the form has been submitted…
    if ($kirby->request()->is('POST') && get('submit')) {

        // check the honeypot and exit if is has been filled in
        if(empty(get('website')) === false) {
            go($page->url());
            exit;
        }

        $data = [
            'title' => get('title'),
            'text' => get('text'),
            'tipp' => get('tipp'),
            'place' => get('place'),
            'state' => get('state'),
            'financetext' => get('financetext'),
            'financeneeds' => get('financeneeds'),
            'initiatortext' => get('initiatortext'),
            'website' => get('website'),
            'author' => Data::encode($kirby->user()->id(), 'yaml')
        ];

        $rules = [

        ];

        $messages = [
            'title'  => 'Please enter your (link: #title text: title)',
            'text' => 'Please enter a valid (link: #email text: email address)',
            'place' => 'Bitte gib einen Ort ein.',
            'state' => 'Bitte wähle ein Bundesland aus.',
            'website' => 'Bitte gib eine URL ein.',
            'initiatortext' => 'Bitte beschreibe die Mitwrikenden.'
        ];

        // some of the data is invalid
        if ($invalid = invalid($data, $rules, $messages)) {
            $alert = $invalid;

        } else {

            // Get all files from the request
            $uploads = $kirby->request()->files()->get('file');

            $problems = $_POST['problems'];

            // authenticate as almighty
            $kirby->impersonate('kirby');

            // everything is ok, let's try to create a new registration
            try {
                // we store registrations as subpages of the current page
                $project = page('projekte')->createChild([
                    'slug'     => str::slug($data['title'] . '-' . $data['place']),
                    'template' => 'projekt',
                    'content'  => $data
                ])->changeStatus('unlisted');

                //uploading files right here
                //they will be arranged in the new project
                foreach ($uploads as $upload) {
                    try {
                        $name = $upload['name'];
                        $file = $project->createFile([
                            'source'   => $upload['tmp_name'],
                            'filename' => $name,
                            'template' => 'upload',
                            'content' => [
                                'date' => date('Y-m-d h:m')
                            ]
                        ]);
                    } 
                    catch (Exception $e) {
                        $alerts[$upload['name']] = $e->getMessage();
                    }
                }

                // encode the files to yml format and update the field
                $fileArray = A::wrap($file->filename());
                $problems = A::wrap($problems);

                $page = $project->update([
                    'gallery' => Data::encode($fileArray, 'yaml'),
                    'problems' => Data::encode($problems, 'yaml'),
                ]);

                if ($project) {
                    // store referer and name in session
                    $kirby->session()->set([
                        'referer' => $page->uri(),
                        'regName'  => esc($data['title'])
                    ]);
                    go('projekte/danke');
                }

            } catch (Exception $e) {
                $alert = ['Your registration failed: ' . $e->getMessage()];
            }
        }
    }

    // return data to template
    return [
        'alert' => $alert ?? null,
        'data'  => $data ?? false,
    ];
};

I would help to know what the problem is?

Saving the data of multiple inputs (same name and the user can add more inputs in the frontend) in a structure field.

Why do you wrap an array inside an array?

Try and error because I don’t know to handle it.

What does get('problems') give you?

Also, what is the result of your updating the structure field (looking into the content file instead of into the Panel). Is anything stored at all? If yes, does it look like it has the correct structure or is it just a mess?

I have found a solution to transform the data into a structure like data.

$problems = [];
    $rawProblems = get('problems');
    foreach($rawProblems as $problem) {
        $problems[] = [
            'problem' => $problem
        ];
    }

$page = $project->update([
    'problems' => Data::encode($problems, 'yaml')
]);

Hm, but that returns the same as your input.

Rather, you would have to add the key for the field inside your structure field

 $problems = array_map( function($item) {
    $problems['problem'] = $item;
    return $problems;
  }, get('problems'));
 
  $project = $project->update([
    'problems' => Data::encode($problems, 'yaml'),
  ]);
1 Like