Compare commits
2 commits
f16394f258
...
84ce42f106
| Author | SHA1 | Date | |
|---|---|---|---|
| 84ce42f106 | |||
| 9651d84dc4 |
9 changed files with 333 additions and 207 deletions
61
README.md
61
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.
|
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.
|
||||||
|
|
@ -661,6 +661,47 @@ let env = {
|
||||||
const element = jsmemory.get(ptr);
|
const element = jsmemory.get(ptr);
|
||||||
const val = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, value, valueLength));
|
const val = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, value, valueLength));
|
||||||
element.value = val;
|
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,
|
addClickListener: addClickListener,
|
||||||
removeClickListener: removeClickListener,
|
removeClickListener: removeClickListener,
|
||||||
|
|
|
||||||
|
|
@ -1,193 +1,26 @@
|
||||||
import Crafter.CppDOM;
|
import Crafter.CppDOM;
|
||||||
using namespace Crafter;
|
using namespace Crafter::CppDOM;
|
||||||
|
using namespace Crafter::CppDOMBindings;
|
||||||
import std;
|
import std;
|
||||||
|
|
||||||
// Create the main container element
|
|
||||||
HtmlElementView* body = new HtmlElementView("body", "<div id='container'>"
|
|
||||||
"<h1>Enhanced Event Handling Demo</h1>"
|
|
||||||
"<div id='events-container'>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Mouse Events</h2>"
|
|
||||||
"<button id='mouseButton'>Click Me!</button>"
|
|
||||||
"<div id='mouseOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Keyboard Events</h2>"
|
|
||||||
"<input type='text' id='keyInput' placeholder='Press keys here'>"
|
|
||||||
"<div id='keyOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Focus Events</h2>"
|
|
||||||
"<input type='text' id='focusInput' placeholder='Focus me!'>"
|
|
||||||
"<div id='focusOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Form Events</h2>"
|
|
||||||
"<form id='formElement'>"
|
|
||||||
"<input type='text' id='formInput' placeholder='Type something'>"
|
|
||||||
"<select id='formSelect'>"
|
|
||||||
"<option value='option1'>Option 1</option>"
|
|
||||||
"<option value='option2'>Option 2</option>"
|
|
||||||
"<option value='option3'>Option 3</option>"
|
|
||||||
"</select>"
|
|
||||||
"<button type='submit'>Submit Form</button>"
|
|
||||||
"</form>"
|
|
||||||
"<div id='formOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Window Events</h2>"
|
|
||||||
"<div id='windowOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Drag & Drop Events</h2>"
|
|
||||||
"<div id='dragSource' draggable='true'>Drag Me!</div>"
|
|
||||||
"<div id='dropTarget'>Drop Here</div>"
|
|
||||||
"<div id='dragOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"<div class='event-section'>"
|
|
||||||
"<h2>Wheel Events</h2>"
|
|
||||||
"<div id='wheelContainer'>Scroll here</div>"
|
|
||||||
"<div id='wheelOutput'></div>"
|
|
||||||
"</div>"
|
|
||||||
"</div>"
|
|
||||||
"</div>");
|
|
||||||
|
|
||||||
// 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() {
|
int main() {
|
||||||
// Mouse Events
|
HtmlElementView body("body");
|
||||||
mouseButton->AddClickListener([&](MouseEvent event) {
|
body.SetInnerHTML("<h1>All Event Handling</h1>");
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Click:</strong> X={}, Y={}</p>", event.clientX, event.clientY));
|
|
||||||
|
HtmlElementView div("div");
|
||||||
|
div.SetInnerHTML("<p>Click me!</p>");
|
||||||
|
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) {
|
body.AddChild(div);
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Mouse Over:</strong> X={}, Y={}</p>", event.clientX, event.clientY));
|
|
||||||
});
|
// Demonstrate new bindings
|
||||||
|
std::string path = GetPathNameString();
|
||||||
mouseButton->AddMouseOutListener([&](MouseEvent event) {
|
std::cout << "Current path: " << path << std::endl;
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Mouse Out:</strong> X={}, Y={}</p>", event.clientX, event.clientY));
|
|
||||||
});
|
|
||||||
|
|
||||||
mouseButton->AddMouseMoveListener([&](MouseEvent event) {
|
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Mouse Move:</strong> X={}, Y={}</p>", event.clientX, event.clientY));
|
|
||||||
});
|
|
||||||
|
|
||||||
mouseButton->AddMouseDownListener([&](MouseEvent event) {
|
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Mouse Down:</strong> Button={}, X={}, Y={}</p>", event.button, event.clientX, event.clientY));
|
|
||||||
});
|
|
||||||
|
|
||||||
mouseButton->AddMouseUpListener([&](MouseEvent event) {
|
|
||||||
mouseOutput->SetInnerHTML(std::format("<p><strong>Mouse Up:</strong> Button={}, X={}, Y={}</p>", event.button, event.clientX, event.clientY));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keyboard Events
|
|
||||||
keyInput->AddKeyDownListener([&](KeyboardEvent event) {
|
|
||||||
std::string keyInfo = std::format("<p><strong>Key Down:</strong> Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}</p>",
|
|
||||||
event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey);
|
|
||||||
keyOutput->SetInnerHTML(keyInfo);
|
|
||||||
});
|
|
||||||
|
|
||||||
keyInput->AddKeyUpListener([&](KeyboardEvent event) {
|
|
||||||
std::string keyInfo = std::format("<p><strong>Key Up:</strong> Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}</p>",
|
|
||||||
event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey);
|
|
||||||
keyOutput->SetInnerHTML(keyInfo);
|
|
||||||
});
|
|
||||||
|
|
||||||
keyInput->AddKeyPressListener([&](KeyboardEvent event) {
|
|
||||||
std::string keyInfo = std::format("<p><strong>Key Press:</strong> Key='{}', Code={}, Ctrl={}, Shift={}, Alt={}</p>",
|
|
||||||
event.key, event.keyCode, event.ctrlKey, event.shiftKey, event.altKey);
|
|
||||||
keyOutput->SetInnerHTML(keyInfo);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Focus Events
|
|
||||||
focusInput->AddFocusListener([&](FocusEvent event) {
|
|
||||||
focusOutput->SetInnerHTML("<p><strong>Focus:</strong> Element gained focus</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
focusInput->AddBlurListener([&](FocusEvent event) {
|
|
||||||
focusOutput->SetInnerHTML("<p><strong>Blur:</strong> Element lost focus</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form Events
|
|
||||||
formInput->AddInputListener([&](InputEvent event) {
|
|
||||||
formOutput->SetInnerHTML(std::format("<p><strong>Input:</strong> Value='{}'</p>", event.data));
|
|
||||||
});
|
|
||||||
|
|
||||||
formInput->AddChangeListener([&](ChangeEvent event) {
|
|
||||||
formOutput->SetInnerHTML(std::format("<p><strong>Change:</strong> Value='{}'</p>", event.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
formSelect->AddChangeListener([&](ChangeEvent event) {
|
|
||||||
formOutput->SetInnerHTML(std::format("<p><strong>Select Change:</strong> Value='{}'</p>", event.value));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit Event
|
|
||||||
formElement->AddSubmitListener([&]() {
|
|
||||||
formOutput->SetInnerHTML("<p><strong>Submit:</strong> Form submitted successfully!</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Window Events
|
|
||||||
// Resize event
|
|
||||||
body->AddResizeListener([&](ResizeEvent event) {
|
|
||||||
windowOutput->SetInnerHTML(std::format("<p><strong>Resize:</strong> Width={}, Height={}</p>", event.width, event.height));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scroll event
|
|
||||||
body->AddScrollListener([&](ScrollEvent event) {
|
|
||||||
windowOutput->SetInnerHTML(std::format("<p><strong>Scroll:</strong> X={}, Y={}</p>", event.scrollX, event.scrollY));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Context Menu Event
|
|
||||||
body->AddContextMenuListener([&](MouseEvent event) {
|
|
||||||
windowOutput->SetInnerHTML(std::format("<p><strong>Context Menu:</strong> X={}, Y={}</p>", event.clientX, event.clientY));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drag and Drop Events
|
|
||||||
dragSource->AddDragStartListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drag Start:</strong> Dragging started</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
dragSource->AddDragEndListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drag End:</strong> Dragging ended</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
dropTarget->AddDragOverListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drag Over:</strong> Dragging over drop target</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
dropTarget->AddDragEnterListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drag Enter:</strong> Drag entered drop target</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
dropTarget->AddDragLeaveListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drag Leave:</strong> Drag left drop target</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
dropTarget->AddDropListener([&](MouseEvent event) {
|
|
||||||
dragOutput->SetInnerHTML("<p><strong>Drop:</strong> Item dropped</p>");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wheel Event
|
|
||||||
wheelContainer->AddWheelListener([&](WheelEvent event) {
|
|
||||||
wheelOutput->SetInnerHTML(std::format("<p><strong>Wheel:</strong> DeltaX={}, DeltaY={}, DeltaZ={}</p>",
|
|
||||||
event.deltaX, event.deltaY, event.deltaZ));
|
|
||||||
});
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
94
examples/SPAExample/README.md
Normal file
94
examples/SPAExample/README.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# 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"(
|
||||||
|
<nav>
|
||||||
|
<h2 style="margin-bottom: 20px; padding: 10px; background-color: #f0f0f0;">SPA Navigation Demo</h2>
|
||||||
|
<a id="home" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">Home</a>
|
||||||
|
<a id="about" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">About</a>
|
||||||
|
<a id="contact" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">Contact</a>
|
||||||
|
</nav>
|
||||||
|
<div id="content" style="min-height: 200px; padding: 15px; border: 1px solid #ccc;"></div>
|
||||||
|
)");
|
||||||
|
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("<h3>Home Page</h3><p>Welcome to the Home page of our Single Page Application!</p><p>This demo shows how to use the new history.pushState and popstate event handling features.</p>");
|
||||||
|
} else if (page == "about") {
|
||||||
|
content->SetInnerHTML("<h3>About Page</h3><p>This is the About page.</p><p>Notice how the URL changes without reloading the page.</p>");
|
||||||
|
} else if (page == "contact") {
|
||||||
|
content->SetInnerHTML("<h3>Contact Page</h3><p>This is the Contact page.</p><p>You can navigate back and forth using the browser's back/forward buttons.</p>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
67
examples/SPAExample/main.cpp
Normal file
67
examples/SPAExample/main.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import Crafter.CppDOM;
|
||||||
|
import std;
|
||||||
|
using namespace Crafter;
|
||||||
|
using namespace Crafter::CppDOMBindings;
|
||||||
|
|
||||||
|
HtmlElementView* body = new HtmlElementView("body", R"(
|
||||||
|
<nav>
|
||||||
|
<h2 style="margin-bottom: 20px; padding: 10px; background-color: #f0f0f0;">SPA Navigation Demo</h2>
|
||||||
|
<a id="home" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">Home</a>
|
||||||
|
<a id="about" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">About</a>
|
||||||
|
<a id="contact" style="margin-right: 15px; text-decoration: none; color: blue; cursor: pointer;">Contact</a>
|
||||||
|
</nav>
|
||||||
|
<div id="content" style="min-height: 200px; padding: 15px; border: 1px solid #ccc;"></div>
|
||||||
|
)");
|
||||||
|
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("<h3>Home Page</h3><p>Welcome to the Home page of our Single Page Application!</p><p>This demo shows how to use the new history.pushState and popstate event handling features.</p>");
|
||||||
|
} else if (page == "about") {
|
||||||
|
content->SetInnerHTML("<h3>About Page</h3><p>This is the About page.</p><p>Notice how the URL changes without reloading the page.</p>");
|
||||||
|
} else if (page == "contact") {
|
||||||
|
content->SetInnerHTML("<h3>Contact Page</h3><p>This is the Contact page.</p><p>You can navigate back and forth using the browser's back/forward buttons.</p>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
17
examples/SPAExample/project.json
Normal file
17
examples/SPAExample/project.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "executable",
|
||||||
|
"implementations": ["main"],
|
||||||
|
"target": "wasm32-wasi",
|
||||||
|
"debug" : true,
|
||||||
|
"dependencies": [
|
||||||
|
{
|
||||||
|
"path":"../../project.json",
|
||||||
|
"configuration":"lib-debug"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
examples/SPAExample/run.sh
Executable file
1
examples/SPAExample/run.sh
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
caddy file-server --listen :8080 --root bin/executable
|
||||||
|
|
@ -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;
|
export module Crafter.CppDOM:BindingsExport;
|
||||||
import std;
|
import std;
|
||||||
import :EventTypes;
|
import :EventTypes;
|
||||||
|
import :BindingsImport;
|
||||||
|
|
||||||
export namespace Crafter::CppDOMBindings {
|
export namespace Crafter::CppDOMBindings {
|
||||||
std::int32_t clickHandlerMaxId = 0;
|
std::int32_t clickHandlerMaxId = 0;
|
||||||
|
|
@ -95,6 +76,9 @@ export namespace Crafter::CppDOMBindings {
|
||||||
std::int32_t dragLeaveHandlerMaxId = 0;
|
std::int32_t dragLeaveHandlerMaxId = 0;
|
||||||
std::unordered_map<std::int32_t, std::function<void(Crafter::MouseEvent)>>* dragLeaveHandlers = new std::unordered_map<std::int32_t, std::function<void(Crafter::MouseEvent)>>();
|
std::unordered_map<std::int32_t, std::function<void(Crafter::MouseEvent)>>* dragLeaveHandlers = new std::unordered_map<std::int32_t, std::function<void(Crafter::MouseEvent)>>();
|
||||||
|
|
||||||
|
std::int32_t popStateHandlerMaxId = 0;
|
||||||
|
std::unordered_map<std::int32_t, std::function<void(void)>>* popStateHandlers = new std::unordered_map<std::int32_t, std::function<void(void)>>();
|
||||||
|
|
||||||
std::int32_t fetchHandlerMaxId = 0;
|
std::int32_t fetchHandlerMaxId = 0;
|
||||||
std::unordered_map<std::int32_t, std::function<void(std::string)>>* fetchHandlers = new std::unordered_map<std::int32_t, std::function<void(std::string)>>();
|
std::unordered_map<std::int32_t, std::function<void(std::string)>>* fetchHandlers = new std::unordered_map<std::int32_t, std::function<void(std::string)>>();
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +197,10 @@ extern "C" {
|
||||||
Crafter::CppDOMBindings::fetchHandlers->find(handlerID)->second(response);
|
Crafter::CppDOMBindings::fetchHandlers->find(handlerID)->second(response);
|
||||||
Crafter::CppDOMBindings::fetchHandlers->erase(handlerID);
|
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 {
|
export namespace Crafter::CppDOMBindings {
|
||||||
|
|
@ -228,4 +216,26 @@ export namespace Crafter::CppDOMBindings {
|
||||||
CppDOMBindings::fetchHandlers->insert({id, callback});
|
CppDOMBindings::fetchHandlers->insert({id, callback});
|
||||||
FetchWithBody(url.data(), url.size(), body.data(), body.size(), id);
|
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<void(void)> 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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +142,10 @@ export namespace Crafter::CppDOMBindings {
|
||||||
|
|
||||||
// Value property functions
|
// Value property functions
|
||||||
__attribute__((import_module("env"), import_name("getValue"))) const char* GetValue(void* ptr);
|
__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);
|
__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) {
|
void SetValue(void* ptr, const std::string_view value) {
|
||||||
SetValue(ptr, value.data(), value.size());
|
SetValue(ptr, value.data(), value.size());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue