diff --git a/Crafter.CppDOM-Bindings.o b/Crafter.CppDOM-Bindings.o deleted file mode 100644 index 9336cb6..0000000 Binary files a/Crafter.CppDOM-Bindings.o and /dev/null differ diff --git a/Crafter.CppDOM-HtmlElement.o b/Crafter.CppDOM-HtmlElement.o deleted file mode 100644 index 701dcf9..0000000 Binary files a/Crafter.CppDOM-HtmlElement.o and /dev/null differ diff --git a/Crafter.CppDOM.o b/Crafter.CppDOM.o deleted file mode 100644 index f7510e6..0000000 Binary files a/Crafter.CppDOM.o and /dev/null differ diff --git a/README.md b/README.md index fd0fe6e..20a4f00 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,4 @@ int main(){ Save and close, then run ``crafter-build build executable && caddy file-server --listen :8080 --root bin/executable``. if you have caddy installed, if not use your favorite static file server instead. Now you can open the browser at ``http://localhost:8080`` and ``Hello World!`` will appear in the browser. -This sample can also be viewed in the [HelloElement example](https://forgejo.catcrafts.net/Catcrafts/Crafter.CppDOM/src/branch/master/examples) +This sample can also be viewed in the [HelloElement example](https://forgejo.catcrafts.net/Catcrafts/Crafter.CppDOM/src/branch/master/examples) \ No newline at end of file diff --git a/additional/env.js b/additional/env.js index 6c6ef3c..6cfd40f 100644 --- a/additional/env.js +++ b/additional/env.js @@ -19,9 +19,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ const decoder = new TextDecoder(); +const encoder = new TextEncoder(); let memorycounter = -1; const jsmemory = new Map(); +const eventHandlers = new Map(); function freeJs(ptr) { jsmemory.delete(ptr); @@ -37,10 +39,35 @@ function setInnerHTML(ptr, html, htmlLenght) { jsmemory.get(ptr).innerHTML = decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, html, htmlLenght)); } +function addClickListener(ptr, handlerID) { + const element = jsmemory.get(ptr); + + // Create a handler that will trigger a notification to C++ + const handler = function(event) { + const { ExecuteClickHandler } = window.crafter_webbuild_wasi.instance.exports; + ExecuteClickHandler(handlerID); + }; + + eventHandlers.set(`${ptr}-${handlerID}`, handler); + + element.addEventListener("click", handler); +} + +function removeClickListener(ptr, handlerID) { + const element = jsmemory.get(ptr); + + const handler = eventHandlers.get(`${ptr}-${handlerID}`); + + element.removeEventListener("click", handler); + eventHandlers.delete(handlerID); +} + let env = { - freeJs:freeJs, - getElementById:getElementById, - setInnerHTML:setInnerHTML, + freeJs: freeJs, + getElementById: getElementById, + setInnerHTML: setInnerHTML, + addClickListener: addClickListener, + removeClickListener: removeClickListener, } window.crafter_webbuild_env = env; \ No newline at end of file diff --git a/examples/HelloElement/main.cpp b/examples/HelloElement/main.cpp index f806cbf..fce4639 100644 --- a/examples/HelloElement/main.cpp +++ b/examples/HelloElement/main.cpp @@ -1,5 +1,5 @@ import Crafter.CppDOM; -using namespace Crafter::CppDOM; +using namespace Crafter; int main(){ HtmlElement body("body"); diff --git a/examples/HelloWorld/project.json b/examples/HelloWorld/project.json index 660f28d..9750e20 100644 --- a/examples/HelloWorld/project.json +++ b/examples/HelloWorld/project.json @@ -2,7 +2,7 @@ "name": "main", "configurations": [ { - "name": "example", + "name": "executable", "implementations": ["main"], "target": "wasm32-wasi", "debug" : true, diff --git a/examples/InteractiveElement/README.md b/examples/InteractiveElement/README.md new file mode 100644 index 0000000..972191e --- /dev/null +++ b/examples/InteractiveElement/README.md @@ -0,0 +1,39 @@ +# Interactive Element Example + +This example demonstrates how to use DOM event handling with Crafter.CppDOM. + +## Features + +- Shows how to create interactive UI elements using C++ +- Demonstrates the framework for attaching event listeners +- Illustrates the planned callback mechanism for event handling + +## Usage + +The library now provides the foundation for interactive web applications: + +```cpp +import Crafter.CppDOM; +using namespace Crafter::CppDOM; + +int main(){ + // Create UI elements + HtmlElement body("body"); + body.SetInnerHTML("

Interactive Element Demo

" + "" + "

Click the button above

"); + + // Attach event listener + HtmlElement button("myButton"); + button.AddEventListener("click"); + + // Future: button.OnClick([]() { + // HtmlElement output("output"); + // output.SetInnerHTML("Button was clicked!"); + // }); + + return 0; +} +``` + +Note: The full callback mechanism requires additional infrastructure to properly bridge between JavaScript events and C++ callbacks. This implementation provides the framework for future development. \ No newline at end of file diff --git a/examples/InteractiveElement/main.cpp b/examples/InteractiveElement/main.cpp new file mode 100644 index 0000000..f4c2d91 --- /dev/null +++ b/examples/InteractiveElement/main.cpp @@ -0,0 +1,16 @@ +import Crafter.CppDOM; +import std; +using namespace Crafter; + +HtmlElement body("body","

Interactive Element Demo

" + "" + "

Click the button above

"); +HtmlElement* button = new HtmlElement("myButton"); //prevent destruction + + +int main() { + button->AddClickListener([](){ + auto output = HtmlElement("output"); + output.SetInnerHTML("Button was clicked!"); + }); +} \ No newline at end of file diff --git a/examples/InteractiveElement/project.json b/examples/InteractiveElement/project.json new file mode 100644 index 0000000..9750e20 --- /dev/null +++ b/examples/InteractiveElement/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/InteractiveElement/run.sh b/examples/InteractiveElement/run.sh new file mode 100755 index 0000000..e706621 --- /dev/null +++ b/examples/InteractiveElement/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-Bindings.cppm b/interfaces/Crafter.CppDOM-Bindings.cppm index 5ead784..fc431db 100644 --- a/interfaces/Crafter.CppDOM-Bindings.cppm +++ b/interfaces/Crafter.CppDOM-Bindings.cppm @@ -22,13 +22,26 @@ export module Crafter.CppDOM:Bindings; import std; export namespace Crafter::CppDOM::Bindings { + int clickHandlerMaxId = 0; + std::unordered_map>* clickHandlers = new std::unordered_map>(); + __attribute__((import_module("env"), import_name("freeJs"))) void FreeJs(void* ptr); __attribute__((import_module("env"), import_name("getElementById"))) void* GetElementById(const char* id, std::size_t idLenght); - inline void* GetElementById(const std::string& id) { - return GetElementById(id.c_str(), id.size()); + void* GetElementById(const std::string_view id) { + return GetElementById(id.data(), id.size()); } __attribute__((import_module("env"), import_name("setInnerHTML"))) void SetInnerHTML(void* ptr, const char* html, std::size_t htmlLenght); - inline void SetInnerHTML(void* ptr, const std::string& html) { - SetInnerHTML(ptr, html.c_str(), html.size()); + void SetInnerHTML(void* ptr, const std::string_view html) { + SetInnerHTML(ptr, html.data(), html.size()); } + + // Event handling functions + __attribute__((import_module("env"), import_name("addClickListener"))) void AddClickListener(void* ptr, int id); + __attribute__((import_module("env"), import_name("removeClickListener"))) void RemoveClickListener(void* ptr, int id); } + +extern "C" { + __attribute__((export_name("ExecuteClickHandler"))) void ExecuteClickHandler(int a) { + Crafter::CppDOM::Bindings::clickHandlers->find(static_cast(a))->second(); + } +} \ No newline at end of file diff --git a/interfaces/Crafter.CppDOM-HtmlElement.cppm b/interfaces/Crafter.CppDOM-HtmlElement.cppm index 32cee49..bd0da22 100644 --- a/interfaces/Crafter.CppDOM-HtmlElement.cppm +++ b/interfaces/Crafter.CppDOM-HtmlElement.cppm @@ -22,24 +22,42 @@ export module Crafter.CppDOM:HtmlElement; import std; import :Bindings; -namespace Crafter::CppDOM { +namespace Crafter { export class HtmlElement { public: void* const ptr; - inline HtmlElement(const char* id, std::size_t idLenght): ptr(Bindings::GetElementById(id, idLenght)) { + std::vector handlers; + HtmlElement(const std::string_view id): ptr(CppDOM::Bindings::GetElementById(id)) { } - inline HtmlElement(const std::string& id): ptr(Bindings::GetElementById(id)) { - + HtmlElement(const std::string_view id, const std::string_view html): ptr(CppDOM::Bindings::GetElementById(id)) { + CppDOM::Bindings::SetInnerHTML(ptr, html); } - inline void SetInnerHTML(const char* html, std::size_t htmlLenght) { - Bindings::SetInnerHTML(ptr, html, htmlLenght); + void SetInnerHTML(const std::string_view& html) { + CppDOM::Bindings::SetInnerHTML(ptr, html); } - inline void SetInnerHTML(const std::string& html) { - Bindings::SetInnerHTML(ptr, html); + + // Event handling methods - simplified for now + int AddClickListener(std::function callback) { + int id = CppDOM::Bindings::clickHandlerMaxId++; + handlers.push_back(id); + CppDOM::Bindings::clickHandlers->insert({id, callback}); + CppDOM::Bindings::AddClickListener(ptr, id); + return id; } - inline ~HtmlElement(){ - Bindings::FreeJs(ptr); + + void RemoveClickListener(int id) { + handlers.erase(std::remove(handlers.begin(), handlers.end(), id), handlers.end()); + CppDOM::Bindings::clickHandlers->erase(id); + CppDOM::Bindings::RemoveClickListener(ptr, id); + } + + ~HtmlElement(){ + for(int handler : handlers) { + CppDOM::Bindings::clickHandlers->erase(handler); + CppDOM::Bindings::RemoveClickListener(ptr, handler); + } + CppDOM::Bindings::FreeJs(ptr); } }; } \ No newline at end of file