Tina's Louie
Here's the form from last time:
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:
You can try the new version.
OK, here are those two again, side by side:
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
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.
User entered data and pressed submit button
Now for the second case.
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:
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.
- /**
- * Get a value for parameter in the POST array.
- * @param string $paramName Name of the parameter.
- * @return string|null Trimmed value, or null if not found.
- */
- function getParamFromPost(string $paramName) {
- $returnValue = null;
- if (isset($_POST[$paramName])) {
- $returnValue = $_POST[$paramName];
- if (is_string($returnValue)) {
- $value = trim($returnValue);
- }
- if ($returnValue == '') {
- $returnValue = null;
- }
- }
- return $returnValue;
- }
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 give 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.
- <?php
- session_start();
- require_once 'library/useful-functions.php';
- // When $errorMessages is MT, shows no errors.
- $errorMessages = '';
- // User input. For initial input, this var remains MT.
- // If the user has given input, it gets put into this var.
- $userInput = '';
- // Is there user input?
- 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();
- }
- }
- // At this point, either:
- // 1. The user hasn't entered any data yet, so $userInput is MT.
- // We want to show the form with an MT input widget.
- // 2. The user entered data, which is in $userInput. It failed validation.
- // We want to show an error message, and the form again, with the Evil Value in the widget.
- ?><!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>Simple form</title>
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
- <link rel="stylesheet" href="library/styles.css">
- </head>
- <body>
- <h1>Simple form</h1>
- <p>Hello! I'm a simple form. Try me.</p>
- <?php
- // Show errors if there are any.
- if ($errorMessages != '') {
- print "<p class='wrong'>$errorMessages</p>";
- }
- ?>
- <form method="post">
- <p>
- <label>
- What's the best animal?<br>
- <input type="text" name="best-animal" size="20" value="<?= $userInput ?>"><br>
- <small>Hint: woof!</small>
- </label>
- </p>
- <p>
- <button type="submit">Save</button>
- </p>
- </form>
- </body>
- </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
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.
Exercise
Sales tax form
Write an app that shows a form like this (style it as you want):
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:
Another:
- 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.
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.