website example
This commit is contained in:
parent
c6c62e5852
commit
c4f764e4ce
6 changed files with 198 additions and 182 deletions
|
|
@ -1,7 +1,5 @@
|
|||
# About
|
||||
|
||||

|
||||
|
||||
Crafter.CppDOM is a C++ library that exposes the browser DOM api's to C++ WebAssembly.
|
||||
|
||||
# HtmlElement vs HtmlElementView
|
||||
|
|
|
|||
|
|
@ -42,10 +42,29 @@ function writeStringToWasm(str) {
|
|||
return ptr;
|
||||
}
|
||||
|
||||
function getElementById(id, idLenght) {
|
||||
const obj = document.getElementById(decoder.decode(new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, id, idLenght)));
|
||||
function getElementById(id, idLength) {
|
||||
try {
|
||||
// Decode the id from WASM memory
|
||||
const elementId = decoder.decode(
|
||||
new Int8Array(window.crafter_webbuild_wasi.instance.exports.memory.buffer, id, idLength)
|
||||
);
|
||||
|
||||
// Attempt to get the element
|
||||
const obj = document.getElementById(elementId);
|
||||
|
||||
// Check if element exists
|
||||
if (!obj) {
|
||||
throw new Error(`Element with id "${elementId}" does not exist.`);
|
||||
}
|
||||
|
||||
// Store in jsmemory
|
||||
jsmemory.set(++memorycounter, obj);
|
||||
|
||||
return memorycounter;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function setInnerHTML(ptr, html, htmlLenght) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ struct Note {
|
|||
void RenderNotes();
|
||||
|
||||
// Global vector to store notes
|
||||
std::vector<Note> notes;
|
||||
std::vector<Note>* notes = new std::vector<Note>();
|
||||
std::vector<HtmlElementView>* noteButtons = new std::vector<HtmlElementView>();
|
||||
|
||||
// Create the head section
|
||||
HtmlElementView* head = new HtmlElementView("head", R"(
|
||||
|
|
@ -20,16 +21,22 @@ HtmlElementView* head = new HtmlElementView("head", R"(
|
|||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center; /* Horizontal centering */
|
||||
align-items: center; /* Vertical centering */
|
||||
}
|
||||
.app-container {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
width: 50vw;
|
||||
max-width: 600px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
|
|
@ -44,7 +51,6 @@ HtmlElementView* head = new HtmlElementView("head", R"(
|
|||
.input-section textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
|
|
@ -125,7 +131,6 @@ HtmlElementView* body = new HtmlElementView("body", R"(
|
|||
<button id="addNoteBtn">Add Note</button>
|
||||
</div>
|
||||
|
||||
<div id="error"></div>
|
||||
<div id="loading"></div>
|
||||
|
||||
<div class="notes-section">
|
||||
|
|
@ -136,121 +141,97 @@ HtmlElementView* body = new HtmlElementView("body", R"(
|
|||
)");
|
||||
HtmlElementView* addNoteBtn = new HtmlElementView("addNoteBtn");
|
||||
HtmlElementView* noteInput = new HtmlElementView("noteInput");
|
||||
HtmlElementView* loadingIndicator = new HtmlElementView("loading");
|
||||
HtmlElementView* notesContainer = new HtmlElementView("notesContainer");
|
||||
|
||||
// Function to fetch all notes from the backend
|
||||
void FetchNotes() {
|
||||
HtmlElementView loadingIndicator("loading");
|
||||
loadingIndicator.SetInnerHTML("<p>Loading notes...</p>");
|
||||
loadingIndicator->SetInnerHTML("<p>Loading notes...</p>");
|
||||
|
||||
// Make HTTP request to backend
|
||||
Fetch("http://localhost:3000/getNotes", [](std::string content) {
|
||||
// Parse response - each note is separated by \n\n
|
||||
notes.clear();
|
||||
Fetch("http://localhost:3000/getNotes", [](std::string input) {
|
||||
notes->clear();
|
||||
std::istringstream stream(input);
|
||||
std::string line;
|
||||
|
||||
// Simple parsing of note data
|
||||
std::uint_fast32_t pos = 0;
|
||||
std::uint_fast32_t prevPos = 0;
|
||||
while (std::getline(stream, line)) {
|
||||
// Skip empty lines
|
||||
if (line.empty()) continue;
|
||||
|
||||
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);
|
||||
// First line is ID
|
||||
std::uint32_t id = std::stoi(line);
|
||||
|
||||
// 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;
|
||||
// Read content until the next empty line
|
||||
std::string content;
|
||||
while (std::getline(stream, line) && !line.empty()) {
|
||||
if (!content.empty()) content += "\n"; // preserve line breaks
|
||||
content += line;
|
||||
}
|
||||
|
||||
// Update UI with notes
|
||||
//RenderNotes();
|
||||
notes->push_back(Note{id, content});
|
||||
}
|
||||
|
||||
loadingIndicator->SetInnerHTML("<p>Notes loaded!</p>");
|
||||
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("<p>Saving note...</p>");
|
||||
|
||||
// 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("<p>Deleting note...</p>");
|
||||
loadingIndicator->SetInnerHTML("<p>Deleting note...</p>");
|
||||
|
||||
// Make POST request to delete note
|
||||
Fetch("http://localhost:3000/deleteNote", std::to_string(id), [](std::string content) {
|
||||
FetchNotes();
|
||||
loadingIndicator->SetInnerHTML("<p>Note deleted!</p>");
|
||||
});
|
||||
}
|
||||
|
||||
// Function to render all notes to the DOM
|
||||
void RenderNotes() {
|
||||
HtmlElementView notesContainer("notesContainer");
|
||||
|
||||
if (notes.empty()) {
|
||||
notesContainer.SetInnerHTML("<p>No notes yet. Add one below!</p>");
|
||||
if (notes->empty()) {
|
||||
notesContainer->SetInnerHTML("<p>No notes yet. Add one below!</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string html = "<div class='notes-list'>";
|
||||
for (const Note& note : notes) {
|
||||
noteButtons->clear();
|
||||
for (const Note& note : *notes) {
|
||||
html += std::format(
|
||||
R"(<div class='note-item' id='note-{}'>
|
||||
<div class='note-content'>{}</div>
|
||||
<button class='delete-btn' onclick='DeleteNote({})'>Delete</button>
|
||||
<button id="note-{}-delete" class='delete-btn'>Delete</button>
|
||||
</div>)",
|
||||
note.id, note.content, note.id
|
||||
);
|
||||
}
|
||||
html += "</div>";
|
||||
|
||||
notesContainer.SetInnerHTML(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) {
|
||||
for (const Note& note : *notes) {
|
||||
HtmlElementView& view = noteButtons->emplace_back(std::format("note-{}-delete", note.id));
|
||||
view.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
|
||||
std::string noteValue = noteInput->GetValue();
|
||||
std::cout << noteValue << std::endl;
|
||||
if (!noteValue.empty()) {
|
||||
loadingIndicator->SetInnerHTML("<p>Saving note...</p>");
|
||||
Fetch("http://localhost:3000/createNote", noteValue, [¬eValue](std::string content) {
|
||||
notes->push_back({static_cast<std::uint32_t>(std::stoi(content)), noteValue});
|
||||
noteInput->SetValue("");
|
||||
RenderNotes();
|
||||
loadingIndicator->SetInnerHTML("<p>Saved note!</p>");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -34,6 +34,10 @@ namespace Crafter {
|
|||
CppDOMBindings::SetInnerHTML(ptr, html);
|
||||
}
|
||||
|
||||
HtmlElementView::HtmlElementView(HtmlElementView&& other) : ptr(other.ptr), clickHandlers(std::move(other.clickHandlers)), mouseOverHandlers(std::move(other.mouseOverHandlers)), mouseOutHandlers(std::move(other.mouseOutHandlers)), mouseMoveHandlers(std::move(other.mouseMoveHandlers)), mouseDownHandlers(std::move(other.mouseDownHandlers)), mouseUpHandlers(std::move(other.mouseUpHandlers)), focusHandlers(std::move(other.focusHandlers)), blurHandlers(std::move(other.blurHandlers)), keyDownHandlers(std::move(other.keyDownHandlers)), keyUpHandlers(std::move(other.keyUpHandlers)), keyPressHandlers(std::move(other.keyPressHandlers)), changeHandlers(std::move(other.changeHandlers)), submitHandlers(std::move(other.submitHandlers)), inputHandlers(std::move(other.inputHandlers)), resizeHandlers(std::move(other.resizeHandlers)), scrollHandlers(std::move(other.scrollHandlers)), contextMenuHandlers(std::move(other.contextMenuHandlers)), dragStartHandlers(std::move(other.dragStartHandlers)), dragEndHandlers(std::move(other.dragEndHandlers)), dropHandlers(std::move(other.dropHandlers)), dragOverHandlers(std::move(other.dragOverHandlers)), dragEnterHandlers(std::move(other.dragEnterHandlers)), dragLeaveHandlers(std::move(other.dragLeaveHandlers)), wheelHandlers(std::move(other.wheelHandlers)) {
|
||||
other.ptr = nullptr;
|
||||
}
|
||||
|
||||
void HtmlElementView::SetInnerHTML(const std::string_view html) {
|
||||
CppDOMBindings::SetInnerHTML(ptr, html);
|
||||
}
|
||||
|
|
@ -413,7 +417,8 @@ namespace Crafter {
|
|||
CppDOMBindings::RemoveWheelListener(ptr, id);
|
||||
}
|
||||
|
||||
HtmlElementView::~HtmlElementView(){
|
||||
HtmlElementView::~HtmlElementView() {
|
||||
if(ptr != nullptr) {
|
||||
for(std::int32_t handler : clickHandlers) {
|
||||
CppDOMBindings::clickHandlers->erase(handler);
|
||||
CppDOMBindings::RemoveClickListener(ptr, handler);
|
||||
|
|
@ -512,6 +517,7 @@ namespace Crafter {
|
|||
}
|
||||
CppDOMBindings::FreeJs(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
HtmlElement::HtmlElement(const std::string_view id): HtmlElementView(id) {
|
||||
|
||||
|
|
@ -521,8 +527,14 @@ namespace Crafter {
|
|||
|
||||
}
|
||||
|
||||
HtmlElement::HtmlElement(HtmlElement&& other): HtmlElementView(std::move(other)) {
|
||||
|
||||
}
|
||||
|
||||
HtmlElement::~HtmlElement() {
|
||||
if(ptr != nullptr) {
|
||||
CppDOMBindings::DeleteElement(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export namespace Crafter::CppDOMBindings {
|
|||
}
|
||||
__attribute__((import_module("env"), import_name("setInnerHTML"))) void SetInnerHTML(void* ptr, const char* html, std::int32_t htmlLenght);
|
||||
void SetInnerHTML(void* ptr, const std::string_view html) {
|
||||
std::cout << "bind" << std::endl;
|
||||
SetInnerHTML(ptr, html.data(), html.size());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import :EventTypes;
|
|||
namespace Crafter {
|
||||
export class HtmlElementView {
|
||||
public:
|
||||
void* const ptr;
|
||||
void* ptr;
|
||||
std::vector<std::int32_t> clickHandlers;
|
||||
std::vector<std::int32_t> mouseOverHandlers;
|
||||
std::vector<std::int32_t> mouseOutHandlers;
|
||||
|
|
@ -55,6 +55,10 @@ namespace Crafter {
|
|||
|
||||
HtmlElementView(const std::string_view id);
|
||||
HtmlElementView(const std::string_view id, const std::string_view html);
|
||||
HtmlElementView(HtmlElementView&&);
|
||||
HtmlElementView(const HtmlElementView&) = delete;
|
||||
HtmlElementView& operator=(const HtmlElementView&) = delete;
|
||||
~HtmlElementView();
|
||||
void SetInnerHTML(const std::string_view html);
|
||||
void SetStyle(const std::string_view style);
|
||||
void SetProperty(const std::string_view property, const std::string_view value);
|
||||
|
|
@ -138,14 +142,15 @@ namespace Crafter {
|
|||
|
||||
std::int32_t AddWheelListener(std::function<void(WheelEvent)> callback);
|
||||
void RemoveWheelListener(std::int32_t id);
|
||||
|
||||
~HtmlElementView();
|
||||
};
|
||||
|
||||
export class HtmlElement : public HtmlElementView {
|
||||
public:
|
||||
HtmlElement(const std::string_view id);
|
||||
HtmlElement(const std::string_view id, const std::string_view html);
|
||||
HtmlElement(HtmlElement&&);
|
||||
HtmlElement(const HtmlElement&) = delete;
|
||||
HtmlElement& operator=(const HtmlElement&) = delete;
|
||||
~HtmlElement();
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue