Google Maps Button Script
Your First Custom Button: A Google Maps Example
Welcome to our series on creating custom buttons! In this first tutorial, we'll build a practical and impressive button from scratch: a "Show on Map" button. When a user is editing a record (like a customer address), they can click this button to instantly open Google Maps in a new tab, pinpointing the address from the form. It's a fantastic way to add powerful, context-aware functionality to your application.
Who is this for?
This guide is for beginners. You should have a basic understanding of HTML and JavaScript. We'll walk through every part of the PHP and JavaScript code, explaining not just *what* it does, but *why* it's a good way to do it.
The Final Code: `template_record_googlemaps.php`
Let's start by looking at the complete, working code. This single file contains everything needed for our button's logic. Save this file as template_record_googlemaps.php inside your /custom_buttons/ directory.
<?php
require_once '../init.php';
// --- Access Control ---
if (!isset($_SESSION['user'])) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Authentication required']);
exit();
}
// --- Get parameters from the URL ---
$table_id = (int)($_GET['table_id'] ?? 0);
$record_id = (int)($_GET['id'] ?? 0);
$sig_from_url = trim($_GET['sig'] ?? '');
if ($table_id === 0 || $record_id === 0) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Table ID and Record ID are missing']);
exit();
}
// --- Security Check: Validate signature ---
$data_to_sign = "{$table_id}-{$record_id}";
$expected_sig = hash_hmac('sha256', $data_to_sign, INVITE_SECRET_KEY);
if (!hash_equals($expected_sig, $sig_from_url)) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Invalid signature.']);
exit();
}
?><!doctype html>
<html lang="<?= $current_lang ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Aktion wird ausgeführt...</title>
<link href="../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../style.css">
<?php include '../theme.php'; ?>
</head>
<body>
<!-- This body is usually empty, as the actions run via JavaScript modals -->
<script src="../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../js/dialog.js"></script>
<script>
// Step 1: Show the initial confirmation dialog.
showDialog({
title: '<?= lang('template_gmaps_title') ?>',
text: '<?= lang('template_gmaps_prompt') ?>',
okText: '<?= lang('template_gmaps_button') ?>',
cancelText: '<?= lang('admin_button_cancel') ?>'
}).then(confirmed => {
if (confirmed) {
// If the user clicks "OK", start the process.
showOnMap();
}
});
/**
* Finds address fields in the form and opens Google Maps.
*/
function showOnMap() {
try {
// --- Step 2: Find a suitable address field directly from the form ---
// Prioritized search for common address field names.
const addressFieldNames = ['strasse', 'plz', 'ort', 'anschrift', 'adresse', 'address', 'street', 'city'];
const addressParts = [];
for (const fieldName of addressFieldNames) {
const inputElement = document.querySelector(`[name="${fieldName}"]`);
if (inputElement && inputElement.value) {
addressParts.push(inputElement.value);
}
}
if (addressParts.length === 0) {
throw new Error('<?= lang('template_gmaps_error_no_address') ?>');
}
// --- Step 3: Construct Google Maps URL and open it ---
const fullAddress = addressParts.join(', ');
const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(fullAddress)}`;
window.open(mapsUrl, '_blank');
} catch (error) {
console.error('Error showing map:', error);
showErrorDialog(error.message);
}
}
/**
* Displays an error message in a dialog.
* @param {string} message - The error message to display.
*/
function showErrorDialog(message) {
showDialog({
title: '<?= lang('admin_toast_error_title') ?>',
text: message,
okText: 'OK'
});
}
</script>
</body>
</html>
Breaking Down the Code
This file is a mix of PHP and JavaScript, each with a distinct role. Let's look at them one by one.
Part 1: The PHP Foundation (Security First!)
The PHP code at the top of the file acts as a security guard. It runs on the server *before* anything is sent to the user's browser. Its only job is to make sure the request is legitimate.
require_once '../init.php';: This loads our application's core, including session management and helper functions likelang().if (!isset($_SESSION['user'])): This is the most basic check. Is a user even logged in? If not, the script stops immediately.- Parameter Check: It verifies that a
table_idand a recordidwere passed in the URL. Without them, the button has no context. - Signature Validation: This is the most important security feature. The application automatically adds a cryptographic signature (
sig) to the button's URL. This PHP code recalculates that signature on the server. If the signature from the URL doesn't match the one calculated here, it means the URL was tampered with, and the script aborts. This prevents unauthorized users from trying to run actions by guessing URLs.
Part 2: The JavaScript Logic (The Action!)
If the security checks pass, the server sends the rest of the file to the browser. The core logic is inside the <script> tag.
-
Ask for Confirmation: The first thing we do is call our custom
showDialog()function.This is great for user experience. Instead of instantly opening a new tab, we ask the user if they're sure. The
title,text, and button labels are all loaded via thelang()function, making our button multilingual! -
Handle the User's Choice: The
.then(confirmed => { ... })part is a JavaScript Promise. It waits for the user to click a button in the dialog. If they click "OK" (confirmedis true), we call our main function,showOnMap(). -
Execute
showOnMap(): This is where the magic happens.const addressFieldNames = [...]: We create a list of common technical field names for address parts. This makes our button robust. It doesn't matter if you named your field `strasse`, `anschrift`, or `street`—the script will find it.for (const fieldName of addressFieldNames): The script loops through our list of names.document.querySelector(`[name="${fieldName}"]`): This is the key. It scans the current web page (the "Edit Record" form) for an input element with a matching name.if (inputElement && inputElement.value): If it finds a field and the field has a value, it adds that value to ouraddressPartsarray.if (addressParts.length === 0): If the loop finishes and we haven't found any address data, we `throw new Error(...)` to show a user-friendly error message.const fullAddress = addressParts.join(', ');: We combine all the parts we found into a single string, separated by commas (e.g., "Musterstraße 1, 12345 Musterstadt").encodeURIComponent(fullAddress): This is **critical**. It converts special characters (like spaces, umlauts, or `ß`) into a format that is safe to use in a URL. Without this, an address like "Große Bärenstraße" would break the link.window.open(mapsUrl, '_blank');: Finally, this command tells the browser to open our constructed Google Maps URL in a new tab.
-
Error Handling: The entire logic is wrapped in a
try...catchblock. If anything goes wrong (like no address field being found), thecatchblock executes, callingshowErrorDialog()to inform the user what happened instead of just failing silently.
Integrating the Button into the System
Now that our script is ready, how do we make it appear in the application? It's a two-step process.
Step 1: Create the Button in Settings
- Navigate to Settings Custom Buttons.
- Click on "New Button".
- Fill out the form:
- Button Display Name:
Google Maps - URL / Script Path:
custom_buttons/template_record_googlemaps.php - Icon: Choose a suitable icon, for example,
fa-map-location-dot. - Color: Pick a color, like
btn-success(green). - Display Context: Select "Record View (action per record)" or "Both Views". This ensures the button appears on the "Edit Record" page.
- Button Display Name:
- Save the button.
Step 2: Place the Button in a Table Layout
You can place a button in the action bar of a record form, or you can place it directly within the form layout itself, just like a text field. Let's do the latter.
- Navigate to Settings Custom Tables and edit the structure of your address table.
- Click "Add Field".
- In the "Add New Field" form, set the following:
- Display Name:
Show on Map - Field Type: Select "Custom Button" from the dropdown.
- Display Name:
- A new dropdown will appear below "Field Type". Select the "Google Maps" button you just created.
- Adjust the Field Width if desired and save the field.
That's it! When you now open a record in that table, you will see your new "Show on Map" button right inside the form. When you click it, our script will run, find the address fields, and open Google Maps.
You've just created your first powerful, context-aware custom button. In the next article, we'll explore how to create buttons that interact with external services like Dropbox or modify data directly in the database.