Skip to main content

3. Dynamic decorations

In this part we will try to synchronize the state of our symbols or objects with corresponding decorations we created in the previous part.

tip

If you are looking for a simpler way, please, see Deferred mode ❤️

Dynamic summary item

We have already explored the possibility of animated icons in Animated decoration in Summary Item, therefore there is no obstacles in doing the same in sync with state changes of our object.

StateMachine /: MakeBoxes[s: StateMachine[symbol_Symbol?AssociationQ], form: (StandardForm | TraditionalForm)] := Module[{
state = s["State"] // ToString,
instances = 0,
eventObject, construct, destruct
}, With[{
textField = EditorView[state // Offload],
controller = CreateUUID[],
notebook = EvaluationNotebook[]
},

(* if notebook was closed *)
With[{clonedEv = notebook // EventClone},
EventHandler[clonedEv, {"OnClose" -> Function[Null,
Print["All removed"];
EventRemove[clonedEv];
destruct;
]}];
];

construct := With[{},
(* subscribe to object events and update decoration *)
eventObject = EventClone[s];
EventHandler[eventObject, {
"State" -> Function[new, state = new // ToString]
}];
];

destruct := With[{},
Echo["Removed"];
EventRemove[eventObject];
instances = 0;
];

EventHandler[controller, {
"Mounted" -> Function[Null,

If[instances === 0, construct];
instances = instances + 1;

],

"Destroy" -> Function[Null,
instances = instances - 1;

(* unsubscribe when there is no instances *)
If[instances === 0, destruct];
]
}];

With[{
summary = {BoxForm`SummaryItem[{"State: ", textField}]}
},
BoxForm`ArrangeSummaryBox[
StateMachine,
s,
None,
summary,
Null,

"Event" -> controller
]
]
] ]

The idea is the same, but instead of static value, we substituted to BoxForm`SummaryItem a dynamic element EditorView which is updated by the a handler function subscribed to updates of our instance.

Let us test it

instance = StateMachine[]

you can copy and paste instances with no issues, since it is tracked by a variable in the box decoration code

and change the state

StateMachineChange[instance, RandomInteger[{1,10}]];

Controllers

We can also mutate our object from the decoration by substituting InputRange or something like this to a widget. Right..?

StateMachine /: MakeBoxes[s: StateMachine[symbol_Symbol?AssociationQ], form: (StandardForm | TraditionalForm)] := Module[{
state = s["State"] // ToString,
instances = 0,
eventObject, construct, destruct, slider
}, With[{
textField = EditorView[state // Offload],
controller = CreateUUID[],
notebook = EvaluationNotebook[]
},

(* if notebook was closed *)
With[{clonedEv = notebook // EventClone},
EventHandler[clonedEv, {"OnClose" -> Function[Null,
Print["All removed"];
EventRemove[clonedEv];
destruct;
]}];
];

slider = InputRange[0, 10, 1, s["State"]];
EventHandler[slider, Function[n,
StateMachineChange[s, n]
]];

construct := With[{},
(* subscribe to object events and update decoration *)
eventObject = EventClone[s];
EventHandler[eventObject, {
"State" -> Function[new, state = new // ToString]
}];

];

destruct := With[{},
Echo["Removed"];
EventRemove[eventObject];
instances = 0;
];

EventHandler[controller, {
"Mounted" -> Function[Null,

If[instances === 0, construct];
instances = instances + 1;

],

"Destroy" -> Function[Null,
instances = instances - 1;

(* unsubscribe when there is no instances *)
If[instances === 0, destruct];
]
}];

With[{
summary = {
BoxForm`SummaryItem[{"State: ", textField}],
BoxForm`SummaryItem[{"", slider}]
}
},
BoxForm`ArrangeSummaryBox[
StateMachine,
s,
None,
summary,
Null,

"Event" -> controller
]
]
] ]

We added only a few line for slider. The rest is the same

warning

InputRange does not support multiple instances and might have a conflict with DOM ids if copied and pasted from the same generated output.

To solve this issue, we your own slider, which is generated purely from Javascript on each run. See how in Communication

Mutability

Each decoration box based on ViewBox does support mutations of inner and outer content - see From Wolfram Kernel.

The easies example would be to remove all instances from all code editors, once our object does not exists anymore. We will start from writing the corresponding method

StateMachine /: Delete[s_StateMachine] := With[{},
EventFire[s, "Destroy", Null];
DeleteObject[s]
]

Then we need to track all spawned instances of a widget in order to kill all of them. ViewBox provides pattern for events handling "Mounted" with an ID of a widget. Let us harvest it

	EventHandler[controller, {
"Mounted" -> Function[uid,
(* collect instances *)
s["Instances"] = If[ListQ[s["Instances"]], Append[s["Instances"], uid], {uid}];

If[instances === 0, construct];
instances = instances + 1;

],

"Destroy" -> Function[uid,
s["Instances"] = s["Instances"] /. {uid -> Nothing};

instances = instances - 1;


If[instances === 0, destruct];
]
}];

The collected IDs are valid to use together with MetaMarker and FrontSubmit. To destroy them one by one we need to submit a command

FrontSubmit[ViewBox`OuterExpression[""], MetaMarker[#]] &/@ s["Instances"]

Here is the full code

StateMachine /: MakeBoxes[s: StateMachine[symbol_Symbol?AssociationQ], form: (StandardForm | TraditionalForm)] := Module[{
state = s["State"] // ToString,
instances = 0,
eventObject, construct, destruct
}, With[{
textField = EditorView[state // Offload],
controller = CreateUUID[],
window = CurrentWindow[],
notebook = EvaluationNotebook[]
},

(* if notebook was closed *)
With[{clonedEv = notebook // EventClone},
EventHandler[clonedEv, {"OnClose" -> Function[Null,
Print["All removed"];
EventRemove[clonedEv];
destruct;
s["Instances"] = {};
]}];
];

construct := With[{},
(* subscribe to object events and update decoration *)
eventObject = EventClone[s];
EventHandler[eventObject, {
"State" -> Function[new, state = new // ToString],
"Destroy" -> Function[Null,
FrontSubmit[ViewBox`OuterExpression[""], MetaMarker[#], "Window"->window] &/@ s["Instances"];
]
}];
];

destruct := With[{},
Echo["Removed"];
EventRemove[eventObject];
instances = 0;
];

EventHandler[controller, {
"Mounted" -> Function[uid,
s["Instances"] = If[ListQ[s["Instances"]], Append[s["Instances"], uid], {uid}];

If[instances === 0, construct];
instances = instances + 1;

],

"Destroy" -> Function[uid,
s["Instances"] = s["Instances"] /. {uid -> Nothing};

instances = instances - 1;

(* unsubscribe when there is no instances *)
If[instances === 0, destruct];
]
}];

With[{
summary = {BoxForm`SummaryItem[{"State: ", textField}]}
},
BoxForm`ArrangeSummaryBox[
StateMachine,
s,
None,
summary,
Null,

"Event" -> controller
]
]
] ]

CSS effects

One can apply some visuals as well

.wlx

<style>
.desintegrate-animation {
animation-duration: 2.6s;
animation-name: bounceOutRight;
}
@keyframes bounceOutRight {
50% {
opacity: 1; transform: translate3d(0, 0, 0);
}
60% {
opacity: 1;
transform: translate3d(-20px, 0, 0);
}

to {
opacity: 0;
transform: translate3d(200px, 0, 0);
}
}
</style>
.js

core.Desintagrate = async (args, env) => {
env.element.parentNode.classList.add('desintegrate-animation');
}

And add an animation call to our boxes

StateMachine /: MakeBoxes[s: StateMachine[symbol_Symbol?AssociationQ], form: (StandardForm | TraditionalForm)] := Module[{
state = s["State"] // ToString,
instances = 0,
eventObject, construct, destruct
}, With[{
textField = EditorView[state // Offload],
controller = CreateUUID[],
window = CurrentWindow[],
notebook = EvaluationNotebook[]
},

(* if notebook was closed *)
With[{clonedEv = notebook // EventClone},
EventHandler[clonedEv, {"OnClose" -> Function[Null,
Print["All removed"];
EventRemove[clonedEv];
destruct;
s["Instances"] = {};
]}];
];

construct := With[{},
(* subscribe to object events and update decoration *)
eventObject = EventClone[s];
EventHandler[eventObject, {
"State" -> Function[new, state = new // ToString],
"Destroy" -> Function[Null,
FrontSubmit[{Desintagrate[], Pause[2.6], ViewBox`OuterExpression[""]} // Offload, MetaMarker[#], "Window"->window] &/@ s["Instances"];
]
}];
];

destruct := With[{},
Echo["Removed"];
EventRemove[eventObject];
instances = 0;
];

EventHandler[controller, {
"Mounted" -> Function[uid,
s["Instances"] = If[ListQ[s["Instances"]], Append[s["Instances"], uid], {uid}];

If[instances === 0, construct];
instances = instances + 1;

],

"Destroy" -> Function[uid,
s["Instances"] = s["Instances"] /. {uid -> Nothing};

instances = instances - 1;

(* unsubscribe when there is no instances *)
If[instances === 0, destruct];
]
}];

With[{
summary = {BoxForm`SummaryItem[{"State: ", textField}]}
},
BoxForm`ArrangeSummaryBox[
StateMachine,
s,
None,
summary,
Null,

"Event" -> controller
]
]
] ]

The result should be following

Deferred mode ❤️

This is a new feature introduced recently for ViewBox, aimed to solve all hassle required for maintaining the instances of the same object. The idea is to create decorations upon rendering.

Please see on how to implement it in Decorating symbols