diff --git a/README.md b/README.md
index 8108829..263b83e 100644
--- a/README.md
+++ b/README.md
@@ -667,6 +667,25 @@ All the example sketches include the DNS related code and will work as captive p
ESPUI.captivePortal = false;
```
+### User-defined JavaScript
+
+You can add your own custom JavaScript to the UI. This allows you to create custom controls, handle events, or otherwise extend the functionality of the UI.
+
+To add custom JavaScript, call `ESPUI.setCustomJS()` before `ESPUI.begin()`. The argument to `setCustomJS()` is a C-string containing the JavaScript code. This string must remain valid for the lifetime of the ESPUIClass instance.
+
+```cpp
+const char* myCustomJS = "alert('Hello from custom JS!');";
+
+void setup() {
+ // ...
+ ESPUI.setCustomJS(myCustomJS);
+ ESPUI.begin("ESPUI Control");
+ // ...
+}
+```
+
+The custom JavaScript is served at `/js/custom.js` and is automatically included in the `index.htm` file.
+
# Notes for Development
diff --git a/data/index.htm b/data/index.htm
index dbb5e84..e072e3e 100644
--- a/data/index.htm
+++ b/data/index.htm
@@ -16,6 +16,7 @@
+
diff --git a/src/ESPUI.cpp b/src/ESPUI.cpp
index 68b1fef..8f06276 100644
--- a/src/ESPUI.cpp
+++ b/src/ESPUI.cpp
@@ -17,6 +17,18 @@
#include
#endif
+// Optional user-defined JavaScript to be included in the UI.
+// Served at /js/custom.js, which is automatically included in index.htm.
+// js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
+static const char* customJS = nullptr;
+
+// Set custom JavaScript to be included in the UI.
+// js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
+void ESPUIClass::setCustomJS(const char* js)
+{
+ customJS = js;
+}
+
static String heapInfo(const __FlashStringHelper* mode)
{
String result;
@@ -1272,6 +1284,15 @@ void ESPUIClass::begin(const char* _title, const char* username, const char* pas
yield();
});
+ server->on("/js/custom.js", HTTP_GET, [](AsyncWebServerRequest* request) {
+ if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword))
+ {
+ return request->requestAuthentication();
+ }
+
+ request->send(200, "application/javascript", customJS ? customJS : "");
+ });
+
server->begin();
#if defined(DEBUG_ESPUI)
diff --git a/src/ESPUI.h b/src/ESPUI.h
index 0e7bcc4..970daa1 100644
--- a/src/ESPUI.h
+++ b/src/ESPUI.h
@@ -201,6 +201,11 @@ public:
void updateVisibility(uint16_t id, bool visibility, int clientId = -1);
+ // Set optional user-defined JavaScript to be included in the UI.
+ // js: JavaScript code as a C-string. Must remain valid for the lifetime of the ESPUIClass instance.
+ // This is intentionally not a String to avoid dynamic memory allocation.
+ void setCustomJS(const char* js);
+
// Variables
const char* ui_title = "ESPUI"; // Store UI Title and Header Name
Control* controls = nullptr;
diff --git a/src/dataIndexHTML.h b/src/dataIndexHTML.h
index 8686f3a..df4c8f6 100644
--- a/src/dataIndexHTML.h
+++ b/src/dataIndexHTML.h
@@ -1,5 +1,5 @@
const char HTML_INDEX[] PROGMEM = R"=====(
-Control