Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bootstrap-webpack/bootstrap/spasm/modules/spa.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
17 changes: 10 additions & 7 deletions source/spasm/dom.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(">");
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);");
}
}
Expand Down Expand Up @@ -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);
Expand Down
116 changes: 113 additions & 3 deletions source/spasm/types.d
Original file line number Diff line number Diff line change
Expand Up @@ -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.<args>.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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This second form is used when you want to connect to slots from elements that are in a List.

Note the idx it receives.

struct Item {
  mixin Node!"li";
  mixin Slot!"click";
  @prop string innerText;
  @callback void onClick(MouseEvent event) {
    this.emit(click);
  }
}

struct App {
  mixin Node!"div";
  @child UnorderedList!Item list;
  @connect!("list.items","click") void itemClick(size_t idx) {
  }
}
mixin Spa!App;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but if you look at the implementation it is really nothing else than

mixin("t."~a~"."~replace!(b,'.','_')~".add(del);");

so basically the same as the single overload, which is why I documented it that way. Should there maybe be some more constraints on this?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an implicit contract between this line and the ArrayItemEvents mixin which generates the functions that handles these.

An assert that "list.items" is of type Array!(T) would help a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't that much rather use a new other UDA than @connect?

Currently this also uses some magic ".items" suffix in the connect string, which is confusing for beginners

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, if you can come up with a good name. I am stuck at @connectList or @connectItem...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @connectItem or @connectItems would make sense, visible to auto completion and very easy to migrate

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Singular item would work.

* 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*);
Expand Down