Create a simply form and store data in file

Hello,

I’m trying to make a form but I’m little confused.
I use “User sign-up” and “Uploading files from frontend” cookbook to start.

I would like to make form to store simply a name and a score.

Later, I would like to return all the name with their score in table.

So i create a snippet score.php (because it’s reuse in differents page)

snippet/score.php
  <form action="" method="post" enctype="multipart/form-data">

    <div class="honeypot">
      <label for="website">Website <abbr title="required">*</abbr></label>
      <input type="website" id="website" name="website">
    </div>

    <div class="form-field">
      <label for="name">Nom</label>
      <input required name="name" type="text" maxlength="3">
      <label for="score">Score</label>
      <input required name="score" type="number" >
    </div>

    <input type="submit" name="submit" value="Submit" class="button">

  </form>

A controller

controllers/score.php

<?php

return function ($kirby) {
    var_dump($kirby);

  $errors   = [];
  $success = '';

  if ($kirby->request()->is('post') === true && get('submit')) {

    // check the honeypot
    if (empty(get('website')) === false) {
      go($page->url());
      exit();
    }

    $data = [
        'name' => get('name'),
        'score' => get('score'),
    ];

    $rules = [
        'name' => ['required', 'maxLength' => 3],
        'score' => ['required', 'integer'],
    ];

    $messages = [
        'name' => 'Your name must have at max 3 characters',
        'score' => 'Should be a number',
    ];

    if ($invalid = invalid($data, $rules, $messages)) {
        $errors = $invalid;

    } else {
        try {
            $score = page('score')->update([
                'name' => $data['name'],
                'score' => $data['score'],
            ]);
        } catch (Exception $e) {
            $errors[] = $e->getMessage();
        }
    }
  }

  return compact('alerts', 'success');
};

And a new content folder “score” and a new txt file “score.txt”

content/score/score.txt
Title: Score

Nothing append, it seem I don’t go to my controller.
Also, I think that my try in my controller isn’t correct, but I didn’t find the best method to update the file.

Can you give me some trails :slight_smile:

Thank you :slight_smile: have a nice day

I don’t know where you are using your form snippet, but since it doesn’t have an action attribute that points to the score page, it will only load the controller of the page where it is located, which might not be your score page.

Also, while you are using the $page variable, it is not passed as argument to the closure, so this variable will be undefined, so should be

return function ($kirby, $page) {

Sure you want a maximum length of 3 chars for the name? This should be minLength, right?

Hello,

I decided to backtrack a bit to go step by step.

I’m designing my new portfolio, and I’ve made an old-fashioned mini game (that’s why I’m limiting the name to 3 characters max). I would like to make a scoreboard, so I’m building a form to record players’ scores.

So I’m going to stabilize my form already, before I use it in other pages.

I’m stuck on the data recording, I don’t know which method to use.

If I use page(score)->save(), it will overwrite all my existing data.
If I use page(score)->update(), I get an error and nothing happens.

I have solved the errors that you pointed out to me, thanks you.

Nice afternoon :slight_smile:

To update page data, you need to authenticate: Permissions | Kirby CMS

$kirby->impersonate('kirby');
$page->update([
                'name' => $data['name'],
                'score' => $data['score'],
            ]);

Thanks, you help a lot,

I succeed with that

try {

            /* Autorisation d'update une page */
            $kirby->impersonate('kirby');
            /* Récupération du scoreboard */
            $scoreBoard = page('score')->Scoreboard()->yaml();

            /*Ajout du nouveau score au scoreboard */
            $newScore = array('name' => $data['name'], 'score' => $data['score'] );
            array_push($scoreBoard, $newScore);


            /* Mise à jour de la page gérant le scoreboard */
            $score = page('score')->update(array('Scoreboard' => $scoreBoard));
            
            return true;
            
        } ...

Now I try to send my form with the fetch method in JS to avoid any page reload.

Do you have any suggestions? Here is the beginning of my script

const formSubmit = document.getElementById('PM-score-form');

formSubmit.addEventListener('submit', async(e) => {
    e.preventDefault();

    let url    = `http://${window.location.hostname}/score.php`;

    console.log(url);
    
    try {
      const response    = await fetch(url);

      console.log(response);

    } catch (error) {
      console.log('Fetch error: ', error);
    }

  });

I have made some progress, here is my code

index.js

const formSubmit = document.getElementById('PM-score-form');

formSubmit.addEventListener('submit', async(e) => {
    e.preventDefault();

    let url    = `http://${window.location.hostname}/score`;
    let newScore = {
        'name' : document.getElementById('PM-score-name').value,
        'score' : document.getElementById('PM-score-number').value
      }
    
    try {
        const response    = await fetch(url, {
            method: "POST",
            body : new FormData(formSubmit)
        });
        console.log(response.json());

    } catch (error) {
      console.log('Fetch error: ', error);
    }

  });
  
controllers/score.php
<?php

return function ($kirby, $page) {

  $alerts   = [];
  $success = '';

  if ($kirby->request()->is('post') === true && get('submit')) {

    // check the honeypot
    if (empty(get('website')) === false) {
      go($page->url());
      exit();
    }

    $data = [
        'name' => get('name'),
        'score' => get('score'),
    ];

    $rules = [
        'name' => ['required', 'maxLength' => 3],
        'score' => ['required', 'integer'],
    ];

    $messages = [
        'name' => 'Le pseudo ne peut pas avoir plus de 3 caractères',
        'score' => 'Le score doit être un nombre',
    ];

    if ($invalid = invalid($data, $rules, $messages)) {
        $alerts = $invalid;
    }

    if (empty($alerts)) {
        try {

            /* Autorisation d'update une page */
            $kirby->impersonate('kirby');
            /* Récupération du scoreboard */
            $scoreBoard = page('score')->Scoreboard()->yaml();

            /*Ajout du nouveau score au scoreboard */
            $newScore = array('name' => $data['name'], 'score' => $data['score'] );
            array_push($scoreBoard, $newScore);


            /* Mise à jour de la page gérant le scoreboard */
            $score = page('score')->update(array('Scoreboard' => $scoreBoard));
            
        } catch (Exception $error) {
            $alerts[] = "Le score n'a pas pu être enregistré";
        }

        // no exception occurred, let's send a success message
        if (empty($alerts) === true) {
            $success = 'Le score a bien été enregistré';
            $data = [];
        }
        
    }
  }

  // return data to template
  return [
    'alerts' => $alerts ?? false,
    'data'    => $data ?? false,
    'success' => $success ?? false
    ];
};```

My form works when I am on the score.php page
But it doesn’t work when I’m on index.php and I call the form with.

Also, I don’t have any return :confused:

templates/index.php
<?php $form = page('score'); ?>
<?= $form->render() ?>

Any idea ?
Good night :slight_smile:

I still don’t understand why my JS doesn’t work, I would like to send my form without reload page.

My controller is returning my whole page ‘score.php’ instead of returning the data placed in my return.
:confused:

config.php
'routes' => [
        [
            'pattern' => '(:any)',
            'action'  => function($themeName) {
                if ($page = page($themeName)) {
                    return page($themeName);
                }
            }
        ],
        [
            'pattern' => '(:any)',
            'action'  => function($score) {
                return page($score);
            }
        ]
    ],
templates/score.php

<?php if($success): ?>
  <div class="PM-alert PM-success">
      <p><?= $success ?></p>
  </div>
<?php endif ?>
<?php
  // if the form input is not valid, show a list of alerts
  if ($alerts) : ?>
      <div class="PM-alert">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" style="fill: rgba(246, 213, 92, 1);transform: ;msFilter:;"><path d="M12.884 2.532c-.346-.654-1.422-.654-1.768 0l-9 17A.999.999 0 0 0 3 21h18a.998.998 0 0 0 .883-1.467L12.884 2.532zM13 18h-2v-2h2v2zm-2-4V9h2l.001 5H11z"></path></svg>
          <ul>
              <?php foreach ($alerts as $message): ?>
                  <li><?= kirbytext($message) ?></li>
              <?php endforeach ?>
          </ul>
      </div>
<?php endif ?>


<form id="PM-score-form" method="POST" enctype="multipart/form-data">

    <div class="honeypot">
      <label for="website">Website <abbr title="required">*</abbr></label>
      <input type="website" id="website" name="website">
    </div>

    <div class="form-field">
      <label for="name">Nom</label>
      <input id="PM-score-name" required name="name" type="text" maxlength="3">
      <label for="score">Score</label>
      <input id="PM-score-number" required name="score" type="number" >
    </div>

    <input id="PM-score-submit" type="submit" name="submit" value="Submit" class="button">

</form>
controllers/score.php

<?php

return function ($kirby, $page) {

  $alerts   = [];
  $success = '';

  if ($kirby->request()->is('post') === true && get('submit')) {

    // check the honeypot
    if (empty(get('website')) === false) {
      go($page->url());
      exit();
    }

    $data = [
        'name' => get('name'),
        'score' => get('score'),
    ];

    $rules = [
        'name' => ['required', 'maxLength' => 3],
        'score' => ['required', 'integer'],
    ];

    $messages = [
        'name' => 'Le pseudo ne peut pas avoir plus de 3 caractères',
        'score' => 'Le score doit être un nombre',
    ];

    if ($invalid = invalid($data, $rules, $messages)) {
        $alerts = $invalid;
    }

    if (empty($alerts)) {
        try {

            /* Autorisation d'update une page */
            $kirby->impersonate('kirby');
            /* Récupération du scoreboard */
            $scoreBoard = page('score')->Scoreboard()->yaml();

            /*Ajout du nouveau score au scoreboard */
            $newScore = array('name' => $data['name'], 'score' => $data['score'] );
            array_push($scoreBoard, $newScore);


            /* Mise à jour de la page gérant le scoreboard */
            $score = page('score')->update(array('Scoreboard' => $scoreBoard));
            
        } catch (Exception $error) {
            $alerts[] = "Le score n'a pas pu être enregistré";
        }

        // no exception occurred, let's send a success message
        if (empty($alerts) === true) {
            $success = 'Le score a bien été enregistré';
            $data = [];
        }
        
    }
  }

  // return data to template
  return [
    'alerts' => $alerts ?? false,
    'data'    => $data ?? false,
    'success' => $success ?? false
    ];
};
JS

  const formSubmit = document.getElementById('PM-score-form');
    formSubmit.addEventListener('submit', async(event) => {

        event.preventDefault()
        
        let url    = `http://${window.location.hostname}/score`;
        let newScore = {
            'name' : document.getElementById('PM-score-name').value,
            'score' : document.getElementById('PM-score-number').value
        }
  
        try {
            const response = await fetch(url, {
                method: "POST",
                body : new FormData(formSubmit)
            });

            console.log(response);

        } catch (error) {
            console.log('Fetch error: ', error);
        }

  
  });

Thank :slight_smile:

What are those routes supposed to do? Apart from the fact that they are both doing the same, they are quite useless anyway.

I also don’t understand why you render the score page in the home page…

Ok, I decided to simplify this, now my form is on my home page, and I save my data on the home content.

I don’t have an intermediate page anymore.

I deleted my routes because they were useless, as you suggested.

Now my goal is to submit my form and get validation without reloading the page.

How can I do this? I thought I had to encode in JSON what I return in my controller, but I didn’t succeed --’

I’d do the Ajax handling of the form either via a route or a content representation because yes, you need to return json.

While this example refers to the Uniform plugin, it can probably help with your task: AJAX - Kirby Uniform