1 module spasm.dom;
2 
3 import spasm.types;
4 import spasm.dom;
5 import spasm.ct;
6 import std.traits : hasMember, isAggregateType;
7 import std.traits : TemplateArgsOf, staticMap, isPointer, PointerTarget, getUDAs, EnumMembers, isInstanceOf, isBasicType, isIntegral;
8 import std.traits : getSymbolsByUDA, getUDAs;
9 import std.meta : Filter, AliasSeq, ApplyLeft, ApplyRight;
10 import spasm.css;
11 import spasm.node;
12 import spasm.event;
13 import std.meta : staticIndexOf;
14 import spasm.array;
15 import spasm.rt.array;
16 
17 @safe:
18 nothrow:
19 
20 version (unittest) {
21   import spasm.sumtype;
22   import std.array : Appender;
23   import std.algorithm : remove, countUntil;
24   import std.array : insertInPlace;
25   class UnittestDomNode {
26     alias Property = SumType!(string,int,bool,double);
27     alias Attribute = SumType!(string,int);
28     NodeType type;
29     Handle handle;
30     UnittestDomNode parent;
31     Property[string] properties;
32     Attribute[string] attributes;
33     string[] classes;
34     Appender!(UnittestDomNode[]) children;
35     this(NodeType type, Handle handle) nothrow { this.type = type; this.handle = handle; }
36     void setAttribute(T)(string name, T value) nothrow @trusted {
37       attributes[name] = Attribute(value);
38     }
39     Attribute getAttribute(string name) nothrow {
40       return attributes[name];
41     }
42     void setProperty(T)(string name, T value) nothrow @trusted {
43       properties[name] = Property(value);
44     }
45     Property getProperty(string name) nothrow {
46       return properties[name];
47     }
48     void toString(scope void delegate(const(char)[]) @safe sink) @trusted {
49       import std.algorithm : each;
50       import std.format : formattedWrite, format;
51       import std.array : join;
52       import std.conv : to;
53       if (type != NodeType.root) {
54         sink.formattedWrite("<%s", type);
55         // write attributes and properties
56         if (classes.length > 0) {
57           sink.formattedWrite(" class=\"%s\"", classes.join(" "));
58         }
59         foreach (kv; properties.byKeyValue()) {
60           sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(i)=>i.to!string)(kv.value));
61         }
62         foreach (kv; attributes.byKeyValue()) {
63           sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(int i)=>i.to!string)(kv.value));
64         }
65         sink(">");
66       }
67       children.data.each!(c => c.toString(sink));
68       // write children
69       if (type != NodeType.root)
70         sink.formattedWrite("</%s>", type);
71     }
72   }
73   Appender!(UnittestDomNode[]) unittest_dom_nodes;
74   private auto getNode(Handle node) {
75     assert(node > 0);
76     return unittest_dom_nodes.data[node - 1];
77   }
78   private auto assumeNoException(Block)(lazy Block block) {
79     try {
80       block();
81     } catch(Exception e) {
82       assert(0, e.msg);
83     }
84   }
85   extern(C) {
86     Handle createElement(NodeType type) {
87       uint idx = cast(uint)unittest_dom_nodes.data.length;
88       unittest_dom_nodes.put(new UnittestDomNode(type, idx + 1));
89       return idx + 1;
90     }
91     void addClass(Handle node, string className) {
92       node.getNode().classes ~= className;
93     }
94     void setProperty(Handle node, string prop, string value) {
95       node.getNode().setProperty(prop, value);
96     }
97     void removeChild(Handle childPtr) @trusted {
98       import std.algorithm : remove;
99       import std.algorithm : countUntil;
100       auto child = childPtr.getNode();
101       auto childIdx = child.parent.children.data.countUntil!(c => c is child);
102       assert(childIdx >= 0);
103       child.parent.children.data.remove(childIdx);
104       child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
105     }
106     void unmount(Handle childPtr) {
107       removeChild(childPtr);
108     }
109     void appendChild(Handle parentPtr, Handle childPtr) @trusted {
110       auto parent = parentPtr.getNode();
111       auto child = childPtr.getNode();
112       if (child.parent) {
113         auto childIdx = child.parent.children.data.countUntil!(c => c is child);
114         if (childIdx > 0) {
115           child.parent.children.data.remove(childIdx);
116           child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
117         }
118       }
119       child.parent = parent;
120       parent.children.put(child);
121     }
122     void insertBefore(Handle parentPtr, Handle childPtr, Handle siblingPtr) @trusted {
123       auto parent = parentPtr.getNode();
124       auto child = childPtr.getNode();
125       auto sibling = siblingPtr.getNode();
126       if (child.parent) {
127         auto childIdx = child.parent.children.data.countUntil!(c => c is child);
128         if (childIdx > 0) {
129           child.parent.children.data.remove(childIdx);
130           child.parent.children.shrinkTo(child.parent.children.data.length - 1).assumeNoException();
131         }
132       }
133       child.parent = parent;
134       auto siblingIdx = parent.children.data.countUntil!(c => c is sibling);
135       assert(siblingIdx >= 0);
136       parent.children.put(child);
137       auto arr = parent.children.data;
138       foreach(i; 0 .. arr.length - siblingIdx)
139         arr[arr.length - 1 - i] = arr[arr.length - 2 - i];
140       arr[siblingIdx] = child;
141     }
142     void setAttribute(Handle node, string attr, string value) {
143       node.getNode().setAttribute(attr, value);
144     }
145     void setAttributeInt(Handle node, string attr, int value) {
146       node.getNode().setAttribute(attr, value);
147     }
148     void setPropertyBool(Handle node, string prop, bool value) {
149       node.getNode().setProperty(prop, value);
150     }
151     void setPropertyInt(Handle node, string prop, int value) {
152       node.getNode().setProperty(prop, value);
153     }
154     void setPropertyDouble(Handle node, string prop, double value) {
155       node.getNode().setProperty(prop, value);
156     }
157     void innerText(Handle nodePtr, string text) {
158     }
159     void removeClass(Handle node, string className) {
160       import std.algorithm : remove;
161       auto n = node.getNode();
162       n.classes = n.classes.remove!(i => i==className);
163     }
164     void changeClass(Handle node, string className, bool on) {
165       import std.algorithm : any;
166       auto n = node.getNode();
167       if (on && !n.classes.any!(c => c == className))
168         n.classes ~= className;
169       else if (!on)
170         removeClass(node, className);
171     }
172     Optional!string getProperty(Handle node, string name) {
173       auto str = node.getNode().getProperty(name).trustedGet!string;
174       return some(str);
175     }
176     Optional!int getPropertyInt(Handle node, string prop) {
177       return some(node.getNode().getProperty(prop).trustedGet!int);
178     }
179     Optional!bool getPropertyBool(Handle node, string prop) {
180       return some(node.getNode().getProperty(prop).trustedGet!bool);
181     }
182     Optional!double getPropertyDouble(Handle node, string prop) {
183       return some(node.getNode().getProperty(prop).trustedGet!double);
184     }
185     void setSelectionRange(Handle node, uint start, uint end) {
186     }
187     void addCss(string css) {
188     }
189   }
190 } else {
191   private extern(C) {
192     Handle createElement(NodeType type);
193     void addClass(Handle node, string className);
194     void setProperty(Handle node, string prop, string value);
195     void removeChild(Handle childPtr);
196     void unmount(Handle childPtr);
197     void appendChild(Handle parentPtr, Handle childPtr);
198     void insertBefore(Handle parentPtr, Handle childPtr, Handle sibling);
199     void setAttribute(Handle nodePtr, string attr, string value);
200     void setAttributeInt(Handle nodePtr, string attr, int value);
201     void setPropertyBool(Handle nodePtr, string attr, bool value);
202     void setPropertyInt(Handle nodePtr, string attr, int value);
203     void setPropertyDouble(Handle nodePtr, string attr, double value);
204     void innerText(Handle nodePtr, string text);
205     void removeClass(Handle node, string className);
206     void changeClass(Handle node, string className, bool on);
207   }
208 
209   extern(C) {
210     string getProperty(Handle node, string prop);
211     int getPropertyInt(Handle node, string prop);
212     bool getPropertyBool(Handle node, string prop);
213     void getPropertyDouble(Handle nodePtr, string prop);
214     void setSelectionRange(Handle node, uint start, uint end);
215     void addCss(string css);
216   }
217 }
218 
219 import spasm.bindings.dom : Document;
220 import spasm.bindings.html : Window;
221 
222 @trusted ref auto undefined() {
223   static __gshared u = Any(JsHandle(0));
224   return u;
225 }
226 @trusted ref auto document() {
227   static __gshared d = Document(JsHandle(1));
228   return d;
229 }
230 @trusted ref auto window() {
231   static __gshared w = Window(JsHandle(2));
232   return w;
233 }
234 
235 void unmount(T)(auto ref T t) if (is(T : SumType!(Types), Types...)) {
236   t.match!(unmount);
237  }
238 
239 void unmount(T)(auto ref T t) if (hasMember!(T, "node")) {
240   unmount(t.node.node);
241   static if (hasMember!(T, "node"))
242     t.node.mounted = false;
243   t.propagateOnUnmount();
244  }
245 
246 auto removeChild(T)(T* t) if (is(T : SumType!(Types), Types...)) {
247   unmount(*t);
248  }
249 
250 auto removeChild(T)(auto ref T t) if (hasMember!(T,"node")) {
251   removeChild(t.node.node);
252   static if (hasMember!(T, "node"))
253     t.node.mounted = false;
254   t.propagateOnUnmount();
255 }
256 
257 auto focus(T)(auto ref T t) if (hasMember!(T,"node")) {
258   t.node.node.focus();
259  }
260 
261 auto renderBefore(T, Ts...)(Handle parent, auto ref T t, Handle sibling, auto ref Ts ts) {
262   static if (is(T : SumType!Types, Types...)) {
263     t.match!((scope ref i) => renderBefore(parent, i, sibling, ts));
264   } else {
265     if (parent == invalidHandle)
266       return;
267     renderIntoNode(parent, t, ts);
268     static if (hasMember!(T, "node")) {
269       parent.insertBefore(t.node.node, sibling);
270       t.node.mounted = true;
271     }
272     t.propagateOnMount();
273   }
274 }
275 
276 auto render(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) @trusted {
277   static if (is(T : SumType!Types, Types...)) {
278     t.match!((scope ref i) => render(parent, i, ts));
279   } else {
280     if (parent == invalidHandle)
281       return;
282     renderIntoNode(parent, t, ts);
283     static if (hasMember!(T, "node")) {
284       if (!t.getNamedNode().mounted) {
285         parent.appendChild(t.getNamedNode.node);
286         t.getNamedNode().mounted = true;
287       }
288     }
289     t.propagateOnMount();
290   }
291 }
292 
293 import std.traits : isFunction;
294 auto propagateOnMount(T)(auto ref T t) {
295   static foreach (c; getChildren!T)
296     __traits(getMember, t, c).propagateOnMount();
297   static if (hasMember!(T, "onMount") && isFunction!(T.onMount))
298     t.onMount();
299 }
300 
301 auto propagateOnUnmount(T)(auto ref T t)
302 {
303   static foreach (c; getChildren!T)
304     __traits(getMember, t, c).propagateOnMount();
305   static if (hasMember!(T, "onUnmount") && isFunction!(T.onUnmount))
306     t.onUnmount();
307 }
308 
309 auto remount(string field, Parent)(auto ref Parent parent) {
310   import std.traits : hasUDA;
311   import std.meta : AliasSeq;
312   alias fields = AliasSeq!(__traits(allMembers, Parent));
313   alias idx = staticIndexOf!(field,fields);
314   static if (fields.length > idx+1) {
315     static foreach(child; fields[idx+1..$]) {
316       static if (hasUDA!(__traits(getMember, Parent, child), spasm.types.child)) {
317         if (__traits(getMember, parent, child).node.mounted)
318           return renderBefore(parent.node.node, __traits(getMember, parent, field), __traits(getMember, parent, child).node.node, parent);
319       }
320     }
321   }
322   return render(parent.node.node, __traits(getMember, parent, field), parent);
323 }
324 
325 template isValue(alias t) {
326   enum isValue = __traits(compiles, { enum p = t; });
327 }
328 
329 template createParameterTuple(Params...) {
330   auto createParameterTuple(Ts...)(auto ref Ts ts) {
331     import std.traits : TemplateArgsOf, staticMap;
332     import spasm.spa : Param;
333     template extractName(Arg) {
334       enum extractName = Arg.Name;
335     }
336     static auto extractField(alias sym)(auto ref Ts ts) @trusted {
337       enum name = sym.stringof;
338       enum literal = isValue!(sym);
339       static if (isValue!(sym)) {
340         static if (isBasicType!(typeof(sym))) {
341           typeof(sym) val = sym;
342           return val;
343         } else {
344           __gshared static auto val = sym;
345           return &val;
346         }
347       } else {
348         alias ContainingType = AliasSeq!(__traits(parent, sym))[0];
349         template isContainingType(ContainingType, T) {
350           enum isContainingType = is(ContainingType == T);
351         }
352         enum index = indexOfPred!(ApplyLeft!(isContainingType, ContainingType), AliasSeq!Ts);
353         return &__traits(getMember, ts[index], name);
354       }
355     }
356     static auto extractFields(Args...)(auto ref Ts ts) @safe if (Args.length > 0) {
357       static if (Args.length > 1)
358         return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts), extractFields!(Args[1..$])(ts).expand);
359       else
360         return tuple(extractField!(TemplateArgsOf!(Args[0])[1])(ts));
361     }
362     alias ParamsTuple = staticMap!(TemplateArgsOf, Params);
363     alias Names = staticMap!(extractName, ParamsTuple);
364     auto Fields = extractFields!(ParamsTuple)(ts);
365     return tuple!(Names)(Fields.expand);
366   }
367 }
368 
369 @trusted
370 void setParamFromParent(string name, T, Ts...)(ref T t, auto ref Ts ts) {
371   import std.traits : PointerTarget, isPointer;
372   import std.meta : AliasSeq;
373   alias TargetType = typeof(getMember!(T, name));
374   static if (isPointer!(TargetType))
375     alias FieldType = PointerTarget!(TargetType);
376   else
377     alias FieldType = TargetType;
378   template matchesField(Parent) {
379     static if (!hasMember!(Parent,name))
380       enum matchesField = false;
381     else {
382       alias ItemTypeParent = AliasSeq!(__traits(parent, getMember!(Parent, name)))[0];
383       static if (is(T == ItemTypeParent))
384         enum matchesField = false;
385       else {
386         alias ItemType = typeof(getMember!(Parent, name));
387         enum matchesField = (is(ItemType == FieldType) || is (ItemType == FieldType*));
388       }
389     }
390   }
391   enum index = indexOfPred!(matchesField, AliasSeq!Ts);
392   static if (index >= ts.length) {
393     return;
394   } else {
395     alias SourceType = typeof(__traits(getMember, Ts[index], name));
396     static if(isPointer!TargetType) {
397       static if (isPointer!SourceType)
398         __traits(getMember, t, name) = __traits(getMember, ts[index], name);
399       else {
400         static if (is(Ts[index] : Tuple!Fields, Fields...)) {
401           static assert(!isBasicType!SourceType, "Cannot assign literal in param uda to a pointer, use member field or avoid use of pointer.");
402         }
403         __traits(getMember, t, name) = &__traits(getMember, ts[index], name);
404       }
405     } else static if (isPointer!SourceType)
406       __traits(getMember, t, name) = *__traits(getMember, ts[index], name);
407     else
408       __traits(getMember, t, name) = __traits(getMember, ts[index], name);
409   }
410 }
411 
412 void setChildFromParent(string name, T, Ts...)(auto ref T t, auto ref Ts ts) {
413   template containsChild(Parent) {
414     enum containsChild = hasMember!(Parent, name);
415   }
416   enum index = indexOfPred!(containsChild, AliasSeq!Ts);
417   static if (index != -1) {
418     alias ChildMemberType = typeof(__traits(getMember, T, name));
419     alias Parent = Ts[index];
420     alias ParentMemberType = typeof(__traits(getMember, Parent, name));
421     static assert(__traits(hasMember, ParentMemberType, "node"), __traits(identifier, ParentMemberType) ~ " doesn't have a member 'node'.");
422     alias ParentMemberNodeType = typeof(ParentMemberType.node);
423     alias ChildMemberNodeType = typeof(ChildMemberType.node);
424     static assert(is(ParentMemberNodeType : NamedNode!type, string type), __traits(identifier, ParentMemberType) ~ "'s member 'node' needs to be of type NamedNode.");
425     // NOTE: for now we assert that the ChildMemberNodeType is an HTMLElement, it would be nice to
426     // allow the component to restrict the nodetype of child elements, in which case
427     // we need to check if ChildMemberNodeType is a supertype or the same as ParentMemberNodeType
428     // this way we could e.g. enforce a child of a Table component to be <tr>
429     import spasm.bindings.html : HTMLElement;
430     static assert(is(ChildMemberNodeType : HTMLElement));
431     auto ptr = &__traits(getMember, ts[index], name).node;
432     __traits(getMember, t, name) = cast(ChildMemberType)ptr;
433   }
434 }
435 
436 void verifyChildParams(Super, string parentField, Params...)() {
437   import std.traits : hasUDA;
438   alias ParamsTuple = staticMap!(TemplateArgsOf, Params);
439   static foreach(Param; AliasSeq!ParamsTuple) {{
440       enum childName = __traits(identifier, Param.Field);
441       alias ChildMemberType = typeof(Param.Field);
442       static if (!isBasicType!(ChildMemberType) && !isValue!(Param.Field) && hasMember!(Super, childName)) {
443         enum childOffset = __traits(getMember, Super, childName).offsetof;
444         enum parentOffset = __traits(getMember, Super, parentField).offsetof;
445         static assert(!hasUDA!(__traits(getMember, Super, childName), child), "Propagated member '"~ childName~ "' cannot have @child attribute in struct "~ Super.stringof);
446         static assert(childOffset > parentOffset, "Propagated member '"~ childName~ "' needs to be declared after '"~parentField~ "' in struct "~ Super.stringof);
447       }
448     }}
449 }
450 
451 auto setPointers(T, Ts...)(auto ref T t, auto ref Ts ts) {
452   import std.meta : AliasSeq;
453   import std.traits : hasUDA;
454   static foreach(sym; T.tupleof) {{
455       static if (isPointer!(typeof(sym)))
456         alias ChildType = PointerTarget!(typeof(sym));
457       else
458         alias ChildType = typeof(sym);
459       enum i = sym.stringof; // TODO: can this be fieldName = __traits(identifier, sym) instead of i = sym.stringof ??
460       enum isImmutable = is(typeof(sym) : immutable(T), T);
461       enum isPublic = __traits(getProtection, sym) == "public";
462       static if (!isImmutable) {
463         static if (is(ChildType : NamedNode!name, string name)) {
464           static if (i != "node")
465             setChildFromParent!(__traits(identifier, sym))(t, ts);
466         } else {
467           static if (isPublic)
468             setParamFromParent!(i)(t, ts);
469           static if (!is(sym) && isAggregateType!T) {
470             static if (is(typeof(sym) : DynamicArray!(Item), Item)) {
471               // items in appenders need to be set via render functions
472             } else {
473               static if (!isCallable!(typeof(sym))) {//} && !isPointer!(typeof(sym))) {
474                 import spasm.spa;
475                 alias Params = getUDAs!(sym, Parameters);
476                 static if (Params.length > 0) {
477                   verifyChildParams!(T, i, Params);
478                   auto params = createParameterTuple!(Params)(AliasSeq!(t, ts));
479                 }
480                 else
481                   alias params = AliasSeq!();
482                 static if (isAggregateType!(ChildType)) {
483                   static if (isPointer!(typeof(sym)))
484                     setPointers(*__traits(getMember, t, i), AliasSeq!(params, t, ts));
485                   else
486                     setPointers(__traits(getMember, t, i), AliasSeq!(params, t, ts));
487                 }
488               }
489             }
490           }
491         }
492       }
493     }}
494 }
495 
496 auto isChildVisible(string child, Parent)(auto ref Parent parent) {
497   alias visiblePreds = getSymbolsByUDA!(Parent, visible);
498   static foreach(sym; visiblePreds) {{
499       alias vs = getUDAs!(sym, visible);
500       // TODO: static assert sym is callable
501       static foreach(v; vs) {{
502         static if (is(v == visible!name, string name) && child == name) {
503           static if (is(typeof(sym) == bool)) {
504             if (!__traits(getMember, parent, __traits(identifier,sym)))
505               return false;
506           } else {
507             auto result = callMember!(__traits(identifier, sym))(parent);
508             if (result == false)
509               return false;
510           }
511         }
512         }}
513     }}
514   return true;
515 }
516 
517 auto callMember(string fun, T)(auto ref T t) {
518   import spasm.ct : ParameterIdentifierTuple;
519   import std.meta : staticMap, AliasSeq;
520   alias params = ParameterIdentifierTuple!(__traits(getMember, t, fun));
521   static if (params.length == 0) {
522     return __traits(getMember, t, fun)();
523   } else static if (params.length == 1) {
524     return __traits(getMember, t, fun)(__traits(getMember, t, params[0]));
525   } else static if (params.length == 2) {
526     return __traits(getMember, t, fun)(__traits(getMember, t, params[0]),__traits(getMember, t, params[1]));
527   }
528   else {
529     static assert(false, "Not implemented");
530   }
531 }
532 
533 auto renderIntoNode(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) if (isPointer!T) {
534   return renderIntoNode(parent, *t, ts);
535 }
536 
537 ref auto getNamedNode(T)(auto ref T t) if (isPointer!T) {
538   return (*t).getNamedNode();
539 }
540 
541 ref auto getNamedNode(T)(auto ref T t) if (!isPointer!T) {
542   import spasm.node : NamedNode;
543   static if (is(T : NamedNode!name, string name)) {
544     return t;
545   } else static if (hasMember!(T, "node")) {
546     return t.node;
547   } else {
548     alias children = getSymbolsByUDA!(T, child);
549     static assert(children.length == 1);
550     return __traits(getMember, t, __traits(identifier, children[0])).getNamedNode();
551   }
552 }
553 
554 template createNestedChildRenderFuncs(string memberName) {
555   auto createNestedChildRenderFuncs(T)(auto ref T t) {
556     import std.traits : hasUDA;
557     import std.typecons : tuple;
558     template isNestedChild(alias member) {
559       alias MemberType = typeof(member);
560       static if (!__traits(hasMember, MemberType, "node"))
561         enum isNestedChild = false;
562       else
563         enum isNestedChild = is(typeof(MemberType.node) : NamedNode!type, string type);
564     }
565     template extractName(alias param) {
566       alias extractName = param.Name;
567     }
568     template extractField(alias param) {
569       alias extractField = param.Field;
570     }
571     template isFieldNestedChild(alias param) {
572       enum isFieldNestedChild = isNestedChild!(extractField!(param));
573     }
574     auto createRenderFunc(alias param)() @trusted {
575       enum nestedChildName = __traits(identifier, extractField!(param));
576       return cast(NestedChildRenderFunc)(Handle parent) @safe {
577         auto nestedChildRenderFuncs = .createNestedChildRenderFuncs!(nestedChildName)(t);
578         render(parent, __traits(getMember, t, nestedChildName), nestedChildRenderFuncs);
579       };
580     }
581     alias ParamsTuple = getAnnotatedParameters!(__traits(getMember, T, memberName));
582     alias ParamFieldsTuple = staticMap!(extractField, ParamsTuple);
583     alias nestedChildParams = Filter!(isFieldNestedChild, ParamsTuple);
584     alias Names = staticMap!(extractName, nestedChildParams);
585     static if (Names.length == 0)
586       return tuple();
587     else
588       return tuple!(Names)(staticMap!(createRenderFunc, nestedChildParams));
589   }
590 }
591 
592 alias NestedChildRenderFunc = void delegate(uint) nothrow @safe;
593 
594 template renderNestedChild(string field) {
595   template hasRenderFunc(alias T) {
596     static if (!hasMember!(T, field))
597       enum hasRenderFunc = false;
598     else {
599       alias fieldType = typeof(__traits(getMember, T, field));
600       enum hasRenderFunc = is(fieldType : NestedChildRenderFunc);
601     }
602   }
603   auto renderNestedChild(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) {
604     enum tupleIndex = indexOfPred!(hasRenderFunc, Ts);
605     static if (tupleIndex != -1) {
606       __traits(getMember, ts[tupleIndex], field)(parent);
607     }
608   }
609 }
610 // NOTE: only trusted because of the one __gshared thing
611 @trusted auto renderIntoNode(T, Ts...)(Handle parent, auto ref T t, auto ref Ts ts) if (!isPointer!T) {
612   import std.traits : hasUDA, getUDAs;
613   import std.meta : AliasSeq;
614   import std.meta : staticMap;
615   import std.traits : isCallable, getSymbolsByUDA, isPointer;
616   import std.conv : text;
617   enum hasNode = hasMember!(T, "node");
618   static if (hasNode) {
619     bool shouldRender = t.getNamedNode().node == invalidHandle;
620   } else
621     bool shouldRender = true;
622   if (shouldRender) {
623     auto node = createNode(parent, t);
624     static if (hasMember!(T, "node")) {
625       t.getNamedNode().node.handle.handle = node;
626     }
627     alias StyleSet = getStyleSet!T;
628     static foreach(sym; T.tupleof) {{
629         enum i = sym.stringof;
630         alias name = getSymbolCustomName!(sym, domName!i);
631         static if (!is(sym)) {
632           alias styles = getStyles!(sym);
633           static if (hasUDA!(sym, child)) {
634             import spasm.spa;
635             alias params = AliasSeq!();
636             if (isChildVisible!(i)(t)) {
637               static if (is(typeof(sym) : DynamicArray!(Item*), Item)) {
638                 foreach(ref item; __traits(getMember, t, i)) {
639                   node.render(*item, AliasSeq!(params));
640                   static if (is(typeof(t) == Array!Item))
641                     t.assignEventListeners(*item);
642                 }
643               } else static if (is(typeof(sym) : NamedNode!(name)*, string name)) {
644                 renderNestedChild!(i)(node, t, ts);
645               } else {
646                 auto nestedChildRenderFuncs = createNestedChildRenderFuncs!(i)(t);
647                 node.render(__traits(getMember, t, i), AliasSeq!(params, nestedChildRenderFuncs));
648               }
649             }
650           } else static if (hasUDA!(sym, prop)) {
651             node.setPropertyTyped!name(__traits(getMember, t, i));
652           } else static if (hasUDA!(sym, attr)) {
653             node.setAttributeTyped!name(__traits(getMember, t, i));
654           }
655 
656           alias extendedStyles = getStyleSets!(sym);
657           static foreach(style; extendedStyles) {
658             static assert(hasMember!(typeof(sym), "node"), "styleset on field is currently only possible when said field has a Node mixin");
659             __traits(getMember, t, i).node.setAttribute(GenerateExtendedStyleSetName!style,"");
660           }
661           static if (i == "node") {
662             node.applyStyles!(T, styles);
663           } else static if (styles.length > 0) {
664             static if (isCallable!(sym)) {
665               auto result = callMember!(i)(t);
666               if (result == true) {
667                t.getNamedNode.applyStyles!(T, styles);
668               }
669             } else static if (is(typeof(sym) == bool)) {
670               if (__traits(getMember, t, i) == true)
671                 t.getNamedNode.applyStyles!(T, styles);
672             } else static if (hasUDA!(sym, child)) {
673               getNamedNode(__traits(getMember, t, i)).applyStyles!(T, styles);
674             }
675           }
676         }
677       }}
678     static foreach(i; __traits(allMembers, T)) {{
679         static if (!is(__traits(getMember, T, i)) && isCallable!(__traits(getMember, T, i))) {
680           alias sym = AliasSeq!(__traits(getMember, T, i))[0];
681 
682           alias name = getSymbolCustomName!(sym, domName!i);
683           static if (hasUDA!(sym, child))
684             static assert(false, "we don't support @child functions");
685           else static if (hasUDA!(sym, prop)) {
686             auto result = callMember!(i)(t);
687             t.getNamedNode.setPropertyTyped!name(result);
688           } else static if (hasUDA!(sym, callback)) {
689             t.getNamedNode.addEventListenerTyped!i(t);
690           } else static if (hasUDA!(sym, attr)) {
691             auto result = callMember!(i)(t);
692             t.getNamedNode.setAttributeTyped!name(result);
693           } else static if (hasUDA!(sym, style)) {
694               auto result = callMember!(i)(t);
695               static foreach (style; getStyles!(sym)) {
696                 __gshared static string className = GetCssClassName!(T, style);
697                 t.getNamedNode.changeClass(className,result);
698               }
699           } else static if (hasUDA!(sym, connect)) {
700             alias connects = getUDAs!(sym, connect);
701             static foreach(c; connects) {
702               auto del = &__traits(getMember, t, i);
703               static if (is(c: connect!(a,b), alias a, alias b)) {
704                 mixin("t."~a~"."~replace!(b,'.','_')~".add(del);");
705               } else static if (is(c : connect!field, alias field)) {
706                 static assert(__traits(compiles, mixin("t."~field)), "Cannot find property "~field~" on "~T.stringof~" in @connect");
707                 mixin("t."~field~".add(del);");
708               } else static if (is(c : connect!field, string field)) {
709                 mixin("t."~field~".add(del);");
710               }
711             }
712           }
713         }
714       }}
715 
716     alias enumsWithApplyStyles = getSymbolsByUDA!(T, ApplyStyle);
717     static foreach(member; enumsWithApplyStyles) {{
718         alias ApplyStyles = getUDAs!(member, ApplyStyle);
719         static foreach(s; ApplyStyles) {
720           static if (is(s == ApplyStyle!Target, alias Target)) {
721             static if (isFunction!member)
722               alias EnumType = ReturnType!member;
723             else
724               alias EnumType = typeof(member);
725             alias GetUDAs = ApplyRight!(ApplyLeft!(getEnumUDAs, EnumType), style);
726             alias table = staticMap!(GetUDAs, __traits(allMembers, EnumType));
727             alias GetCssClass = ApplyLeft!(GetCssClassName, T);
728             alias classes = staticMap!(GetCssClass, staticMap!(extractStyleStruct, table));
729             enum string[classes.length] styleTable = [classes];
730             size_t idx = __traits(getMember, t, __traits(identifier, member));
731             static if (__traits(identifier, Target) == "node") {
732               node.addClass(styleTable[idx]);
733             } else static if (hasUDA!(Target, child)) {
734               __traits(getMember, t, __traits(identifier, Target)).getNamedNode.addClass(styleTable[idx]);
735             } else
736               static assert(false, "Can only have ApplyStyle point to node or to a child component");
737           }
738         }
739       }}
740   }
741  }
742 
743 template getSymbolCustomName(alias symbol, string defaultName) {
744   alias names = getStringUDAs!(symbol);
745   static if (names.length > 0)
746     enum getSymbolCustomName = names[0];
747   else
748     enum getSymbolCustomName = defaultName;
749 }
750 template getEnumUDAs(EnumType, string field, alias UDA) {
751   alias udas = AliasSeq!(__traits(getAttributes, __traits(getMember, EnumType, field)));
752   alias getEnumUDAs = Filter!(isDesiredUDA!UDA, udas);
753 }
754 
755 private template isDesiredUDA(alias attribute) {
756   template isDesiredUDA(alias toCheck) {
757     static if (is(typeof(attribute)) && !__traits(isTemplate, attribute)) {
758       static if (__traits(compiles, toCheck == attribute))
759         enum isDesiredUDA = toCheck == attribute;
760       else
761         enum isDesiredUDA = false;
762     } else static if (is(typeof(toCheck))) {
763       static if (__traits(isTemplate, attribute))
764         enum isDesiredUDA =  isInstanceOf!(attribute, typeof(toCheck));
765       else
766         enum isDesiredUDA = is(typeof(toCheck) == attribute);
767     } else static if (__traits(isTemplate, attribute))
768       enum isDesiredUDA = isInstanceOf!(attribute, toCheck);
769     else
770       enum isDesiredUDA = is(toCheck == attribute);
771   }
772 }
773 
774 template among(alias field, T...) {
775   static if (T.length == 0)
776     enum among = false;
777   else static if (T.length == 1)
778     enum among = field.stringof == T[0];
779   else
780     enum among = among!(field,T[0..$/2]) || among!(field,T[$/2..$]);
781 }
782 
783 template getAnnotatedParameters(alias symbol) {
784   import spasm.spa;
785   alias Params = getUDAs!(symbol, Parameters);
786   alias getAnnotatedParameters = staticMap!(TemplateArgsOf, Params);
787 }
788 
789 template updateChildren(alias member) {
790   enum field = __traits(identifier, member);
791   alias Source = AliasSeq!(__traits(parent, member))[0];
792   template isParamField(Param) {
793     enum isParamField = TemplateArgsOf!(Param)[1].stringof == field;
794   }
795   static void updateChildren(Parent)(auto ref Parent parent) {
796     // we are updating field in parent
797     // all children that have a pointer with the exact same name
798     // should get an update
799     // all children that have a params annotation that refers to the field
800     // should get an update
801     import std.traits : getSymbolsByUDA;
802     import std.meta : ApplyLeft, staticMap;
803     static if (isPointer!(Parent))
804       alias ParentType = PointerTarget!(Parent);
805     else
806       alias ParentType = Parent;
807     static foreach(t; ParentType.tupleof) {
808       static foreach(param; getAnnotatedParameters!(t)) {{
809           static if (!isValue!(TemplateArgsOf!(param)[1])) {
810             alias target = TemplateArgsOf!(param)[1];
811             static if (__traits(isSame, target, member)) {
812               __traits(getMember, parent, __traits(identifier, t)).update!(__traits(getMember, __traits(getMember, parent, __traits(identifier, t)), param.Name));
813             }
814           }
815         }}
816     }
817     alias getSymbol = ApplyLeft!(getMember, parent);
818     alias childrenNames = getChildren!Parent;
819     alias children = staticMap!(getSymbol,childrenNames);
820     static foreach(c; children) {{
821         alias ChildType = typeof(c);
822         static if (hasMember!(ChildType, field)) {
823           __traits(getMember, parent, c.stringof).update!(__traits(getMember, __traits(getMember, parent, c.stringof), field));
824         } else
825           .updateChildren!(member)(__traits(getMember, parent, c.stringof));
826       }}
827   }
828 }
829 
830 auto update(T)(ref T node) if (hasMember!(T, "node")){
831   struct Inner {
832     nothrow:
833     void opDispatch(string name, V)(auto ref V v) const @safe {
834       mixin("node.update!(node." ~ name ~ ")(v);");
835       // NOTE: static assert won't work in opDispatch, as the compiler will not output the string but just ignore the opDispatch call and error out saying missing field on Inner
836       static if(!hasMember!(T, name))
837         pragma(msg, "********* Error: " ~ T.stringof ~ " has no property named "~name);
838     }
839   }
840   return Inner();
841 }
842 
843 void update(Range, Sink)(auto ref Range source, ref Sink sink) {
844   import std.range : ElementType;
845   import std.algorithm : copy;
846   alias E = ElementType!Range;
847   auto output = Updater!(Sink)(&sink);
848   foreach(i; source)
849     output.put(i);
850 }
851 
852 void setVisible(string field, Parent)(auto ref Parent parent, bool visible) {
853   bool current = __traits(getMember, parent, field).node.mounted;
854   if (current != visible) {
855     if (visible) {
856       remount!(field)(parent);
857     } else {
858       unmount(__traits(getMember, parent, field));
859     }
860   }
861 }
862 
863 @trusted
864 template update(alias field) {
865   import std.traits : isPointer;
866   static void updateDom(Parent, T)(auto ref Parent parent, auto ref T t) {
867     import spasm.ct : ParameterIdentifierTuple;
868     import std.traits : hasUDA, isCallable, getUDAs;
869     import std.meta : AliasSeq;
870     import std.meta : staticMap;
871     alias name = domName!(field.stringof);
872     static if (hasUDA!(field, prop)) {
873       parent.node.setPropertyTyped!name(t);
874     } else static if (hasUDA!(field, attr)) {
875       parent.node.setAttributeTyped!name(t);
876     }
877     static if (is(T == bool)) {
878       alias styles = getStyles!(field);
879       static foreach(style; styles) {
880         __gshared static string className = GetCssClassName!(Parent, style);
881         parent.getNamedNode.changeClass(className,t);
882       }
883       static if (hasUDA!(field, visible)) {
884         alias udas = getUDAs!(field, visible);
885         static foreach (uda; udas) {
886           static if (is(uda : visible!elem, alias elem)) {
887             setVisible!(elem)(parent, __traits(getMember, parent, __traits(identifier, field)));
888           }
889         }
890       }
891     }
892     static foreach(i; __traits(allMembers, Parent)) {{
893         alias sym = AliasSeq!(__traits(getMember, parent, i))[0];
894         static if (isCallable!(sym)) {
895           alias params = ParameterIdentifierTuple!sym;
896           static if (among!(field, params)) {
897             static if (hasUDA!(sym, prop)) {
898               alias cleanName = domName!i;
899               auto result = callMember!(i)(parent);
900               parent.node.node.setPropertyTyped!cleanName(result);
901             }
902             else static if (hasUDA!(sym, attr)) {
903               alias cleanName = domName!i;
904               auto result = callMember!(i)(parent);
905               parent.node.node.setAttributeTyped!cleanName(result);
906             }
907             else static if (hasUDA!(sym, style)) {
908               auto result = callMember!(i)(parent);
909               static foreach (style; getStyles!(sym))
910               {
911                 __gshared static string className = GetCssClassName!(Parent, style);
912                 parent.node.node.changeClass(className,result);
913               }
914             } else {
915               import std.traits : ReturnType;
916               alias RType = ReturnType!(__traits(getMember, parent, i));
917               static if (is(RType : void))
918                 callMember!(i)(parent);
919               else {
920                 auto result = callMember!(i)(parent);
921                 static if (hasUDA!(sym, visible)) {
922                   alias udas = getUDAs!(sym, visible);
923                   static foreach(uda; udas) {
924                     static if (is(uda : visible!elem, alias elem)) {
925                       setVisible!(elem)(parent, result);
926                     }
927                   }
928                 }
929               }
930             }
931           }
932         }
933       }}
934     updateChildren!(field)(parent);
935   }
936   static void update(Parent)(scope auto ref Parent parent) {
937     if (!parent.node.mounted)
938       return;
939     static if (isPointer!Parent)
940       updateDom(*parent, __traits(getMember, parent, field.stringof));
941     else
942       updateDom(parent, __traits(getMember, parent, field.stringof));
943   }
944   static void update(Parent, T)(scope auto ref Parent parent, T t) {
945     mixin("parent."~field.stringof~" = t;");
946     if (!parent.node.mounted)
947       return;
948     static if (isPointer!Parent)
949       updateDom(*parent, t);
950     else
951       updateDom(parent, t);
952   }
953 }
954 
955 template symbolFromAliasThis(Parent, string name) {
956   import std.meta : anySatisfy;
957   alias aliasThises = AliasSeq!(__traits(getAliasThis, Parent));
958   static if (aliasThises.length == 0)
959     enum symbolFromAliasThis = false;
960   else {
961     alias hasSymbol = ApplyRight!(hasMember, name);
962     enum symbolFromAliasThis = anySatisfy!(hasSymbol, aliasThises);
963   }
964 }
965 
966 auto setAttributeTyped(string name, T)(Handle node, auto ref T t) {
967   import std.traits : isPointer;
968   static if (isPointer!T) {
969     if (t !is null)
970       node.setAttributeTyped!name(*t);
971   } else static if (is(T == bool))
972     node.setAttributeBool(name, t);
973   else static if (is(T : int)) {
974     node.setAttributeInt(name, t);
975   } else {
976     node.setAttribute(name, t);
977   }
978 }
979 
980 auto setPropertyTyped(string name, T)(Handle node, auto ref T t) {
981   import std.traits : isPointer, isNumeric;
982   static if (isPointer!T) {
983     if (t !is null)
984       node.setPropertyTyped!name(*t);
985   }
986   else static if (is(T == bool))
987     node.setPropertyBool(name, t);
988   else static if (isIntegral!(T))
989     node.setPropertyInt(name, t);
990   else static if (isNumeric!(T))
991     node.setPropertyDouble(name, t);
992   else
993   {
994     static if (__traits(compiles, __traits(getMember, api, name)))
995       __traits(getMember, api, name)(node, t);
996     else
997       node.setProperty(name, t);
998   }
999 }
1000 
1001 auto applyStyles(T, styles...)(Handle node) {
1002   static foreach(style; styles) {
1003     node.addClass(GetCssClassName!(T, style));
1004   }
1005 }
1006 
1007 Handle createNode(T)(Handle parent, ref T t) {
1008   enum hasNode = hasMember!(T, "node");
1009   static if (hasNode && is(typeof(t.node) : NamedNode!tag, alias tag)) {
1010     mixin("NodeType n = NodeType." ~ tag ~ ";");
1011     return createElement(n);
1012   } else
1013     return parent;
1014 }
1015 
1016 template indexOfPred(alias Pred, TList...) {
1017   enum indexOfPred = indexOf!(Pred, TList).index;
1018 }
1019 
1020 template indexOf(alias Pred, args...) {
1021   import std.meta : AliasSeq;
1022   static if (args.length > 0) {
1023     static if (Pred!(args[0])) {
1024       enum index = 0;
1025     } else {
1026       enum next = indexOf!(Pred, AliasSeq!(args[1..$])).index;
1027       enum index = (next == -1) ? -1 : 1 + next;
1028     }
1029   } else {
1030     enum index = -1;
1031   }
1032 }
1033 
1034 template domName(string name) {
1035   import std.algorithm : stripRight;
1036   import std.conv : text;
1037   static if (name[$-1] == '_')
1038     enum domName = name[0..$-1];
1039   else
1040     enum domName = name;
1041 }
1042 
1043 template join(Seq...) {
1044   static if (is(typeof(Seq) == string))
1045     enum join = Seq;
1046   else {
1047     static if (Seq.length == 1) {
1048       enum join = Seq[0];
1049     }
1050     else {
1051       enum join = Seq[0] ~ "," ~ join!(Seq[1 .. $]);
1052     }
1053   }
1054 }