Same-page validation

Tina's Louie
Tina's Louie

Here's the form from last time:

Form

Let's add two validation rules:

  • Widget can't be MT.
  • Widget can't have a number.

So if the user enters 333, they'll see:

Error message

You can try the new version.

OK, here are those two again, side by side:

Form Error message

They're similar. The one on the left has an MT widget. The user hasn't entered anything yet. The one on the right is the same form, but with an error message, and a value in the text widget. Also, the heading, the prompt, the font, etc., is the same.

It's common to have one PHP file that can create both screens:

  • When needed, an MT form.
  • When needed, a form with the last value the user typed, and some error messages.

Here's what it would do:

Data flow

​Data flow

Let's compare the cases.

User comes to the page for the first time

$userInput is set to MT (1). There is no POST data (2), so skip that bit. There are no errors (3), so skip that as well. Show the widget (4). $userInput is MT, so the widget will be MT.

We end up with what we wanted.

Form

User entered data and pressed submit button

Now for the second case.

Data flow

​Data flow

The use types some data, and hits the button. The page sends the data to itself.

$userInput is set to MT (1), as before. There is POST data (2) this time. Put the user's data into $userInput. Validate it. If it's OK, when send the data off for processing.

If the data isn't valid, the program continues. Show errors from validation (3). Show the widget, with $userInput in it (4). Recall that $userInput has the data the user typed in last time.

We have what we wanted:

Error message

Notice how important the variable $userInput is. The content of $userInput is what is shown in the widget (4). So, we arrange to have the right data in there. When the program starts, we make $userInput MT. Then, if there's POST data (2), we replace the MT in $userInput with whatever was in the POST. So, by the time we get to putting $userInput in the widget (4), it has what we want to show.

The code

Let's check out the code for the form page. You can download it.

Before we do that, let's check a couple of new functiony friends. Here's a call to a new one:

  • $userInput = getParamFromPost('best-animal');

getParamFromPost() is almost the same as getParamFromGet(), with a couple of changes.

  1. /**
  2.  * Get a value for parameter in the POST array.
  3.  * @param string $paramName Name of the parameter.
  4.  * @return string|null Trimmed value, or null if not found.
  5.  */
  6. function getParamFromPost(string $paramName) {
  7.     $returnValue = null;
  8.     if (isset($_POST[$paramName])) {
  9.         $returnValue = $_POST[$fieldName];
  10.         if (is_string($returnValue)) {
  11.             $value = trim($returnValue);
  12.         }
  13.         if ($returnValue == '') {
  14.             $returnValue = null;
  15.         }
  16.     }
  17.     return $returnValue;
  18. }

It checks POST rather than GET. Obvious enough.

It also trims the input, that is, strips leading and trailing spaces. It only does this for strings.

We're now dealing with data people type into a form, so we have to allow for typing habits. One habit is to add a space at the end of every word. Stripping extra spaces takes care of that.

The other one is a validation function. It follows the same pattern as the others.

  • /**
  •  * @param string $userInput User input to check.
  •  * @return string Error message, MT if data is OK.
  •  */
  • function checkBestAnimal($userInput) {
  •     $errorMessage = '';
  •     if (is_null($userInput)) {
  •         $errorMessage = 'Sorry, you must enter your fave animal.';
  •     }
  •     if ($errorMessage == '') {
  •         if (is_numeric($userInput)) {
  •             $errorMessage = "Sorry, you can't enter numbers.";
  •         }
  •     }
  •     return $errorMessage;
  • }

The return value will me MT if there are problem, or it will have an error message.

OK, let's check out the code for the form page. Remember that it deals with two cases:

  • Showing an MT form, with no input.
  • Showing a form with what the user typed.
  1. <?php
  2. session_start();
  3. require_once 'library/useful-functions.php';
  4. // When $errorMessages is MT, shows no errors.
  5. $errorMessages = '';
  6. // User input. For initial input, this var remains MT.
  7. // If the user has given input, it gets put into this var.
  8. $userInput = '';
  9. // Is there user input?
  10. if ($_POST) {
  11.     // There is input.
  12.     // Grab it.
  13.     $userInput = getParamFromPost('best-animal');
  14.     // Check it.
  15.     $errorMessages = checkBestAnimal($userInput);
  16.     if ($errorMessages == '') {
  17.         // No errors. Pass to processing page.
  18.         // Send data for processing.
  19.         $_SESSION['valid_best_animal'] = $userInput;
  20.         header('Location: process-simple-form-data.php');
  21.         exit();
  22.     }
  23. }
  24. // At this point, either:
  25. // 1. The user hasn't entered any data yet, so $userInput is MT.
  26. // We want to show the form with an MT input widget.
  27. // 2. The user entered data, which is in $userInput. It failed validation.
  28. // We want to show an error message, and the form again, with the Evil Value in the widget.
  29. ?><!DOCTYPE html>
  30. <html lang="en">
  31.     <head>
  32.         <meta charset="UTF-8">
  33.         <title>Simple form</title>
  34.         <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  35.         <link rel="stylesheet" href="library/styles.css">
  36.     </head>
  37.     <body>
  38.         <h1>Simple form</h1>
  39.         <p>Hello! I'm a simple form. Try me.</p>
  40.         <?php
  41.         // Show errors if there are any.
  42.         if ($errorMessages != '') {
  43.             print "<p class='wrong'>$errorMessages</p>";
  44.         }
  45.         ?>
  46.         <form method="post">
  47.             <p>
  48.                 <label>
  49.                     What's the best animal?<br>
  50.                     <input type="text" name="best-animal" size="20" value="<?= $userInput ?>"><br>
  51.                     <small>Hint: woof!</small>
  52.                 </label>
  53.             </p>
  54.             <p>
  55.                 <button type="submit">Save</button>
  56.             </p>
  57.         </form>
  58.     </body>
  59. </html>

We'll come back to the first few lines in a mo. Line 8 initializes $userInput to MT. Line 10 is:

  • if ($_POST) {

This is a quick way of checking whether there is any POST data. If there is, grab it...

  • $userInput = getParamFromPost('best-animal');

... and check it...

  • $errorMessages = checkBestAnimal($userInput);

Now, if there are no errors, we want to process the user's data. We do it on this page, but let's send the data somewhere else for processing. We'll look at the deets of that in a mo.

The rest of the code should be familiar. Remember, we make sure $userInput has what we want to show (either MT, or what the user typed last time, so this code will show the text input widget, with the right stuff in it:

  • <input type="text" name="best-animal" size="20" value="<?= $userInput ?>">

One thing that's different is that the form doesn't have an action:

  • <form method="post">

In that case, the browser sends the data back to the same page, the one with the form on it.

Send data to the processing page

If the data passes validation, we'll send it to a processing page. We'll:

  • Store the data somewhere the processing page can get it.
  • Jump to the processing page.

This code does that:

  • session_start();
  • ...
  • $_SESSION['valid_best_animal'] = $userInput;
  • header('Location: process-simple-form-data.php');
  • exit();

The jump-to-a-page is done by header. It redirects the browser to a different page, with the URL in the Location property, as shown.

Important!

header() works only when nothing has been output yet! So it only works in PHP at the top of the file, before any HTML.

So, where do we stash the data?

Stashing data into the session

One way to send data between pages is with sessions. PHP sessions preserve data across page accesses, but only data you say you want to keep.

If you want to use sessions, put...

  • session_start();

... near the top of your code. For the page we're talking about, I made it the first thing.

To put data it the session, use the $_SESSION array, like this:

  • $_SESSION['key'] = $value;

Examples:

  • $_SESSION['rosie'] = 'A cute doggo';
  • $_SESSION['acceleration'] = 8.5;
  • $_SESSION['price'] = $retailPrice;
  • $_SESSION['a_looooong_key_that_would_totally_work'] = 2 + 3*4;
  • $_SESSION['you can use spaces too'] = "It weights $weight kilos";
  • $key = 'Buffy is the best';
  • $_SESSION[$key] = true;
  • $anotherOne = "I'll be back";
  • $_SESSION[$anotherOne . $anotherOne] = 3;

I make keys lowercase with _ for spaces, just to keep things easy.

OK, here's that code again:

  • if ($_POST) {
  •     // There is input.
  •     // Grab it.
  •     $userInput = getParamFromPost('best-animal');
  •     // Check it.
  •     $errorMessages = checkBestAnimal($userInput);
  •     if ($errorMessages == '') {
  •         // No errors. Pass to processing page.
  •         // Send data for processing.
  •         $_SESSION['valid_best_animal'] = $userInput;
  •         header('Location: process-simple-form-data.php');
  •         exit();
  •     }
  • }

So

  • Get the user's data from POST into a variable
  • Check it
  • If it's OK
  • Put the data into the session
  • Set up a jump another page
  • Stop running the program

You need the exit() for this to work. Remember, header() will have an effect only if there's nothing in the page before it. exit() stops the program (line End in VBA), making sure that happens.

Process the data

Valid data is sent to process-simple-form-data.php by these lines:

  • // Stash data for processing later.
  • $_SESSION['valid_best_animal'] = $userInput;
  • // Off to processing.
  • header('Location: process-simple-form-data.php');

Only valid data is sent. process-simple-form-data.php doesn't repeat all the validation. (It would in an industrial-strength web app, but not in our simple ones.)

Here's what we have:

  • <?php
  • session_start();
  • // Get user data from stash.
  • $userInputBestAnimal = strtolower($_SESSION['valid_best_animal']);
  • // Message to show.
  • $message = '';
  • // Class to apply to the message.
  • $messageClass = '';
  • if ($userInputBestAnimal == 'dog' || $userInputBestAnimal == 'dogs') {
  •     $message = 'Yes, the dog is the best animal.';
  •     $messageClass = 'right';
  • }
  • else {
  •     $message = "No, $userInputBestAnimal is not the best animal.";
  •     $messageClass = 'wrong';
  • }
  • ?><!DOCTYPE html>
  • ...
  • <p class="<?= $messageClass ?>"><?= $message ?></p>

This is how the page grabs the data send by the other page:

  • session_start();
  • $userInputBestAnimal = strtolower($_SESSION['valid_best_animal']);

session_start() is at the top of the code. Otherwise, the session stuff won't work.

More on sessions

PHP does a good job of keeping session data separate. Each browser instance has its own session. Each computer runs its own browser instances. So every PC, tablet, phone, whatevs, has separate sessions.

If you run multiple browsers at the same time on your PC, like Firefox and Chrome, each one has separate session. However, the tabs on the browser share session data. This is why you can open multiple tabs when running Moodle, and every tab is logged in as you.

Moodle is written in PHP, BTW.

Ethan
Ethan

So, where it the session data kept? In each browser?

Good question! No, PHP stores session data on the server. What is sent to the browser is a session id, in a cookie. PHP looks up the data associated with that session id.

Pattern

Apps use same-page validation a lot. Let's make a pattern out of it. Remember, a pattern is a common way of doing things. When you need to make an app, check the pattern list, to remind yourself of things you know how to do.

Pattern

Same page validation

Put a form and its validation code on the same page.

Exercise

Exercise

Sales tax form

Write an app that shows a form like this (style it as you want):

Form, no input

(MT instead of 0 is OK.)

The user fills in the fields. All are required. If there are errors, show them all, and the form, with the user's input values. For example:

Errors

Another:

More errors

  • State should be MI or IL. Case should not matter. There can be extra leading and trailing spaces.
  • Price should be a positive number.
  • Discount rate should be a positive number.

If all is OK, show the output.

Output

You can try my solution.

Submit the URL of your solution, and a zip of your files.

Up next

Let's see how you add new records to a database.