Yesterday, I gave an overview of how I approach adding dynamic content to a static, server-rendered website.
Today, we're going to dig into one piece of that puzzle: ajaxy forms. Let's dig in!
Forms are awesome! With an HTML form, you can support JavaScript-free user iterations.
For example, let's say I want to let users add an item to a list. I can include a <form>
element with a field to add a new item.
I'll give the form an [action]
that points to a PHP file (this is what processes the form request and does stuff with the data), and a [method]
attribute of POST
since I want to send data.
<form action="/path/to/add-item.php" method="POST"> <label for="item">The New Item</label> <input type="text" name="item" id="item" required> <button>Add Item</button> <div role="status"></div> </form>
By default, when the user submits the form, the page will redirect to the file specified in the [action]
attribute. That file will automatically receive any field value that has a [name]
attribute.
When its done, it can display a message or redirect the user back to the page they were on.
In my personal web apps, I use a Web Component to take over and send the data with JavaScript, without leaving the page. Users get a working experience always, and a better one once the JS loads.
The PHP to make this work
I love PHP! Like, unapologetically love it!
It's not perfect. No language is. But it runs nearly everywhere, has great docs, a ton of community support, and just works, pretty much everywhere.
My backend files have a few helper functions to get and respond to data.
First, I have two functions I use for processing requests. The get_method()
function gets the HTTP method used for the request. The get_request_data()
method combines the request body
(as JSON or a FormData
object) and any query string parameters into an array of keys and values.
<?php /** * Get the API method * @return String The API method */ function get_method () { return $_SERVER['REQUEST_METHOD']; } /** * Get data object from API data * @return Object The data object */ function get_request_data () { return array_merge(empty($_POST) ? array() : $_POST, (array) json_decode(file_get_contents('php://input'), true), $_GET); }
I also have a function, is_ajax()
, that checks if the request was a direct file load or an ajax request made with JavaScript.
<?php /** * Check if request is Ajax * @return boolean If true, request is ajax */ function is_ajax () { return !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; }
And I have a function I use to send back responses: send_response()
.
This accepts the $response
to send (as an array), the status $code
to use, a URL to $redirect
to, and $query
string parameter to use on the redirect (for displaying status messages with server-rendered PHP if JavaScript isn't working).
<?php /** * Send an API response * @param array $response The API response * @param integer $code The response code * @param string $redirect A URL to redirect to * @param string $query The string to use for the redirect query string */ function send_response ($response, $code = 200, $redirect = null, $query = 'message') { // If ajax, respond if (is_ajax()) { http_response_code($code); die(json_encode($response)); } // If redirect URL, redirect $url = array_key_exists('url', $response) ? $response['url'] : $redirect; $message = !array_key_exists('url', $response) && !empty($redirect) ? (strpos($redirect, '?') === false ? '?' : '&') . $query . '=' . $response['message'] . '&success=' . ($code === 200 ? 'true' : 'false') : ''; if (!empty($url)) { header('Location: ' . $url . $message); exit; } // Otherwise, show message die($response['message']); }
I also love using flat JSON file storage for my projects.
I have a server directory just for storing data. It includes an .htaccess
file that blocks all web traffic.
And I use two helper functions to read and write those files…
<?php /** * Get file * @param String $filename The filename * @param * $fallback Fallback content if the file isn't found * @param Boolean $as_string Return string instead of decoded object * @return * The file content */ function get_file ($filename, $fallback = '{}') { // File path $path = dirname(__FILE__) . '/hidden-stuff-path/' . $filename . '.json'; // If file exists, return it if (file_exists($path)) { $file = file_get_contents($path); return json_decode($file, true); } // Otherwise, return a fallback return json_decode($fallback, true); } /** * Create/update a file * @param String $filename The filename * @param * $content The content to save */ function set_file ($filename, $content, $fallback = '{}') { // File path $path = dirname(__FILE__) . '/hidden-stuff-path/' . $filename . '.json'; // If there's no content but there's a fallback, use it if (empty($content)) { file_put_contents($path, $fallback); return; } // Otherwise, save the content file_put_contents($path, json_encode($content)); }
Let's look at how to actually use all this stuff!
Putting it all together
Let's say I'm adding an item to the list, using the form I shared above.
I start by getting the HTTP method used, and the request data. I also setup a URL to $redirect
to in responses.
<?php // Get the method and data $method = get_method(); $data = get_request_data(); // The redirect URL $redirect = 'https://url-of-my-awesome-site.com/add-items';
I make sure the $method
is an allowed request type, and the $data
includes any required values.
<?php // Get the method and data $method = get_method(); $data = get_request_data(); // The redirect URL $redirect = 'https://url-of-my-awesome-site.com/add-items'; // Only support POST method if ($method !== 'POST') { send_response(array( 'message' => 'This method is not supported.', ), 400, $redirect); } // Make sure all required data is provided if (!isset($data['item']) || empty($data['item'])) { send_response(array( 'message' => 'Please provide an item to add.', ), 400, $redirect); }
If I get past this point, the user has provided the data I need, and I can write it to storage.
Let's assume for our purposes I have a file in my /hidden-stuff-path
(not the real directory path for my storage) called items.json
, and it stores the items the user adds.
I'll use get_file()
to read the file, modify it, and then use set_file()
to write it back down.
<?php // ... // Get the user items, with an empty array as a fallback $user_items = get_file('items', '[]'); // Add the new item to it $user_items[] = $data['item']; // Save the data back down set_file('items', $user_items);
In a real-world application, I'll typically start creating helper functions for tasks like this (like add_item_to_user_account($username, $item)
).
Once saved, I can send back a success message.
<?php // ... // Save the data back down set_file('items', $user_items); // Send a success response send_response(array( 'message' => 'Item added to the list!' ), 200, $redirect);
A real application will probably have some additional checks to sanitize data before saving it or make sure it conforms to the required format, but this is the basics of how I handle all of my personal apps and my membership portal.
Showing the status message with PHP
If JavaScript is running, a Web Component sends the request, gets the response, and shows a status message in the [role="status"]
element.
But if a full server reload happened, the send_response()
method adds a query string parameter with the message to the $redirect
URL. By default, it has a value of message
, unless you pass in a different key to use.
In my apps, I'll use that to display a message on the page after redirect.
<form action="/path/to/add-item.php" method="POST"> <label for="item">The New Item</label> <input type="text" name="item" id="item" required> <button>Add Item</button> <div role="status"><?php if (isset($_GET['message'])) : ?><?php echo htmlspecialchars($_GET['message']); ?><?php endif; ?></div> </form>
I also pass the query string through the PHP htmlspecialchars()
method to encode the string and reduce the risk of cross-site scripting attacks.
Enhancing with JavaScript
So… how do you enhance this with JavaScript? That's tomorrow's article!
Like this? A Go Make Things membership is the best way to support my work and help me create more free content.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] Progressively enhancing forms with an HTML Web Component (part 1)"