Skip to content

Commit

Permalink
Merge pull request #106 from NuiCpp/devel
Browse files Browse the repository at this point in the history
Added delocalized element utility.
  • Loading branch information
5cript authored Jan 25, 2024
2 parents 4bca60e + e8f0fbd commit e4a8146
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 1 deletion.
127 changes: 127 additions & 0 deletions nui/include/nui/frontend/utility/delocalized.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#pragma once

#include <nui/frontend/element_renderer.hpp>
#include <nui/frontend/dom/element.hpp>
#include <nui/frontend/event_system/observed_value.hpp>
#include <nui/frontend/elements/div.hpp>
#include <nui/frontend/elements/nil.hpp>
#include <nui/frontend/attributes/class.hpp>
#include <nui/frontend/attributes/style.hpp>

#include <memory>
#include <optional>
#include <string>

namespace Nui
{
/**
* @brief A delocalized element can switch positions in several slots.
*/
template <typename SlotId>
class Delocalized
{
public:
Delocalized(Nui::ElementRenderer renderer = {})
: delocalizedElement_{[&renderer]() -> decltype(delocalizedElement_) {
if (renderer)
{
auto element = Dom::Element::makeElement(HtmlElement{"div", &RegularHtmlElementBridge});
element->replaceElement(renderer);
return element;
}
else
{
return {};
}
}()}
, slotId_{std::make_unique<Nui::Observed<SlotId>>()}
{}
Delocalized(Delocalized const&) = delete;
Delocalized(Delocalized&&) = default;
Delocalized& operator=(Delocalized const&) = delete;
Delocalized& operator=(Delocalized&&) = default;

std::shared_ptr<Dom::Element> element()
{
return delocalizedElement_;
}
void element(Nui::ElementRenderer renderer)
{
delocalizedElement_ = Dom::Element::makeElement(HtmlElement{"div", &RegularHtmlElementBridge});
delocalizedElement_->replaceElement(renderer);
}
bool hasElement() const
{
return delocalizedElement_ != nullptr;
}
void initializeIfEmpty(Nui::ElementRenderer renderer)
{
if (!hasElement())
element(std::move(renderer));
}

void slot(SlotId slot)
{
*slotId_ = slot;
}
SlotId slot() const
{
return slotId_->value();
}

template <typename SlotIdB>
friend ElementRenderer delocalizedSlot(
SlotIdB slot,
Delocalized<SlotIdB>& delocalizedElement,
std::vector<Attribute> wrapperAttributes,
Nui::ElementRenderer alternative);

private:
std::shared_ptr<Dom::Element> delocalizedElement_;
// Wrapped in a unique_ptr for pointer stability.
std::unique_ptr<Nui::Observed<SlotId>> slotId_;
};

template <typename SlotId>
ElementRenderer delocalizedSlot(
SlotId slot,
Delocalized<SlotId>& delocalizedElement,
std::vector<Attribute> wrapperAttributes = {},
ElementRenderer alternative = Elements::div{Attributes::style = "display: none"}())
{
using namespace Elements;
using namespace Attributes;

auto element = delocalizedElement.delocalizedElement_;

return Elements::div{std::move(wrapperAttributes)}(
observe(*delocalizedElement.slotId_),
[element, slot, &observedId = *delocalizedElement.slotId_, alternative]() mutable {
return [element, slot, &observedId, alternative](
Dom::Element& wrapper, Renderer const& gen) -> std::shared_ptr<Dom::Element> {
if (element && slot == observedId.value())
{
wrapper.val().call<void>("appendChild", element->val());
return nil()(wrapper, gen);
}
else
{
if (alternative)
return alternative(wrapper, gen);
else
return nil()(wrapper, gen);
}
};
});
}

inline ElementRenderer delocalizedSlot(
char const* slot,
Delocalized<std::string>& delocalizedElement,
std::vector<Attribute> wrapperAttributes = {},
ElementRenderer alternative = Elements::div{Attributes::style = "display: none"}())
{
return delocalizedSlot<std::string>(
std::string{slot}, delocalizedElement, std::move(wrapperAttributes), std::move(alternative));
}
}
3 changes: 2 additions & 1 deletion nui/test/nui/engine/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ namespace Nui::Tests::Engine
printIndent(indent + 1);
allValues[(*it)->uid()].print(indent + 1, referenceStack);
}
std::cout << "\n";
printIndent(indent);
std::cout << "\n]";
std::cout << "]";
}

void Array::updateArrayObject()
Expand Down
2 changes: 2 additions & 0 deletions nui/test/nui/engine/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ namespace Nui::Tests::Engine
auto elem = createBasicElement(tag);
elem.set("nodeType", int{1});
elem.set("appendChild", Function{[self = elem](Nui::val value) -> Nui::val {
if (value.hasOwnProperty("parentNode"))
value["parentNode"].call<void>("removeChild", value);
value.set("parentNode", self);
self["childNodes"].template as<Array&>().push_back(value.handle());
return self["children"].template as<Array&>().push_back(value.handle());
Expand Down
108 changes: 108 additions & 0 deletions nui/test/nui/test_delocalized.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#pragma once

#include <gtest/gtest.h>

#include "common_test_fixture.hpp"
#include "engine/global_object.hpp"
#include "engine/document.hpp"

#include <nui/frontend/elements.hpp>
#include <nui/frontend/attributes.hpp>
#include <nui/frontend/utility/delocalized.hpp>

#include <vector>
#include <string>

namespace Nui::Tests
{
using namespace Engine;
using namespace std::string_literals;

class TestDelocalized : public CommonTestFixture
{
protected:
auto bodyChildren()
{
return Nui::val::global("document")["body"]["children"];
}

public:
Delocalized<std::string> delocalizedElement_;
};

TEST_F(TestDelocalized, SingleSlotDelocalizedRender)
{
using namespace Nui::Elements;
using namespace Nui::Attributes;
using Nui::Elements::div;
using Nui::Elements::span;

delocalizedElement_.initializeIfEmpty(span{}("Hello"));
delocalizedElement_.slot("slot1");

render(body{}(delocalizedSlot("slot1", delocalizedElement_)));

ASSERT_EQ(bodyChildren()["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["tagName"].as<std::string>(), "div");
ASSERT_EQ(bodyChildren()[0]["children"]["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["children"][0]["tagName"].as<std::string>(), "span");
EXPECT_EQ(bodyChildren()[0]["children"][0]["textContent"].as<std::string>(), "Hello");
}

TEST_F(TestDelocalized, SlotShowsAlternativeWhenNotActive)
{
using namespace Nui::Elements;
using namespace Nui::Attributes;
using Nui::Elements::div;
using Nui::Elements::span;

delocalizedElement_.initializeIfEmpty(span{}("Hello"));
delocalizedElement_.slot("slot2");

render(body{}(delocalizedSlot("slot1", delocalizedElement_)));

ASSERT_EQ(bodyChildren()["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["tagName"].as<std::string>(), "div");
ASSERT_EQ(bodyChildren()[0]["children"]["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["children"][0]["tagName"].as<std::string>(), "div");
EXPECT_EQ(bodyChildren()[0]["children"][0]["attributes"]["style"].as<std::string>(), "display: none");
}

TEST_F(TestDelocalized, WrapperGetsAttributesAssignedWhenSet)
{
using namespace Nui::Elements;
using namespace Nui::Attributes;
using Nui::Elements::div;
using Nui::Elements::span;

delocalizedElement_.initializeIfEmpty(span{}("Hello"));
delocalizedElement_.slot("slot1");

render(body{}(delocalizedSlot("slot1", delocalizedElement_, std::vector<Attribute>{class_ = "wrapper"})));

ASSERT_EQ(bodyChildren()["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["tagName"].as<std::string>(), "div");
EXPECT_EQ(bodyChildren()[0]["attributes"]["class"].as<std::string>(), "wrapper");
ASSERT_EQ(bodyChildren()[0]["children"]["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["children"][0]["tagName"].as<std::string>(), "span");
}

TEST_F(TestDelocalized, CanUseDifferentReplacementElement)
{
using namespace Nui::Elements;
using namespace Nui::Attributes;
using Nui::Elements::div;
using Nui::Elements::span;

delocalizedElement_.initializeIfEmpty(span{}("Hello"));
delocalizedElement_.slot("slotX");

render(body{}(delocalizedSlot("slot1", delocalizedElement_, {}, div{}("Hello"))));

ASSERT_EQ(bodyChildren()["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["tagName"].as<std::string>(), "div");
ASSERT_EQ(bodyChildren()[0]["children"]["length"].as<long long>(), 1);
EXPECT_EQ(bodyChildren()[0]["children"][0]["tagName"].as<std::string>(), "div");
EXPECT_EQ(bodyChildren()[0]["children"][0]["textContent"].as<std::string>(), "Hello");
}
}
1 change: 1 addition & 0 deletions nui/test/nui/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "components/test_table.hpp"
#include "components/test_dialog.hpp"
#include "components/test_select.hpp"
#include "test_delocalized.hpp"

#include <gtest/gtest.h>

Expand Down

0 comments on commit e4a8146

Please sign in to comment.