Single Page Application
[ 150 keer bekeken / views ]
Dit is de tweede post over bibliotheken die ik heb geschreven om het bouwen van een applicatie voor esp32 bordjes te vereenvoudigen.
Deel 1: Networking
Deel 2: Single Page Application (GUI)
Deel 3: Filesystem manager
Deze post gaat dus over de SPAmanager. Een bibliotheek waarmee op een eenvoudige manier een GUI kan worden geschreven voor je volgende esp32 project.
Wat is een Single Page Application (SPA)?
Een Single Page Application (SPA) is een webapplicatie of website die volledig binnen één enkele webpagina werkt. In plaats van elke keer een nieuwe pagina van de server op te halen wanneer een gebruiker navigeert, laadt een SPA eenmalig de essentiële HTML-, CSS- en JavaScript-bestanden en beheert daarna de navigatie en interacties via JavaScript.
De SPAmanager bibliotheek zorgt ervoor dat de layout van de webapplicatie netjes voor je geregeld wordt. Hij ziet er altijd hetzelfde uit.

De bovenste regel is gereserveerd voor (van links naar rechts) ruimte voor drop-down menu’s, een titel en de datum/tijd.
Op de onderste regel kunnen status- en fout-meldingen worden weergegeven.
Alles daartussen is gereserveerd voor pagina’s met informatie van jouw project.

Je ontkomt er niet aan om voor iedere pagina die je nodig hebt kleine stukjes html te schrijven maar dit blijft beperkt tot alleen de zaken die je in jouw project nodig hebt. Hierbij zorgt de SPAmanager ervoor dat de layout consistent blijft.
Om je project van pagina’s en menu’s te voorzien heeft de bibliotheek een aantal methoden beschikbaar. We zullen de belangrijkste één voor één behandelen.
addPage()
Met deze methode kun je een pagina aan je SPA toevoegen. Iedere pagina heeft een naam en een (klein) stukje html.
//-- Voeg een eenvoudige hoofdpagina toe spaManager.addPage("Home", R"( <div> <h1>Welkom bij mijn ESP32 Project</h1> <p>Huidige temperatuur: <span id="temp">--</span> °C</p> <button id="refreshBtn">Vernieuwen</button> </div> )");
Bovenstaande code heeft dit resultaat:

Bestaat je applicatie uit meerdere pagina’s, dan kun je één daarvan “actief” maken met de methode activatePage()
. Met deze methode worden alle andere pagina’s automatisch naar de achtergrond gestuurd.
addMenu()
Met deze methode voeg je een drop-down menu toe aan de pagina.
//-- Voeg een hoofdmenu toe aan de homepagina spaManager.addMenu("Home", "Hoofdmenu"); //-- Voeg een instellingenmenu toe aan de instellingenpagina spaManager.addMenu("Home", "Instellingenmenu");
Bovenstaande code heeft dit als resultaat:

addMenuItem()
Binnen het met addMenu() aangemaakte drop-down menu kun je keuzen plaatsen met addMenuItem(). Zo’n menu-item kan een URL starten, een functie aanroepen of een popup-menu activeren.
//-- Definieer een callback functie void ledAanzetten() { digitalWrite(LED_PIN, HIGH); Serial.println("LED aangezet"); } //-- Voeg een menu-item toe dat de functie aanroept wanneer erop geklikt wordt spaManager.addMenuItem("Home", "Hoofdmenu", "LED AAN", ledAanzetten); //-- Voeg een menu-item toe dat naar een externe website navigeert spaManager.addMenuItem("Home", "Hoofdmenu", "Documentatie", "https://example.com/docs");

disableMenuItem()
Mocht, in een bepaalde context een menu-item geen functie hebben, dan kan dat menu-item met deze methode “ge-grayed-out” gemaakt worden. Het menu-item blijft dan wel in de drop-down lijst staan, maar je kunt er niet meer op klikken.
//-- Schakel het "Reset Apparaat" menu-item uit spaManager.disableMenuItem("Home", "Hoofdmenu", "LED AAN"); //-- Je kunt items uitschakelen op basis van voorwaarden if (!isWifiConnected) { spaManager.disableMenuItem("Home", "Hoofdmenu", "LED AAN"); }

enableMenuItem()
Met deze methode kan een menu-item weer actief gemaakt worden.
//-- Schakel het "Reset Apparaat" menu-item in spaManager.enableMenuItem("Instellingen", "Instellingenmenu", "Reset Apparaat"); //-- Je kunt items inschakelen op basis van voorwaarden if (isWifiConnected) { spaManager.enableMenuItem("Home", "Hoofdmenu", "Cloud Upload"); }
includeJsFile()
Eventuele extra javascript functies kunnen met de methode includeJsFile() in de, in de browser aanwezige, code worden geïnjecteerd. Hierdoor is het mogelijk om functionaliteit die niet standaard in de SPA aanwezig is, toe te voegen. Na het aanroepen van deze methode zal het nieuwe Javascript bestand niet direct naar de browser gestuurd worden. Dit zal pas gebeuren als de esp32 van de browser “hoort” dat de html pagina volledig is ingeladen.
void onPageLoaded() { //-- Initialiseer het dashboard na het laden van scripts spaManager.callJsFunction("initDashboard"); } void setup() { //-- Stel de display manager in spaManager.begin("/SYS"); //-- Registreer de callback voor wanneer de pagina is geladen spaManager.pageIsLoaded(onPageLoaded); //-- Voeg JavaScript bestanden toe //-- deze bestanden worden door de SPAmanager geladen //-- zodra de html pagina volledig is geladen spaManager.includeJsFile("/scripts/utils.js"); spaManager.includeJsFile("/scripts/charts.js"); spaManager.includeJsFile("/scripts/sensors.js"); }
includeCssFile()
Deze methode doet hetzelfde als includeJsFile() maar dan voor style-sheets.
void setup() { //-- Stel de display manager in spaManager.begin("/SYS"); //-- Voeg CSS bestanden toe spaManager.includeCssFile("/styles/main.css"); spaManager.includeCssFile("/styles/dashboard.css"); //-- Voeg conditioneel een thema CSS toe if (darkModeEnabled) { spaManager.includeCssFile("/styles/dark-theme.css"); } else { spaManager.includeCssFile("/styles/light-theme.css"); } }
Hoe werkt deze SPAmanager intern
Bij het opstarten zal de esp32 intern een html-raamwerk opbouwen.
Zodra er een client (lees: webbrowser) verbinding maakt met de interne webserver zal dit raamwerk (SPAmanager.html) naar de browser worden gestuurd, samen met het bijbehorende style-sheet (SPAmanager.css) en de nodige Javascript (SPAmanager.js). Als de pagina volledig in de browser is geladen stuurt de browser een signaal naar de SPAmanager die vervolgens de door jouw opgegeven pagina’s met menu’s injecteert in de, in de browser reeds aanwezige, code. Ook kunnen extra Javascript en style-sheets worden geïnjecteerd. Vervolgens draait de hele SPA in de browser. Via web-sockets vindt er communicatie plaats tussen de browser en de interne webserver.
Voorbeelden
Alle in deze post gepresenteerde voorbeelden staan in de examples
folder van de github repository.
API Reference
SPAmanager(uint16_t port = 80)
Constructor for the SPAmanager class.
- port: The port number for the web server (default is 80).
Example:
//-- Create a SPAmanager instance with default port (80) SPAmanager spaManager; //-- Or specify a custom port SPAmanager spaManager(8080);
begin(const char* systemPath, Stream* debugOut = nullptr)
Initializes the SPAmanager with system files path and optional debug output.
- systemPath: Path to the system files (HTML, CSS, JS)
- debugOut: Optional parameter for debug output stream.
Example:
void setup() { Serial.begin(115200); //-- Initialize with system files in /SYS directory //-- and debug output to Serial spaManager.begin("/SYS", &Serial); //-- Or initialize without debug output //spaManager.begin("/SYS"); }
addPage(const char* pageName, const char* html)
Adds a web page to the display manager.
- pageName: The name of the page.
- html: The HTML content of the page.
Example:
//-- Add a simple home page spaManager.addPage("Home", R"( <div> <h1>Welcome to my ESP32 Web Server</h1> <p>Current temperature: <span id="temp">--</span> °C</p> <button id="refreshBtn">Refresh</button> </div> )");
activatePage(const char* pageName)
Activates a web page, making it the current page being displayed.
- pageName: The name of the page to activate.
Example:
//-- Switch to the settings page spaManager.activatePage("Settings");
setPageTitle(const char* pageName, const char* title)
Sets the title of a specific web page.
- pageName: The name of the page.
- title: The new title for the page.
Example:
//-- Set the title of the home page spaManager.setPageTitle("Home", "Smart Home Dashboard"); //-- Update title based on sensor status if (alarmTriggered) { spaManager.setPageTitle("Home", "ALERT: Motion Detected!"); }
addMenu(const char* pageName, const char* menuName)
Adds a menu to a specific web page.
- pageName: The name of the page to add the menu to.
- menuName: The name of the menu.
Example:
//-- Add a main menu to the home page spaManager.addMenu("Home", "Main Menu"); //-- Add a settings menu to the settings page spaManager.addMenu("Settings", "Settings Menu");
addMenuItem(const char* pageName, const char* menuName, const char* itemName, std::function callback)
Adds a menu item with a callback function to a specific menu on a web page.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- itemName: The name of the menu item.
- callback: The function to call when the menu item is selected.
Example:
//-- Define a callback function void turnOnLED() { digitalWrite(LED_PIN, HIGH); Serial.println("LED turned ON"); } //-- Add a menu item that calls the function when clicked spaManager.addMenuItem("Home", "Main Menu", "Turn ON LED", turnOnLED);
addMenuItem(const char* pageName, const char* menuName, const char* itemName, const char* url)
Adds a menu item with a URL to a specific menu on a web page.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- itemName: The name of the menu item.
- url: The URL to navigate to when the menu item is selected.
Example:
//-- Add a menu item that navigates to the settings page spaManager.addMenuItem("Home", "Main Menu", "Settings", "/settings"); //-- Add a menu item that links to an external website spaManager.addMenuItem("Home", "Main Menu", "Documentation", "https://example.com/docs");
addMenuItem(const char* pageName, const char* menuName, const char* itemName, std::function<void(const char)> callback, const char param)
Adds a menu item with a parameterized callback function to a specific menu on a web page.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- itemName: The name of the menu item.
- callback: The function to call when the menu item is selected, receiving a const char* parameter.
- param: The string value to pass to the callback function when invoked.
Example:
void handleMenuAction(const char* action) { if (strcmp(action, "on") == 0) { digitalWrite(LED_PIN, HIGH); spaManager.setMessage("LED turned ON", 6); } else if (strcmp(action, "off") == 0) { digitalWrite(LED_PIN, LOW); spaManager.setMessage("LED turned OFF", 3); } } //-- In setup or where menus are configured: spaManager.addMenuItem("Home", "Main Menu", "Turn ON LED", handleMenuAction, "on"); spaManager.addMenuItem("Home", "Main Menu", "Turn OFF LED", handleMenuAction, "off");
addMenuItemPopup(const char* pageName, const char* menuName, const char* menuItem, const char* popupMenu, std::function&)> callback = nullptr)
Adds a menu item that opens a popup menu when selected.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- menuItem: The name of the menu item that will open the popup.
- popupMenu: The HTML content of the popup menu.
- callback: Optional callback function that receives a map of input values from the popup.
Example:
//-- Define a callback function to handle form submission void handleWifiSettings(const std::map<std::string, std::string>& values) { //-- Get values from the map std::string ssid = values.at("ssid"); std::string password = values.at("password"); //-- Use the values to connect to WiFi WiFi.begin(ssid.c_str(), password.c_str()); //-- Show a message spaManager.setMessage("Connecting to WiFi...", 10); } //-- Add a menu item that opens a popup for WiFi settings const char* wifiPopupHtml = R"( <div class="popup-content"> <h3>WiFi Settings</h3> <input name="ssid" placeholder="WiFi SSID"> <input name="password" type="password" placeholder="WiFi Password"> <button type="submit">Connect</button> </div> )"; spaManager.addMenuItemPopup("Settings", "Settings Menu", "WiFi Settings", wifiPopupHtml, handleWifiSettings);
enableMenuItem(const char* pageName, const char* menuName, const char* itemName)
Enables a specific menu item on a web page.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- itemName: The name of the menu item to enable.
Example:
//-- Enable the "Reset Device" menu item spaManager.enableMenuItem("Settings", "Settings Menu", "Reset Device"); //-- You might enable items based on conditions if (isWifiConnected) { spaManager.enableMenuItem("Home", "Main Menu", "Cloud Upload"); }
disableMenuItem(const char* pageName, const char* menuName, const char* itemName)
Disables a specific menu item on a web page.
- pageName: The name of the page containing the menu.
- menuName: The name of the menu.
- itemName: The name of the menu item to disable.
Example:
//-- Disable the "Reset Device" menu item spaManager.disableMenuItem("Settings", "Settings Menu", "Reset Device"); //-- You might disable items based on conditions if (!isWifiConnected) { spaManager.disableMenuItem("Home", "Main Menu", "Cloud Upload"); }
setMessage(const char* message, int duration)
Sets a message to be displayed for a specified duration.
- message: The message to display.
- duration: The duration in seconds to display the message (0 is infinite).
Example:
//-- Show a temporary message for 3 seconde spaManager.setMessage("Settings saved successfully!", 3); //-- Show a message when a sensor is triggered if (motionDetected) { spaManager.setMessage("Motion detected in living room", 5); }
setErrorMessage(const char* message, int duration)
Sets an error message to be displayed for a specified duration.
- message: The error message to display.
- duration: The duration in seconds to display the error message (0 is infinite).
Example:
//-- Show an error message for 5 seconds spaManager.setErrorMessage("Failed to connect to WiFi!", 5); //-- Show an error when a sensor reading fails if (isnan(temperature)) { spaManager.setErrorMessage("Temperature sensor error", 3); }
callJsFunction(const char* functionName)
Calls a JavaScript function in the browser by its name.
- functionName: The name of the JavaScript function to call.
Example:
//-- Call a simple JavaScript function spaManager.callJsFunction("refreshData"); //-- Call a JavaScript function with parameters char jsCommand[64]; float sensorValue = 23.5; sprintf(jsCommand, "updateChart(%f)", sensorValue); spaManager.callJsFunction(jsCommand);
setPlaceholder(const char* pageName, const char* placeholder, T value)
Sets a placeholder value in the HTML content of a web page.
- pageName: The name of the page containing the placeholder.
- placeholder: The ID of the element to update.
- value: The value to set (can be int, float, double, or const char*).
Example:
//-- Set temperature placeholder with a float value float temperature = 23.5; spaManager.setPlaceholder("Home", "temp", temperature); //-- Set humidity placeholder with an integer value int humidity = 45; spaManager.setPlaceholder("Home", "humidity", humidity); //-- Set status placeholder with a string value spaManager.setPlaceholder("Home", "status", "Online");
getPlaceholder(const char* pageName, const char* placeholder)
Gets the value of a placeholder in the HTML content of a web page.
- pageName: The name of the page containing the placeholder.
- placeholder: The ID of the element to get the value from.
Example:
//-- Get the current value of the temperature placeholder PlaceholderValue temp = spaManager.getPlaceholder("Home", "temp"); //-- Use the value in different formats int tempInt = temp.asInt(); float tempFloat = temp.asFloat(); const char* tempStr = temp.c_str(); Serial.print("Current temperature: "); Serial.println(tempStr);
enableID(const char* pageName, const char* id)
Enables an HTML element with the specified ID on a web page by setting its
display style to “block”.
- pageName: The name of the page containing the element.
- id: The ID of the HTML element to enable.
Example:
//-- Enable a button when WiFi is not connected if (WiFi.status() == WL_CONNECTED) { spaManager.disableID("Home", "connectButton"); } else { spaManager.enableID("Home", "connectButton"); } //-- Enable a form when the device is ready spaManager.enableID("Settings", "wifiForm");
disableID(const char* pageName, const char* id)
Disables an HTML element with the specified ID on a web page by setting its
display style to “none”.
- pageName: The name of the page containing the element.
- id: The ID of the HTML element to disable.
Example:
//-- Disable a button when WiFi is disconnected if (WiFi.status() != WL_CONNECTED) { spaManager.disableID("Home", "uploadButton"); } else { spaManager.enableID("Home", "uploadButton"); } //-- Disable a form during processing spaManager.disableID("Settings", "wifiForm");
pageIsLoaded(std::function callback)
Sets a callback function to be called when the web page is fully loaded.
- callback: The function to call when the page is loaded.
Example:
void onPageLoaded() { //-- This function will be called when the page is loaded spaManager.setMessage("Page loaded successfully!", 3); //-- Initialize the page spaManager.callJsFunction("initializeDashboard"); //-- Update placeholders with current values spaManager.setPlaceholder("Dashboard", "temp", currentTemperature); spaManager.setPlaceholder("Dashboard", "humidity", currentHumidity); } void setup() { //-- Set up the display manager spaManager.begin("/SYS"); //-- Register the page loaded callback spaManager.pageIsLoaded(onPageLoaded); //-- Include JavaScript and CSS files spaManager.includeJsFile("/scripts/charts.js"); spaManager.includeCssFile("/styles/custom.css"); //-- Add pages and other setup // ... }
getSystemFilePath() const
Returns the path to the system files directory.
Example:
//-- Get the system files path std::string sysPath = spaManager.getSystemFilePath(); //-- Use the path to construct file paths std::string cssPath = sysPath + "/custom.css"; Serial.println(("CSS file path: " + cssPath).c_str());
includeCssFile(const std::string &cssFile)
Includes a CSS file in the web page.
- cssFile: The path to the CSS file to include.
Example:
void onPageLoaded() { //-- initialize fields or what not } void setup() { //-- Set up the display manager spaManager.begin("/SYS"); //-- Register the page loaded callback spaManager.pageIsLoaded(onPageLoaded); //-- Include CSS files spaManager.includeCssFile("/styles/main.css"); spaManager.includeCssFile("/styles/dashboard.css"); //-- Conditionally include theme CSS if (darkModeEnabled) { spaManager.includeCssFile("/styles/dark-theme.css"); } else { spaManager.includeCssFile("/styles/light-theme.css"); } }
includeJsFile(const std::string &scriptFile)
Includes a JavaScript file in the web page.
- scriptFile: The path to the JavaScript file to include.
Example:
void onPageLoaded() { //-- Initialize the dashboard after including scripts spaManager.callJsFunction("initDashboard"); } void setup() { //-- Set up the display manager spaManager.begin("/SYS"); //-- Register the page loaded callback spaManager.pageIsLoaded(onPageLoaded); //-- Include JavaScript files //-- these files are loaded by the SPAmanager as //-- soon as the html page is fully loaded spaManager.includeJsFile("/scripts/utils.js"); spaManager.includeJsFile("/scripts/charts.js"); spaManager.includeJsFile("/scripts/sensors.js"); }