diff --git a/README.md b/README.md index c7bcc99..18318fb 100644 --- a/README.md +++ b/README.md @@ -102,4 +102,63 @@ 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. \ 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. + +# 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 diff --git a/additional/env.js b/additional/env.js index 8e84382..c6500f8 100644 --- a/additional/env.js +++ b/additional/env.js @@ -661,6 +661,47 @@ 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 8a8778a..5c2baf7 100644 --- a/examples/AllEventHandling/main.cpp +++ b/examples/AllEventHandling/main.cpp @@ -1,193 +1,26 @@ import Crafter.CppDOM; -using namespace Crafter; +using namespace Crafter::CppDOM; +using namespace Crafter::CppDOMBindings; import std; -// Create the main container element -HtmlElementView* body = new HtmlElementView("body", "
Click: X={}, Y={}
", event.clientX, event.clientY)); + HtmlElementView body("body"); + body.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; }); - - 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)); - }); - + + body.AddChild(div); + + // Demonstrate new bindings + std::string path = GetPathNameString(); + std::cout << "Current path: " << path << std::endl; + return 0; } \ No newline at end of file diff --git a/examples/SPAExample/main.cpp b/examples/SPAExample/main.cpp new file mode 100644 index 0000000..cd39dd3 --- /dev/null +++ b/examples/SPAExample/main.cpp @@ -0,0 +1,67 @@ +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("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("This is the About page.
Notice how the URL changes without reloading the page.
"); + } else if (page == "contact") { + content->SetInnerHTML("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 new file mode 100644 index 0000000..9750e20 --- /dev/null +++ b/examples/SPAExample/project.json @@ -0,0 +1,17 @@ +{ + "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 new file mode 100755 index 0000000..e706621 --- /dev/null +++ b/examples/SPAExample/run.sh @@ -0,0 +1 @@ +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 15b59c5..e1d4280 100644 --- a/interfaces/Crafter.CppDOM-BindingsExport.cppm +++ b/interfaces/Crafter.CppDOM-BindingsExport.cppm @@ -1,26 +1,7 @@ -/* -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; @@ -95,6 +76,9 @@ export namespace Crafter::CppDOMBindings { std::int32_t dragLeaveHandlerMaxId = 0; std::unordered_map