From e9afde52161c44ff7b71a1ca0bdde272401a29c4 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 12 Nov 2025 15:58:04 +0100 Subject: [PATCH 1/4] website progress --- examples/AllEventHandling/main.cpp | 5 - examples/FetchExample/main.cpp | 1 - examples/Website/backend/main.cpp | 70 +++++++ examples/Website/backend/project.json | 16 ++ examples/Website/frontend/main.cpp | 256 +++++++++++++++++++++++++ examples/Website/frontend/project.json | 17 ++ examples/Website/frontend/run.sh | 1 + 7 files changed, 360 insertions(+), 6 deletions(-) create mode 100644 examples/Website/backend/main.cpp create mode 100644 examples/Website/backend/project.json create mode 100644 examples/Website/frontend/main.cpp create mode 100644 examples/Website/frontend/project.json create mode 100755 examples/Website/frontend/run.sh diff --git a/examples/AllEventHandling/main.cpp b/examples/AllEventHandling/main.cpp index 584caf5..8a8778a 100644 --- a/examples/AllEventHandling/main.cpp +++ b/examples/AllEventHandling/main.cpp @@ -1,8 +1,3 @@ -/* - * Enhanced Event Handling Demo - * This example showcases all available event types in Crafter.CppDOM - */ - import Crafter.CppDOM; using namespace Crafter; import std; diff --git a/examples/FetchExample/main.cpp b/examples/FetchExample/main.cpp index 01dace9..f2209d4 100644 --- a/examples/FetchExample/main.cpp +++ b/examples/FetchExample/main.cpp @@ -7,7 +7,6 @@ int main(){ SetInnerHTML(body, "

Fetch Example

Testing HTTP requests...

"); Fetch("https://httpbin.org/get", [body](std::string result){ - std::cout << "callback recieved2" << std::endl; if (!result.empty()) { SetInnerHTML(body, "

Fetch Example

Response: " + result + "

"); } else { diff --git a/examples/Website/backend/main.cpp b/examples/Website/backend/main.cpp new file mode 100644 index 0000000..901aa77 --- /dev/null +++ b/examples/Website/backend/main.cpp @@ -0,0 +1,70 @@ +/* +Crafter® Build +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 version 3.0 as published by the Free Software Foundation; + +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 +*/ +import Crafter.Network; +import std; +using namespace Crafter; + +struct Note { + std::uint32_t id; + std::string content; +}; +std::vector notes; +int next_id = 1; + +int main() { + ListenerHTTP listener(3000, { + {"/createNote", [&](const HTTPRequest& request) { + if(request.method == "OPTIONS") { + std::cout << CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}) << std::endl; + return CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}); + } + std::uint32_t id = next_id++; + notes.emplace_back(id, request.body); + return CreateResponseHTTP("200 OK", {{"Content-Type", "application/json"}, {"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}}, std::format("{}", id)); + }}, + + {"/deleteNote", [&](const HTTPRequest& request) { + if(request.method == "OPTIONS") { + std::cout << CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}) << std::endl; + return CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}); + } + int idToRemove = std::stoi(request.body); + notes.erase(std::remove_if(notes.begin(), notes.end(), + [idToRemove](Note note) { + return note.id == idToRemove; + }), + notes.end()); + return CreateResponseHTTP("200 OK", {{"Content-Type", "application/json"}, {"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}}); + }}, + + {"/getNotes", [&](const HTTPRequest& request) { + if(request.method == "OPTIONS") { + std::cout << CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}) << std::endl; + return CreateResponseHTTP("200 OK", {{"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}, {"Access-Control-Allow-Methods", "*"}}); + } + std::string result; + for(const Note& note : notes) { + result += std::format("{}\n{}\n\n", note.id, note.content); + } + return CreateResponseHTTP("200 OK", {{"Content-Type", "application/json"}, {"Access-Control-Allow-Origin", "*"}, {"Access-Control-Allow-Headers", "*"}}, result); + }} + }); + + listener.Listen(); +} diff --git a/examples/Website/backend/project.json b/examples/Website/backend/project.json new file mode 100644 index 0000000..fb2c752 --- /dev/null +++ b/examples/Website/backend/project.json @@ -0,0 +1,16 @@ +{ + "name": "website-backend", + "configurations": [ + { + "name": "executable", + "implementations": ["main"], + "libs": ["crafter-thread"], + "dependencies": [ + { + "path":"/home/jorijn/repos/Crafter/Crafter.Network/project.json", + "configuration":"lib-debug" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/Website/frontend/main.cpp b/examples/Website/frontend/main.cpp new file mode 100644 index 0000000..80e70ca --- /dev/null +++ b/examples/Website/frontend/main.cpp @@ -0,0 +1,256 @@ +import Crafter.CppDOM; +import std; +using namespace Crafter; +using namespace Crafter::CppDOMBindings; + +// Structure to represent a note +struct Note { + std::uint32_t id; + std::string content; +}; + +void RenderNotes(); + +// Global vector to store notes +std::vector notes; + +// Create the head section +HtmlElementView* head = new HtmlElementView("head", R"( + Note Taking App + +)"); + +// Create the body section +HtmlElementView* body = new HtmlElementView("body", R"( +
+

📝 Note Taking App

+ +
+

Add New Note

+ +
+ +
+ +
+
+ +
+

Your Notes

+
+
+
+)"); +HtmlElementView* addNoteBtn = new HtmlElementView("addNoteBtn"); +HtmlElementView* noteInput = new HtmlElementView("noteInput"); + +// Function to fetch all notes from the backend +void FetchNotes() { + HtmlElementView loadingIndicator("loading"); + loadingIndicator.SetInnerHTML("

Loading notes...

"); + + // Make HTTP request to backend + Fetch("http://localhost:3000/getNotes", [](std::string content) { + // Parse response - each note is separated by \n\n + notes.clear(); + + // Simple parsing of note data + std::uint_fast32_t pos = 0; + std::uint_fast32_t prevPos = 0; + + while ((pos = content.find('\n', prevPos)) != std::string::npos) { + if (pos > prevPos) { + // Extract ID + std::string idStr = content.substr(prevPos, pos - prevPos); + std::uint_fast32_t id = std::stoul(idStr); + + // Find the next newline for content + std::uint_fast32_t contentStart = pos + 1; + std::uint_fast32_t contentEnd = content.find('\n', contentStart); + if (contentEnd != std::string::npos) { + std::string noteContent = content.substr(contentStart, contentEnd - contentStart); + notes.push_back({id, noteContent}); + } + } + prevPos = content.find('\n', pos) + 1; + if (prevPos >= content.length()) break; + } + + // Update UI with notes + //RenderNotes(); + }); +} + +// Function to add a new note via the backend +void AddNote(const std::string& content) { + if (content.empty()) return; + + HtmlElementView loadingIndicator("loading"); + loadingIndicator.SetInnerHTML("

Saving note...

"); + + // Make POST request to create note + Fetch("http://localhost:3000/createNote", content, [](std::string content) {}); +} + +// Function to delete a note via the backend +void DeleteNote(std::uint32_t id) { + HtmlElementView loadingIndicator("loading"); + loadingIndicator.SetInnerHTML("

Deleting note...

"); + + // Make POST request to delete note + Fetch("http://localhost:3000/deleteNote", std::to_string(id), [](std::string content) { + FetchNotes(); + }); +} + +// Function to render all notes to the DOM +void RenderNotes() { + HtmlElementView notesContainer("notesContainer"); + + if (notes.empty()) { + notesContainer.SetInnerHTML("

No notes yet. Add one below!

"); + return; + } + + std::string html = "
"; + for (const Note& note : notes) { + html += std::format( + R"(
+
{}
+ +
)", + note.id, note.content, note.id + ); + } + html += "
"; + + notesContainer.SetInnerHTML(html); + + // Add event listeners to delete buttons + for (const Note& note : notes) { + HtmlElementView deleteBtn(std::format("note-{}-delete", note.id)); + deleteBtn.AddClickListener([id = note.id](MouseEvent event) { + DeleteNote(id); + }); + } +} + +std::string* currentNoteValue = new std::string(); + +// Main function +int main() { + // Initialize the app + FetchNotes(); + + // Add input listener to track textarea changes + noteInput->AddInputListener([&](InputEvent event) { + *currentNoteValue += event.data; + std::cout << *currentNoteValue << std::endl; + }); + + // Add click listener to add note button + addNoteBtn->AddClickListener([&](MouseEvent event) { + std::cout << "click!" << std::endl; + std::cout << *currentNoteValue << std::endl; + // Use the captured value from input event + if (!currentNoteValue->empty()) { + AddNote(*currentNoteValue); + // Clear the textarea by setting its value to empty string + noteInput->SetInnerHTML(""); + *currentNoteValue = ""; // Reset the stored value + } + }); +} \ No newline at end of file diff --git a/examples/Website/frontend/project.json b/examples/Website/frontend/project.json new file mode 100644 index 0000000..8d02876 --- /dev/null +++ b/examples/Website/frontend/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/Website/frontend/run.sh b/examples/Website/frontend/run.sh new file mode 100755 index 0000000..e706621 --- /dev/null +++ b/examples/Website/frontend/run.sh @@ -0,0 +1 @@ +caddy file-server --listen :8080 --root bin/executable \ No newline at end of file From b96faacf89fd8469f24e8f2835840c3364212889 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 12 Nov 2025 16:25:12 +0100 Subject: [PATCH 2/4] value --- additional/env.js | 13 +++++++++++++ implementations/Crafter.CppDOM-HtmlElement.cpp | 15 +++++++++++++++ interfaces/Crafter.CppDOM-BindingsImport.cppm | 7 +++++++ interfaces/Crafter.CppDOM-HtmlElement.cppm | 4 ++++ 4 files changed, 39 insertions(+) diff --git a/additional/env.js b/additional/env.js index 62f5e57..776e159 100644 --- a/additional/env.js +++ b/additional/env.js @@ -630,6 +630,19 @@ let env = { element.parentNode.removeChild(element); } }, + 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; + }, addClickListener: addClickListener, removeClickListener: removeClickListener, addMouseOverListener: addMouseOverListener, diff --git a/implementations/Crafter.CppDOM-HtmlElement.cpp b/implementations/Crafter.CppDOM-HtmlElement.cpp index 921f277..e8e98ec 100644 --- a/implementations/Crafter.CppDOM-HtmlElement.cpp +++ b/implementations/Crafter.CppDOM-HtmlElement.cpp @@ -62,6 +62,21 @@ namespace Crafter { return CppDOMBindings::HasClass(ptr, className); } + std::string HtmlElementView::GetValue() { + const char* value = CppDOMBindings::GetValue(ptr); + if(value != nullptr) { + std::string result(value); + std::free(value); + return result; + } else { + return ""; + } + } + + void HtmlElementView::SetValue(const std::string_view value) { + CppDOMBindings::SetValue(ptr, value); + } + std::int32_t HtmlElementView::AddClickListener(std::function callback) { std::int32_t id = CppDOMBindings::clickHandlerMaxId++; clickHandlers.push_back(id); diff --git a/interfaces/Crafter.CppDOM-BindingsImport.cppm b/interfaces/Crafter.CppDOM-BindingsImport.cppm index 23ee974..8285003 100644 --- a/interfaces/Crafter.CppDOM-BindingsImport.cppm +++ b/interfaces/Crafter.CppDOM-BindingsImport.cppm @@ -138,4 +138,11 @@ export namespace Crafter::CppDOMBindings { } __attribute__((import_module("env"), import_name("deleteElement"))) void DeleteElement(void* ptr); + + // Value property functions + __attribute__((import_module("env"), import_name("getValue"))) const char* GetValue(void* ptr); + __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()); + } } \ No newline at end of file diff --git a/interfaces/Crafter.CppDOM-HtmlElement.cppm b/interfaces/Crafter.CppDOM-HtmlElement.cppm index 71b64d0..986dec4 100644 --- a/interfaces/Crafter.CppDOM-HtmlElement.cppm +++ b/interfaces/Crafter.CppDOM-HtmlElement.cppm @@ -63,6 +63,10 @@ namespace Crafter { void ToggleClass(const std::string_view className); bool HasClass(const std::string_view className); + // Value property accessors + std::string GetValue(); + void SetValue(const std::string_view value); + std::int32_t AddClickListener(std::function callback); void RemoveClickListener(std::int32_t id); From 3a45e25409f4c9d7e6939eee6d31f70a1f84a677 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 12 Nov 2025 16:36:52 +0100 Subject: [PATCH 3/4] input value example --- examples/InputValueExample/main.cpp | 17 +++++++++++++++++ examples/InputValueExample/project.json | 17 +++++++++++++++++ examples/InputValueExample/run.sh | 1 + implementations/Crafter.CppDOM-HtmlElement.cpp | 2 +- 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 examples/InputValueExample/main.cpp create mode 100644 examples/InputValueExample/project.json create mode 100755 examples/InputValueExample/run.sh diff --git a/examples/InputValueExample/main.cpp b/examples/InputValueExample/main.cpp new file mode 100644 index 0000000..247199a --- /dev/null +++ b/examples/InputValueExample/main.cpp @@ -0,0 +1,17 @@ +import Crafter.CppDOM; +import std; +using namespace Crafter; + + +HtmlElement* body = new HtmlElement("body", R"(

Input GetValue() and SetValue() Example



)"); +HtmlElement* button = new HtmlElement("button"); +HtmlElement* output = new HtmlElement("valueOutput"); +HtmlElement* input = new HtmlElement("input"); + +int main(){ + button->AddClickListener([](Crafter::MouseEvent) { + std::string newValue = input->GetValue(); + output->SetInnerHTML(newValue); + input->SetValue(""); + }); +} \ No newline at end of file diff --git a/examples/InputValueExample/project.json b/examples/InputValueExample/project.json new file mode 100644 index 0000000..9750e20 --- /dev/null +++ b/examples/InputValueExample/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/InputValueExample/run.sh b/examples/InputValueExample/run.sh new file mode 100755 index 0000000..e706621 --- /dev/null +++ b/examples/InputValueExample/run.sh @@ -0,0 +1 @@ +caddy file-server --listen :8080 --root bin/executable \ No newline at end of file diff --git a/implementations/Crafter.CppDOM-HtmlElement.cpp b/implementations/Crafter.CppDOM-HtmlElement.cpp index e8e98ec..c43c678 100644 --- a/implementations/Crafter.CppDOM-HtmlElement.cpp +++ b/implementations/Crafter.CppDOM-HtmlElement.cpp @@ -66,7 +66,7 @@ namespace Crafter { const char* value = CppDOMBindings::GetValue(ptr); if(value != nullptr) { std::string result(value); - std::free(value); + std::free(const_cast(value)); return result; } else { return ""; From c6c62e5852ef3051cf5aa2e938c185f9b6842e1e Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 12 Nov 2025 16:39:01 +0100 Subject: [PATCH 4/4] input example readme --- examples/InputValueExample/README.md | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/InputValueExample/README.md diff --git a/examples/InputValueExample/README.md b/examples/InputValueExample/README.md new file mode 100644 index 0000000..0795820 --- /dev/null +++ b/examples/InputValueExample/README.md @@ -0,0 +1,42 @@ +# InputValueExample + +This example demonstrates how to get and set input element values using Crafter.CppDOM. + +## Features + +- Shows how to get input element values using GetValue() +- Demonstrates how to set input element values using SetValue() +- Illustrates updating UI elements based on input changes +- Shows how to reset input values + +## Usage + +```cpp +import Crafter.CppDOM; +import std; +using namespace Crafter; + +HtmlElement* body = new HtmlElement("body", R"(

Input GetValue() and SetValue() Example



)"); +HtmlElement* button = new HtmlElement("button"); +HtmlElement* output = new HtmlElement("valueOutput"); +HtmlElement* input = new HtmlElement("input"); + +int main(){ + button->AddClickListener([](Crafter::MouseEvent) { + std::string newValue = input->GetValue(); + output->SetInnerHTML(newValue); + input->SetValue(""); + }); +} +``` + +## 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