Nui
html_element.hpp
Go to the documentation of this file.
1 #pragma once
2 
14 #include <nui/concepts.hpp>
16 
17 #include <nui/frontend/val.hpp>
18 
19 #include <vector>
20 #include <utility>
21 #include <concepts>
22 #include <memory>
23 #include <functional>
24 #include <optional>
25 #include <initializer_list>
26 
27 namespace Nui
28 {
29  class HtmlElement;
30 
31  //----------------------------------------------------------------------------------------------
32  // Workaround Helper Classes for Linkage Bug in WASM-LD.
33  //----------------------------------------------------------------------------------------------
34  template <typename HtmlElem>
36  {
37  public:
39  HtmlElem htmlElement,
40  std::vector<std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>> children);
41 
42  std::shared_ptr<Dom::Element> operator()(Dom::Element& parentElement, Renderer const& gen) const;
43 
44  private:
45  HtmlElem htmlElement_;
46  std::vector<std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>> children_;
47  };
48  template <typename HtmlElem>
50  {
51  public:
52  TrivialRenderer(HtmlElem htmlElement);
53  std::shared_ptr<Dom::Element> operator()(Dom::Element& parentElement, Renderer const& gen) const;
54 
55  private:
56  HtmlElem htmlElement_;
57  };
58  //----------------------------------------------------------------------------------------------
60  {
61  public:
62  friend class DomElement;
63 
64  HtmlElement(HtmlElement const&) = default;
65  HtmlElement(HtmlElement&&) = default;
66  virtual ~HtmlElement() = default;
67  HtmlElement(char const* name, HtmlElementBridge const* bridge, std::vector<Attribute> const& attributes)
68  : name_{name}
69  , bridge_{bridge}
70  , attributes_{attributes}
71  {}
72  HtmlElement(char const* name, HtmlElementBridge const* bridge, std::vector<Attribute>&& attributes)
73  : name_{name}
74  , bridge_{bridge}
75  , attributes_{std::move(attributes)}
76  {}
77  template <typename... T>
78  HtmlElement(char const* name, HtmlElementBridge const* bridge, T&&... attributes)
79  : name_{name}
80  , bridge_{bridge}
81  , attributes_{std::forward<T>(attributes)...}
82  {}
83 
85  {
86  return {name_, bridge_, attributes_};
87  }
88 
89  private:
90  template <typename... ObservedValues, std::invocable GeneratorT>
91  auto reactiveRender(ObservedValueCombinator<ObservedValues...> observedValues, GeneratorT&& elementRenderer) &&
92  {
93  return [self = this->clone(),
94  observedValues = std::move(observedValues),
95  elementRenderer =
96  std::forward<GeneratorT>(elementRenderer)](auto& parentElement, Renderer const& gen) {
97  using ElementType = std::decay_t<decltype(parentElement)>;
98 
99  // function is called when observed values change to refabricate the children.
100  auto childrenRefabricator = std::make_shared<std::function<void()>>();
101 
102  auto&& createdSelf = renderElement(gen, parentElement, self);
103 
104  if (gen.type == RendererType::Inplace)
105  {
106  *childrenRefabricator = [observedValues,
107  elementRenderer,
108  fragmentContext = Detail::FragmentContext<ElementType>{},
109  createdSelfWeak = std::weak_ptr<ElementType>(createdSelf),
110  childrenRefabricator]() mutable {
111  fragmentContext.clear();
112 
113  auto parent = createdSelfWeak.lock();
114  if (!parent || observedValues.isAnyExpired())
115  {
116  childrenRefabricator.reset();
117  return;
118  }
119 
120  Detail::makeChildrenUpdateEvent(observedValues, childrenRefabricator, createdSelfWeak);
121 
122  // regenerate children
123  if constexpr ((std::is_same_v<decltype(elementRenderer()), std::string>))
124  parent->setTextContent(elementRenderer());
125  else
126  fragmentContext.push(elementRenderer()(*parent, Renderer{.type = RendererType::Fragment}));
127  };
128  }
129  else
130  {
131  *childrenRefabricator = [observedValues,
132  elementRenderer,
133  createdSelfWeak = std::weak_ptr<ElementType>(createdSelf),
134  childrenRefabricator]() mutable {
135  auto parent = createdSelfWeak.lock();
136  if (!parent || observedValues.isAnyExpired())
137  {
138  childrenRefabricator.reset();
139  return;
140  }
141 
142  // clear children
143  parent->clearChildren();
144 
145  Detail::makeChildrenUpdateEvent(observedValues, childrenRefabricator, createdSelfWeak);
146 
147  // regenerate children
148  if constexpr ((std::is_same_v<decltype(elementRenderer()), std::string>))
149  parent->setTextContent(elementRenderer());
150  else
151  elementRenderer()(*parent, Renderer{.type = RendererType::Append});
152  };
153  }
154 
155  (*childrenRefabricator)();
156  return createdSelf;
157  };
158  }
159 
160  template <typename RangeType, typename GeneratorT>
161  auto rangeRender(RangeType&& valueRange, GeneratorT&& elementRenderer) &&
162  {
163  return [self = this->clone(),
164  rangeRenderer = std::make_shared<
165  Detail::RangeRenderer<std::decay_t<RangeType>, GeneratorT, RangeType::isRandomAccess>>(
166  std::move(valueRange).underlying(), std::forward<GeneratorT>(elementRenderer))](
167  auto& parentElement, Renderer const& gen) mutable {
168  if (gen.type == RendererType::Inplace)
169  throw std::runtime_error("fragments are not supported for range generators");
170 
171  auto&& materialized = renderElement(gen, parentElement, self);
172  (*rangeRenderer)(materialized);
173  return materialized;
174  };
175  }
176 
177  public:
178  // Children functions:
179  template <typename... ElementT>
180  requires requires(ElementT&&... elements) {
181  std::vector<std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>>{
182  std::forward<ElementT>(elements)...};
183  }
184  auto operator()(ElementT&&... elements) &&
185  {
186  return std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>{
188  this->clone(),
189  std::vector<std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>>{
190  std::forward<ElementT>(elements)...}}};
191 
192  // Unknown Linkage BUG in wasm-ld :(
193  // return
194  // [self = this->clone(),
195  // children = std::vector<std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer
196  // const&)>>{
197  // std::forward<ElementT>(elements)...}](auto& parentElement, Renderer const& gen) {
198  // auto materialized = renderElement(gen, parentElement, self);
199  // materialized->appendElements(children);
200  // return materialized;
201  // };
202  }
203 
204  // Trivial case:
205  auto operator()() &&
206  {
207  return std::function<std::shared_ptr<Dom::Element>(Dom::Element&, Renderer const&)>{
209 
210  // Unknown Linkage BUG in wasm-ld :(
211  // return [self = this->clone()](auto& parentElement, Renderer const& gen) {
212  // return renderElement(gen, parentElement, self);
213  // };
214  }
215 
216  // Text content functions:
217  template <typename T>
218  requires std::same_as<std::decay_t<T>, std::string>
219  auto operator()(T&& text) &&
220  {
221  return [self = this->clone(), text = std::forward<T>(text)](auto& parentElement, Renderer const& gen) {
222  auto materialized = renderElement(gen, parentElement, self);
223  materialized->setTextContent(text);
224  return materialized;
225  };
226  }
227  auto operator()(std::string_view view) &&
228  {
229  return [self = this->clone(), view](auto& parentElement, Renderer const& gen) {
230  auto materialized = renderElement(gen, parentElement, self);
231  materialized->setTextContent(view);
232  return materialized;
233  };
234  }
235  auto operator()(char const* text) &&
236  {
237  return [self = this->clone(), text](auto& parentElement, Renderer const& gen) {
238  auto materialized = renderElement(gen, parentElement, self);
239  materialized->setTextContent(text);
240  return materialized;
241  };
242  }
243  template <typename T>
244  requires Fundamental<T>
245  auto operator()(T fundamental) &&
246  {
247  return [self = this->clone(), fundamental](auto& parentElement, Renderer const& gen) {
248  auto materialized = renderElement(gen, parentElement, self);
249  materialized->setTextContent(std::to_string(fundamental));
250  return materialized;
251  };
252  }
253 
254  // Generator functions:
255  template <typename GeneratorT>
256  requires InvocableReturns<GeneratorT, std::string>
257  auto operator()(GeneratorT&& textGenerator) &&
258  {
259  return [self = this->clone(),
260  textGenerator = std::forward<GeneratorT>(textGenerator)](auto& parentElement, Renderer const& gen) {
261  auto materialized = renderElement(gen, parentElement, self);
262  materialized->setTextContent(textGenerator());
263  return materialized;
264  };
265  }
266  template <std::invocable GeneratorT>
267  requires(!InvocableReturns<GeneratorT, std::string>)
268  auto operator()(GeneratorT&& elementRenderer) &&
269  {
270  return [self = this->clone(), elementRenderer = std::forward<GeneratorT>(elementRenderer)](
271  auto& parentElement, Renderer const& gen) {
272  auto materialized = renderElement(gen, parentElement, self);
273  elementRenderer()(*materialized, Renderer{.type = RendererType::Append});
274  return materialized;
275  };
276  }
277  template <typename T, std::invocable<T&, Renderer const&> GeneratorT>
278  requires InvocableReturns<GeneratorT, std::string>
279  auto operator()(GeneratorT&& elementRenderer) &&
280  {
281  return [self = this->clone(), elementRenderer = std::forward<GeneratorT>(elementRenderer)](
282  auto& parentElement, Renderer const& gen) {
283  auto materialized = renderElement(gen, parentElement, self);
284  materialized->setTextContent(elementRenderer(*materialized, Renderer{.type = RendererType::Append}));
285  return materialized;
286  };
287  }
288 
289  // Reactive functions:
290  template <typename... ObservedValues, std::invocable GeneratorT>
292  {
293  return std::move(*this).operator()(std::move(combinator).split(), std::move(combinator).generator());
294  }
295  template <typename... ObservedValues, std::invocable GeneratorT>
296  auto operator()(ObservedValueCombinator<ObservedValues...> observedValues, GeneratorT&& elementRenderer) &&
297  {
298  return std::move(*this).reactiveRender(
299  std::move(observedValues), std::forward<GeneratorT>(elementRenderer));
300  }
301 
302  // Range functions:
303  template <typename ObservedValue, typename GeneratorT>
304  auto operator()(ObservedRange<ObservedValue> observedRange, GeneratorT&& elementRenderer) &&
305  {
306  return std::move(*this).rangeRender(std::move(observedRange), std::forward<GeneratorT>(elementRenderer));
307  }
308  template <typename ObservedValue, typename GeneratorT>
309  auto operator()(std::pair<ObservedRange<ObservedValue>, GeneratorT>&& mapPair) &&
310  {
311  return std::move(*this).rangeRender(std::move(mapPair.first), std::move(mapPair.second));
312  }
313  template <typename IteratorT, typename GeneratorT, typename... ObservedT>
314  auto operator()(UnoptimizedRange<IteratorT, ObservedT...>&& unoptimizedRange, GeneratorT&& elementRenderer) &&
315  {
316  return [self = this->clone(),
317  rangeRenderer =
318  std::make_shared<Detail::UnoptimizedRangeRenderer<IteratorT, GeneratorT, ObservedT...>>(
319  std::move(unoptimizedRange), std::forward<GeneratorT>(elementRenderer))](
320  auto& parentElement, Renderer const& gen) {
321  if (gen.type == RendererType::Inplace)
322  throw std::runtime_error("fragments are not supported for range generators");
323 
324  auto&& materialized = renderElement(gen, parentElement, self);
325  (*rangeRenderer)(materialized);
326  return materialized;
327  };
328  }
329 
330  // Observed text and number content functions:
331  inline auto operator()(Observed<std::string> const& observedString) &&
332  {
333  return std::move(*this).operator()(observe(observedString), [&observedString]() -> std::string {
334  return observedString.value();
335  });
336  }
337  template <typename T>
338  requires Fundamental<T>
339  auto operator()(Observed<T> const& observedNumber) &&
340  {
341  return std::move(*this).operator()(observe(observedNumber), [&observedNumber]() -> std::string {
342  return std::to_string(observedNumber.value());
343  });
344  }
345 
346  inline std::vector<Attribute> const& attributes() const
347  {
348  return attributes_;
349  }
350 
351  inline char const* name() const
352  {
353  return name_;
354  }
355 
356  inline HtmlElementBridge const* bridge() const
357  {
358  return bridge_;
359  }
360 
361  private:
362  char const* name_;
363  HtmlElementBridge const* bridge_;
364  std::vector<Attribute> attributes_;
365  };
366 }
Definition: fragment_context.hpp:7
Definition: range_renderer.hpp:19
Definition: element.hpp:42
Definition: html_element.hpp:60
requires InvocableReturns< GeneratorT, std::string > auto operator()(GeneratorT &&textGenerator) &&
Definition: html_element.hpp:257
requires InvocableReturns< GeneratorT, std::string > auto operator()(GeneratorT &&elementRenderer) &&
Definition: html_element.hpp:279
std::vector< Attribute > const & attributes() const
Definition: html_element.hpp:346
requires(!InvocableReturns< GeneratorT, std::string >) auto operator()(GeneratorT &&elementRenderer) &&
Definition: html_element.hpp:267
HtmlElement clone() const
Definition: html_element.hpp:84
HtmlElement(char const *name, HtmlElementBridge const *bridge, std::vector< Attribute > const &attributes)
Definition: html_element.hpp:67
auto operator()(std::string_view view) &&
Definition: html_element.hpp:227
auto operator()(std::pair< ObservedRange< ObservedValue >, GeneratorT > &&mapPair) &&
Definition: html_element.hpp:309
requires Fundamental< T > auto operator()(T fundamental) &&
Definition: html_element.hpp:245
auto operator()() &&
Definition: html_element.hpp:205
HtmlElement(HtmlElement &&)=default
requires Fundamental< T > auto operator()(Observed< T > const &observedNumber) &&
Definition: html_element.hpp:339
friend class DomElement
Definition: html_element.hpp:62
requires std::same_as< std::decay_t< T >, std::string > auto operator()(T &&text) &&
Definition: html_element.hpp:219
char const * name() const
Definition: html_element.hpp:351
auto operator()(ObservedValueCombinatorWithGenerator< GeneratorT, ObservedValues... > combinator) &&
Definition: html_element.hpp:291
HtmlElement(char const *name, HtmlElementBridge const *bridge, T &&... attributes)
Definition: html_element.hpp:78
auto operator()(Observed< std::string > const &observedString) &&
Definition: html_element.hpp:331
HtmlElement(char const *name, HtmlElementBridge const *bridge, std::vector< Attribute > &&attributes)
Definition: html_element.hpp:72
HtmlElementBridge const * bridge() const
Definition: html_element.hpp:356
virtual ~HtmlElement()=default
auto operator()(ElementT &&... elements) &&
Definition: html_element.hpp:184
auto operator()(char const *text) &&
Definition: html_element.hpp:235
requires requires(ElementT &&... elements)
Definition: html_element.hpp:180
HtmlElement(HtmlElement const &)=default
auto operator()(ObservedValueCombinator< ObservedValues... > observedValues, GeneratorT &&elementRenderer) &&
Definition: html_element.hpp:296
auto operator()(UnoptimizedRange< IteratorT, ObservedT... > &&unoptimizedRange, GeneratorT &&elementRenderer) &&
Definition: html_element.hpp:314
auto operator()(ObservedRange< ObservedValue > observedRange, GeneratorT &&elementRenderer) &&
Definition: html_element.hpp:304
Definition: range.hpp:18
Definition: observed_value_combinator.hpp:109
Definition: observed_value_combinator.hpp:160
Definition: range.hpp:46
void makeChildrenUpdateEvent(auto &observedValues, auto &childrenRefabricator, auto &createdSelfWeak)
Definition: make_children_update_event.hpp:7
Definition: file_dialog.hpp:6
auto renderElement(Renderer const &gen, auto &element, auto const &htmlElement)
Definition: materialize.hpp:56
Definition: html_element.hpp:36
std::shared_ptr< Dom::Element > operator()(Dom::Element &parentElement, Renderer const &gen) const
ChildrenRenderer(HtmlElem htmlElement, std::vector< std::function< std::shared_ptr< Dom::Element >(Dom::Element &, Renderer const &)>> children)
Definition: html_element_bridge.hpp:14
Definition: materialize.hpp:52
RendererType type
Definition: materialize.hpp:53
Definition: html_element.hpp:50
TrivialRenderer(HtmlElem htmlElement)
std::shared_ptr< Dom::Element > operator()(Dom::Element &parentElement, Renderer const &gen) const