diff --git a/bootstrap-webpack/bootstrap/spasm/modules/spa.js b/bootstrap-webpack/bootstrap/spasm/modules/spa.js index 9a5ad37..9de706f 100644 --- a/bootstrap-webpack/bootstrap/spasm/modules/spa.js +++ b/bootstrap-webpack/bootstrap/spasm/modules/spa.js @@ -66,6 +66,7 @@ export let jsExports = { innerText: (nodePtr,textLen, textOffset) => { nodes[nodePtr].innerText = decoder.string(textLen,textOffset); }, + setAttributeBool: setAttribute, setAttributeInt: setAttribute, setAttribute: (nodePtr, attrLen, attrOffset, valueLen, valueOffset) => setAttribute(nodePtr, attrLen, attrOffset, decoder.string(valueLen,valueOffset)), addEventListener: (nodePtr, listenerType, ctx, fun, eventType) => { diff --git a/source/spasm/dom.d b/source/spasm/dom.d index 5df8f77..2cba0f3 100644 --- a/source/spasm/dom.d +++ b/source/spasm/dom.d @@ -24,7 +24,7 @@ version (unittest) { import std.array : insertInPlace; class UnittestDomNode { alias Property = SumType!(string,int,bool,double); - alias Attribute = SumType!(string,int); + alias Attribute = SumType!(string,int,bool); NodeType type; Handle handle; UnittestDomNode parent; @@ -60,7 +60,7 @@ version (unittest) { sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(i)=>i.to!string)(kv.value)); } foreach (kv; attributes.byKeyValue()) { - sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(int i)=>i.to!string)(kv.value)); + sink.formattedWrite(" %s=%s", kv.key, spasm.sumtype.match!((string s)=>format(`"%s"`,s),(i)=>i.to!string)(kv.value)); } sink(">"); } @@ -145,6 +145,9 @@ version (unittest) { void setAttributeInt(Handle node, string attr, int value) { node.getNode().setAttribute(attr, value); } + void setAttributeBool(Handle node, string attr, bool value) { + node.getNode().setAttribute(attr, value); + } void setPropertyBool(Handle node, string prop, bool value) { node.getNode().setProperty(prop, value); } @@ -198,6 +201,7 @@ version (unittest) { void insertBefore(Handle parentPtr, Handle childPtr, Handle sibling); void setAttribute(Handle nodePtr, string attr, string value); void setAttributeInt(Handle nodePtr, string attr, int value); + void setAttributeBool(Handle nodePtr, string attr, bool value); void setPropertyBool(Handle nodePtr, string attr, bool value); void setPropertyInt(Handle nodePtr, string attr, int value); void setPropertyDouble(Handle nodePtr, string attr, double value); @@ -703,9 +707,8 @@ template renderNestedChild(string field) { static if (is(c: connect!(a,b), alias a, alias b)) { mixin("t."~a~"."~replace!(b,'.','_')~".add(del);"); } else static if (is(c : connect!field, alias field)) { - static assert(__traits(compiles, mixin("t."~field)), "Cannot find property "~field~" on "~T.stringof~" in @connect"); - mixin("t."~field~".add(del);"); - } else static if (is(c : connect!field, string field)) { + static assert(__traits(compiles, mixin("t."~field)), + "Cannot find property "~field~" on "~T.stringof~" in @connect"); mixin("t."~field~".add(del);"); } } @@ -968,9 +971,9 @@ auto setAttributeTyped(string name, T)(Handle node, auto ref T t) { static if (isPointer!T) { if (t !is null) node.setAttributeTyped!name(*t); - } else static if (is(T == bool)) + } else static if (is(T == bool)) { node.setAttributeBool(name, t); - else static if (is(T : int)) { + } else static if (is(T : int)) { node.setAttributeInt(name, t); } else { node.setAttribute(name, t); diff --git a/source/spasm/types.d b/source/spasm/types.d index 4fd6d97..30352a2 100644 --- a/source/spasm/types.d +++ b/source/spasm/types.d @@ -192,13 +192,123 @@ enum NodeType { root = 1024 // Special element used in unittests } +/** + * User defined attribute to register the symbol as event handler to the event + * listener of the same name as the symbol. + * + * Examples: + * --- + * @callback void onClick(MouseEvent event) { + * this.update.textContent = "ouch!"; + * } + * --- + */ +enum callback; + // deprecated("Use spasm.types.Child instead") enum child; +/** + * User defined attribute to attach to children that should be embedded into the + * DOM as child nodes. + * + * Using `@child` on a `DynamicArray!(Item*)` makes all items embed as children. + * + * Otherwise the given symbol will be embedded as child. + */ enum child; + +/** + * User defined attribute to attach to any member field or member function to + * set the value. On rendering or updating this causes the property on this node + * with the same name as the symbol on which this UDA is attached on to change + * value to what the symbol evaluates to. + * + * Property means this property name on the native JavaScript object will be + * changed. + * + * Incompatible with @child (will not match in that case) + * + * Examples: + * --- + * struct SaveButton { + * mixin Node!"button"; + * mixin Slot!"click"; + * + * @prop string innerText = "Save"; + * + * @callback void onClick(MouseEvent event) { + * this.emit(click); + * } + * } + * --- + */ enum prop; -enum callback; + +/** + * User defined attribute to attach to any member field or member function to + * set the value. On rendering or updating this causes the attribute for this + * node with the same name as the symbol on which this UDA is attached on to + * change value to what the symbol evaluates to. + * + * Attribute means a DOM attribute on a node which is set using setAttribute. + * + * Incompatible with @prop and @child (will not match in those cases) + * + * Examples: + * --- + * struct Checkbox { + * mixin Node!"input"; + * @attr string type = "checkbox"; + * } + * --- + */ enum attr; -struct connect(field...) {}; -struct visible(alias condition) {}; + +/** + * User defined attribute to attach to member functions to call + * `this..add(&func)` when rendering this into a node. + * + * Supports 2 kinds of overloads: + * `@connect!"someMember.someEvent"` with an arbitrary string to add this + * function into. Most commonly some kind of `"button.click"` string. + * `@connect!("someMember", "someEvent")` which is the same as using a single + * string but writes a dot after the first argument and replaces all dots in + * the second argument with underscores. + * + * Examples: + * --- + * struct App { + * @child Button addButton; + * @connect!"addButton.click" void addClick() { + * Item* item = allocator.make!Item; + * item.innerText = "Item"; + * this.items.put(item); + * this.update!(items); + * } + * } + * --- + */ +struct connect(field...) {} + +/** + * User defined attribute to attach to a member boolean field or a member + * function evaluating to a boolean value to make the given argument field + * appear or disappear. + * + * `visible` actually removes and readds the nodes when mounting or updating. + * + * Params: + * target = the name of the member that is supposed to be hidden/shown based + * on the value. `true` means it is shown, `false` means it's hidden. + * + * Examples: + * --- + * struct App { + * @child Dialog loginDialog; + * @visible!"loginDialog" bool showLoginDialog; + * } + * --- + */ +struct visible(alias target) {} template isTOrPointer(T, Target) { enum isTOrPointer = is(T : Target) || is(T : Target*);