diff --git a/README.md b/README.md index 18318fb..c7bcc99 100644 --- a/README.md +++ b/README.md @@ -102,63 +102,4 @@ int main(){ } ``` -This feature allows you to make HTTP requests directly from C++ code running in WebAssembly, which can be useful for communicating with APIs or backend services. - -# Location Pathname Access - -The library now provides access to the current page's pathname through the `GetPathNameString()` function: - -```cpp -import Crafter.CppDOM; -using namespace Crafter::CppDOMBindings; - -int main(){ - // Get the current page's pathname - std::string path = GetPathNameString(); - - // Use the path - HtmlElementView body("body"); - body.SetInnerHTML("Current path: " + path); -} -``` - -# PopState Event Handling - -The library supports handling the `popstate` event for navigation history changes: - -```cpp -import Crafter.CppDOM; -using namespace Crafter::CppDOMBindings; - -int main(){ - // Add a listener for popstate events - auto popStateId = AddPopStateListener([]() { - // This will be called when the user navigates back/forward - std::string path = GetPathNameString(); - HtmlElementView body("body"); - body.SetInnerHTML("Navigated to: " + path); - }); - - // Later, remove the listener if needed - // RemovePopStateListener(popStateId); -} -``` - -# History PushState Functionality - -The library provides access to the `history.pushState` API for manipulating browser history: - -```cpp -import Crafter.CppDOM; -using namespace Crafter::CppDOMBindings; - -int main(){ - // Push a new state to the browser history - PushState("{\"page\": \"about\"}", "About Page", "/about"); - - // This will modify the browser URL without reloading the page - // and can be combined with popstate event handling -} -``` - -These features allow you to build single-page applications with proper URL handling and navigation state management. \ No newline at end of file +This feature allows you to make HTTP requests directly from C++ code running in WebAssembly, which can be useful for communicating with APIs or backend services. \ No newline at end of file diff --git a/additional/env.js b/additional/env.js index c6500f8..8e84382 100644 --- a/additional/env.js +++ b/additional/env.js @@ -661,47 +661,6 @@ let env = { const element = jsmemory.get(ptr); const val = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, value, valueLength)); element.value = val; - }, getValue: function(ptr) { - const element = jsmemory.get(ptr); - if(element.value) { - return writeStringToWasm(element.value || ""); - } else { - return 0; - } - }, - setValue: function(ptr, value, valueLength) { - const element = jsmemory.get(ptr); - const val = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, value, valueLength)); - element.value = val; - }, - getPathName: function() { - return writeStringToWasm(window.location.pathname); - }, - addPopStateListener: function(handlerID) { - const handler = function(event) { - const { ExecutePopStateHandler } = window.crafter_webbuild_wasi.instance.exports; - ExecutePopStateHandler(handlerID); - }; - - window.addEventListener('popstate', handler); - - // Store handler for potential cleanup - eventHandlers.set(`popstate-${handlerID}`, handler); - }, - removePopStateListener: function(handlerID) { - const handler = eventHandlers.get(`popstate-${handlerID}`); - - if (handler) { - window.removeEventListener('popstate', handler); - eventHandlers.delete(`popstate-${handlerID}`); - } - }, - pushState: function(data, dataLength, title, titleLength, url, urlLength) { - const dataStr = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, data, dataLength)); - const titleStr = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, title, titleLength)); - const urlStr = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, url, urlLength)); - - window.history.pushState(JSON.parse(dataStr), titleStr, urlStr); }, addClickListener: addClickListener, removeClickListener: removeClickListener, diff --git a/examples/AllEventHandling/main.cpp b/examples/AllEventHandling/main.cpp index 5c2baf7..8a8778a 100644 --- a/examples/AllEventHandling/main.cpp +++ b/examples/AllEventHandling/main.cpp @@ -1,26 +1,193 @@ import Crafter.CppDOM; -using namespace Crafter::CppDOM; -using namespace Crafter::CppDOMBindings; +using namespace Crafter; import std; +// Create the main container element +HtmlElementView* body = new HtmlElementView("body", "
" + "

Enhanced Event Handling Demo

" + "
" + "
" + "

Mouse Events

" + "" + "
" + "
" + "
" + "

Keyboard Events

" + "" + "
" + "
" + "
" + "

Focus Events

" + "" + "
" + "
" + "
" + "

Form Events

" + "
" + "" + "" + "" + "
" + "
" + "
" + "
" + "

Window Events

" + "
" + "
" + "
" + "

Drag & Drop Events

" + "
Drag Me!
" + "
Drop Here
" + "
" + "
" + "
" + "

Wheel Events

" + "
Scroll here
" + "
" + "
" + "
" + "
"); + +// Get references to elements +HtmlElementView* mouseButton = new HtmlElementView("mouseButton"); +HtmlElementView* mouseOutput = new HtmlElementView("mouseOutput"); +HtmlElementView* keyInput = new HtmlElementView("keyInput"); +HtmlElementView* keyOutput = new HtmlElementView("keyOutput"); +HtmlElementView* focusInput = new HtmlElementView("focusInput"); +HtmlElementView* focusOutput = new HtmlElementView("focusOutput"); +HtmlElementView* formInput = new HtmlElementView("formInput"); +HtmlElementView* formSelect = new HtmlElementView("formSelect"); +HtmlElementView* formElement = new HtmlElementView("formElement"); +HtmlElementView* formOutput = new HtmlElementView("formOutput"); +HtmlElementView* windowOutput = new HtmlElementView("windowOutput"); +HtmlElementView* dragSource = new HtmlElementView("dragSource"); +HtmlElementView* dropTarget = new HtmlElementView("dropTarget"); +HtmlElementView* dragOutput = new HtmlElementView("dragOutput"); +HtmlElementView* wheelContainer = new HtmlElementView("wheelContainer"); +HtmlElementView* wheelOutput = new HtmlElementView("wheelOutput"); + int main() { - HtmlElementView body("body"); - body.SetInnerHTML("

All Event Handling

"); - - HtmlElementView div("div"); - div.SetInnerHTML("

Click me!

"); - div.SetStyle("padding: 20px; background-color: lightblue; cursor: pointer;"); - - // Add click handler - div.AddClickListener([](Crafter::MouseEvent e) { - std::cout << "Clicked!" << std::endl; + // Mouse Events + mouseButton->AddClickListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Click: X={}, Y={}

", event.clientX, event.clientY)); }); - - body.AddChild(div); - - // Demonstrate new bindings - std::string path = GetPathNameString(); - std::cout << "Current path: " << path << std::endl; - + + mouseButton->AddMouseOverListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Mouse Over: X={}, Y={}

", event.clientX, event.clientY)); + }); + + mouseButton->AddMouseOutListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Mouse Out: X={}, Y={}

", event.clientX, event.clientY)); + }); + + mouseButton->AddMouseMoveListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Mouse Move: X={}, Y={}

", event.clientX, event.clientY)); + }); + + mouseButton->AddMouseDownListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Mouse Down: Button={}, X={}, Y={}

", event.button, event.clientX, event.clientY)); + }); + + mouseButton->AddMouseUpListener([&](MouseEvent event) { + mouseOutput->SetInnerHTML(std::format("

Mouse Up: Button={}, X={}, Y={}

", event.button, event.clientX, event.clientY)); + }); + + // Keyboard Events + keyInput->AddKeyDownListener([&](KeyboardEvent event) { + std::string keyInfo = std::format("

Key Down: Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}

", + event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey); + keyOutput->SetInnerHTML(keyInfo); + }); + + keyInput->AddKeyUpListener([&](KeyboardEvent event) { + std::string keyInfo = std::format("

Key Up: Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}

", + event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey); + keyOutput->SetInnerHTML(keyInfo); + }); + + keyInput->AddKeyPressListener([&](KeyboardEvent event) { + std::string keyInfo = std::format("

Key Press: Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}

", + event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey); + keyOutput->SetInnerHTML(keyInfo); + }); + + // Focus Events + focusInput->AddFocusListener([&](FocusEvent event) { + focusOutput->SetInnerHTML("

Focus: Element gained focus

"); + }); + + focusInput->AddBlurListener([&](FocusEvent event) { + focusOutput->SetInnerHTML("

Blur: Element lost focus

"); + }); + + // Form Events + formInput->AddInputListener([&](InputEvent event) { + formOutput->SetInnerHTML(std::format("

Input: Value='{}'

", event.data)); + }); + + formInput->AddChangeListener([&](ChangeEvent event) { + formOutput->SetInnerHTML(std::format("

Change: Value='{}'

", event.value)); + }); + + formSelect->AddChangeListener([&](ChangeEvent event) { + formOutput->SetInnerHTML(std::format("

Select Change: Value='{}'

", event.value)); + }); + + // Submit Event + formElement->AddSubmitListener([&]() { + formOutput->SetInnerHTML("

Submit: Form submitted successfully!

"); + }); + + // Window Events + // Resize event + body->AddResizeListener([&](ResizeEvent event) { + windowOutput->SetInnerHTML(std::format("

Resize: Width={}, Height={}

", event.width, event.height)); + }); + + // Scroll event + body->AddScrollListener([&](ScrollEvent event) { + windowOutput->SetInnerHTML(std::format("

Scroll: X={}, Y={}

", event.scrollX, event.scrollY)); + }); + + // Context Menu Event + body->AddContextMenuListener([&](MouseEvent event) { + windowOutput->SetInnerHTML(std::format("

Context Menu: X={}, Y={}

", event.clientX, event.clientY)); + }); + + // Drag and Drop Events + dragSource->AddDragStartListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drag Start: Dragging started

"); + }); + + dragSource->AddDragEndListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drag End: Dragging ended

"); + }); + + dropTarget->AddDragOverListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drag Over: Dragging over drop target

"); + }); + + dropTarget->AddDragEnterListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drag Enter: Drag entered drop target

"); + }); + + dropTarget->AddDragLeaveListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drag Leave: Drag left drop target

"); + }); + + dropTarget->AddDropListener([&](MouseEvent event) { + dragOutput->SetInnerHTML("

Drop: Item dropped

"); + }); + + // Wheel Event + wheelContainer->AddWheelListener([&](WheelEvent event) { + wheelOutput->SetInnerHTML(std::format("

Wheel: DeltaX={}, DeltaY={}, DeltaZ={}

", + event.deltaX, event.deltaY, event.deltaZ)); + }); + return 0; } \ No newline at end of file diff --git a/examples/SPAExample/README.md b/examples/SPAExample/README.md deleted file mode 100644 index 6f9687e..0000000 --- a/examples/SPAExample/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# SPAExample - -This example demonstrates how to create a Single Page Application (SPA) using Crafter.CppDOM with dynamic content updating and browser history management. - -## Features - -- Shows how to implement SPA navigation without page reloads -- Demonstrates usage of `PushState` and `PopState` events -- Illustrates dynamic content updating based on navigation -- Shows how to handle browser back/forward buttons -- Uses modern web techniques for seamless navigation - -## Usage - -```cpp -import Crafter.CppDOM; -import std; -using namespace Crafter; -using namespace Crafter::CppDOMBindings; - -HtmlElementView* body = new HtmlElementView("body", R"( - -
-)"); -HtmlElementView* home = new HtmlElementView("home"); -HtmlElementView* about = new HtmlElementView("about"); -HtmlElementView* contact = new HtmlElementView("contact"); -HtmlElementView* content = new HtmlElementView("content"); - -void UpdateContent(const std::string_view page) { - if (page == "home") { - content->SetInnerHTML("

Home Page

Welcome to the Home page of our Single Page Application!

This demo shows how to use the new history.pushState and popstate event handling features.

"); - } else if (page == "about") { - content->SetInnerHTML("

About Page

This is the About page.

Notice how the URL changes without reloading the page.

"); - } else if (page == "contact") { - content->SetInnerHTML("

Contact Page

This is the Contact page.

You can navigate back and forth using the browser's back/forward buttons.

"); - } -} - -int main() { - body->SetStyle("font-family: Arial, sans-serif; margin: 0; padding: 20px;"); - - // Add click handlers for navigation - home->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"home\"}", "Home", "/"); - UpdateContent("home"); - }); - - about->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"about\"}", "About", "/about"); - UpdateContent("about"); - }); - - contact->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"contact\"}", "Contact", "/contact"); - UpdateContent("contact"); - }); - - // Add popstate listener to handle browser back/forward buttons - auto popStateId = AddPopStateListener([]() { - std::string path = GetPathNameString(); - if (path == "/") { - UpdateContent("home"); - } else if (path == "/about") { - UpdateContent("about"); - } else if (path == "/contact") { - UpdateContent("contact"); - } else { - UpdateContent("home"); - } - }); - - // Initialize with home page - UpdateContent("home"); - - return 0; -} -``` - -## Building and Running - -```bash -crafter-build build executable -run.sh -``` - -Then navigate to `http://localhost:8080/` in your browser. - -If caddy is not installed, you can use your favorite static file server instead. \ No newline at end of file diff --git a/examples/SPAExample/main.cpp b/examples/SPAExample/main.cpp deleted file mode 100644 index cd39dd3..0000000 --- a/examples/SPAExample/main.cpp +++ /dev/null @@ -1,67 +0,0 @@ -import Crafter.CppDOM; -import std; -using namespace Crafter; -using namespace Crafter::CppDOMBindings; - -HtmlElementView* body = new HtmlElementView("body", R"( - -
-)"); -HtmlElementView* home = new HtmlElementView("home"); -HtmlElementView* about = new HtmlElementView("about"); -HtmlElementView* contact = new HtmlElementView("contact"); -HtmlElementView* content = new HtmlElementView("content"); - -void UpdateContent(const std::string_view page) { - if (page == "home") { - content->SetInnerHTML("

Home Page

Welcome to the Home page of our Single Page Application!

This demo shows how to use the new history.pushState and popstate event handling features.

"); - } else if (page == "about") { - content->SetInnerHTML("

About Page

This is the About page.

Notice how the URL changes without reloading the page.

"); - } else if (page == "contact") { - content->SetInnerHTML("

Contact Page

This is the Contact page.

You can navigate back and forth using the browser's back/forward buttons.

"); - } -} - -int main() { - body->SetStyle("font-family: Arial, sans-serif; margin: 0; padding: 20px;"); - - // Add click handlers for navigation - home->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"home\"}", "Home", "/"); - UpdateContent("home"); - }); - - about->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"about\"}", "About", "/about"); - UpdateContent("about"); - }); - - contact->AddClickListener([](Crafter::MouseEvent e) { - PushState("{\"page\": \"contact\"}", "Contact", "/contact"); - UpdateContent("contact"); - }); - - // Add popstate listener to handle browser back/forward buttons - auto popStateId = AddPopStateListener([]() { - std::string path = GetPathNameString(); - if (path == "/") { - UpdateContent("home"); - } else if (path == "/about") { - UpdateContent("about"); - } else if (path == "/contact") { - UpdateContent("contact"); - } else { - UpdateContent("home"); - } - }); - - // Initialize with home page - UpdateContent("home"); - - return 0; -} \ No newline at end of file diff --git a/examples/SPAExample/project.json b/examples/SPAExample/project.json deleted file mode 100644 index 9750e20..0000000 --- a/examples/SPAExample/project.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "main", - "configurations": [ - { - "name": "executable", - "implementations": ["main"], - "target": "wasm32-wasi", - "debug" : true, - "dependencies": [ - { - "path":"../../project.json", - "configuration":"lib-debug" - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/SPAExample/run.sh b/examples/SPAExample/run.sh deleted file mode 100755 index e706621..0000000 --- a/examples/SPAExample/run.sh +++ /dev/null @@ -1 +0,0 @@ -caddy file-server --listen :8080 --root bin/executable \ No newline at end of file diff --git a/interfaces/Crafter.CppDOM-BindingsExport.cppm b/interfaces/Crafter.CppDOM-BindingsExport.cppm index e1d4280..15b59c5 100644 --- a/interfaces/Crafter.CppDOM-BindingsExport.cppm +++ b/interfaces/Crafter.CppDOM-BindingsExport.cppm @@ -1,7 +1,26 @@ +/* +Crafter.CppDOM +Copyright (C) 2025 Catcrafts +Catcrafts.net + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3.0 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + export module Crafter.CppDOM:BindingsExport; import std; import :EventTypes; -import :BindingsImport; export namespace Crafter::CppDOMBindings { std::int32_t clickHandlerMaxId = 0; @@ -76,9 +95,6 @@ export namespace Crafter::CppDOMBindings { std::int32_t dragLeaveHandlerMaxId = 0; std::unordered_map>* dragLeaveHandlers = new std::unordered_map>(); - std::int32_t popStateHandlerMaxId = 0; - std::unordered_map>* popStateHandlers = new std::unordered_map>(); - std::int32_t fetchHandlerMaxId = 0; std::unordered_map>* fetchHandlers = new std::unordered_map>(); } @@ -197,10 +213,6 @@ extern "C" { Crafter::CppDOMBindings::fetchHandlers->find(handlerID)->second(response); Crafter::CppDOMBindings::fetchHandlers->erase(handlerID); } - - __attribute__((export_name("ExecutePopStateHandler"))) void ExecutePopStateHandler(std::int32_t handlerID) { - Crafter::CppDOMBindings::popStateHandlers->find(handlerID)->second(); - } } export namespace Crafter::CppDOMBindings { @@ -216,26 +228,4 @@ export namespace Crafter::CppDOMBindings { CppDOMBindings::fetchHandlers->insert({id, callback}); FetchWithBody(url.data(), url.size(), body.data(), body.size(), id); } - - __attribute__((import_module("env"), import_name("getPathName"))) const char* GetPathName(); - std::string GetPathNameString() { - const char* path = GetPathName(); - return std::string(path); - } - - std::int32_t AddPopStateListener(std::function callback) { - std::int32_t id = popStateHandlerMaxId++; - popStateHandlers->insert({id, callback}); - AddPopStateListener(id); - return id; - } - - void RemovePopStateListener(std::int32_t id) { - RemovePopStateListener(id); - popStateHandlers->erase(id); - } - - void PushState(const std::string_view data, const std::string_view title, const std::string_view url) { - PushState(data.data(), data.size(), title.data(), title.size(), url.data(), url.size()); - } } \ No newline at end of file diff --git a/interfaces/Crafter.CppDOM-BindingsImport.cppm b/interfaces/Crafter.CppDOM-BindingsImport.cppm index b308a70..16aa616 100644 --- a/interfaces/Crafter.CppDOM-BindingsImport.cppm +++ b/interfaces/Crafter.CppDOM-BindingsImport.cppm @@ -142,10 +142,6 @@ export namespace Crafter::CppDOMBindings { // Value property functions __attribute__((import_module("env"), import_name("getValue"))) const char* GetValue(void* ptr); - __attribute__((import_module("env"), import_name("getPathName"))) const char* GetPathName(); - __attribute__((import_module("env"), import_name("addPopStateListener"))) void AddPopStateListener(int id); - __attribute__((import_module("env"), import_name("removePopStateListener"))) void RemovePopStateListener(int id); - __attribute__((import_module("env"), import_name("pushState"))) void PushState(const char* data, std::int32_t dataLength, const char* title, std::int32_t titleLength, const char* url, std::int32_t urlLength); __attribute__((import_module("env"), import_name("setValue"))) void SetValue(void* ptr, const char* value, std::int32_t valueLength); void SetValue(void* ptr, const std::string_view value) { SetValue(ptr, value.data(), value.size());