ViewBox
Used to replace wrapped Wolfram Expression with custom HTML/JS elements
(*VB[*)(expr)(*,*)(*view*)(*]VB*)
Most decorations generated by TemplateBox
, graphics functions Graphics
, Graphics3D
, InputButton
and etc are ViewBox-es.
- the expression is not editable by a user and is fully replaced in the editor by the provided view (behaves like a 1 symbol)
- the inner
expr
is revealed after first evaluation
RGBColor
, DateObject
and InterpretationBox (together with EditorView) and many more use this structure to display a human-readable form of the content behind
There is a helper function to prevent an editor from rendering, if you want to write using this low-level representation
ViewBox[expr_, displayView_, opts___]
it will keep expr
in a code replacing visually by DOM element. The last one will be attached to the expression displayView
executed after.
Options
"Event"
Manually enables events generation. Using the provided event object identifier (_String
), it fires various events based on its state. You can attach a normal EventHandler
to the given id
uid = CreateUUID[];
EventHandler[uid, {pattern_String :> handler_, ..}]
ViewBox[.., "Event"->uid]
There are following patterns available to be attached to
"Mounted"
fires once a widget is visible. A unique identifier is provided as a payload"Destroy"
fires once it was removed
Once it has been mounted it provides a unique ID as a string in "Mounted"
event, which can be used in the constructor FrontInstanceReference. You can also apply ViewBox`InnerExpression
and ViewBox`OuterExpression
as well.
Examples
This method is much faster than InterpretationBox or Interpretation, since it does not spawn EditorView inside for displaying regular boxes.
Simple straight example
Let us make a special symbol with a single property
boxObject /: MakeBoxes[boxObject[s_], StandardForm] := With[{
g = Graphics[{Blue, Disk[{0,0},1], Opacity[0.5], Red,Disk[{0,0},s]}, ImageSize->80, Controls->False, ImagePadding->None]
},
ViewBox[boxObject[s], g ]
]
As you can see no JS required
Table[boxObject[i], {i,3}]
And you can still work with them as it was symbols
Event listeners
We can make frontend beep, once widget has been destroyed
boxObject /: MakeBoxes[boxObject[s_], StandardForm] := With[{
g = Graphics[{Blue, Disk[{0,0},1], Opacity[0.5], Red,Disk[{0,0},s]}, ImageSize->80, Controls->False, ImagePadding->None],
uid = CreateUUID[]
},
EventHandler[uid, {"Destroy"->Beep}];
ViewBox[boxObject[s], g, "Event"->uid]
]
Replacing expression with custom JS
One can define its own style of cells boxes
.js
core.Replacer = async (args, env) => {
env.element.style.background = "red";
env.element.style.width = "2em";
env.element.style.height = "1em";
}
wrapper /: MakeBoxes[wrapper[expr_], StandardForm] := ViewBox[wrapper[expr], Replacer]
and then try
wrapper[1/2]
This is basically how RGBColor
, DateObject
are implemented
Mutability
In general it is possible to update the expression underneath indirectly. For this reason, there are multiple way of accessing this feature
From Wolfram Kernel
Please see methods below
ViewBox`InnerExpression
Is used to replace a covered expression by ViewBox
with a given string.
It must be evaluated in the context of an instance of ViewBox
, use FrontInstanceReference and FrontSubmit in order to apply this to a specific box
It returns the current content if no string is specified as an argument. Use it with FrontInstanceReference and FrontFetch.
For example, lets make a dummy view-box, which covers a simple string with a disk
dummy /: MakeBoxes[_dummy, StandardForm] := With[{uid = CreateUUID[]},
EventHandler[uid, {
"Mounted" -> Function[res, ref = res]
}];
ViewBox["Covered", Graphics[Disk[]], "Event"->uid]
]
dummy[]
Now our element reported about itself into ref
global symbol. Let us fetch this covered expression
FrontFetch[ViewBox`InnerExpression[], FrontInstanceReference[ref]]
"\"Covered\""
Now lets change it
FrontSubmit[ViewBox`InnerExpression["1+1"], FrontInstanceReference[ref]]
Now if you evaluate our disk, you will see
2
ViewBox`OuterExpression
Is used to replace or update the content string within the box (including the box as well)
It will destroy an instance of the ViewBox once applied.
Example
ref = FrontInstanceReference[];
Plot[x, {x,0,1}, Epilog->{ref}]
and then we can destroy it and replace with our text
FrontSubmit[ViewBox`OuterExpression["Hello World"], ref]
From Javascript function
An expression displayView
is evaluated on WLJS Interpreter in the browser with special property provided (see tutorial WLJS Functions)
core.displayView = async (args, env) => {
env.global.EditorWidget
}
this object contains property to update the content
EditorWidget.applyChanges('"new content"')
Example
Let us create a special object, that will act like a checkbox
CheckObject /: MakeBoxes[CheckObject[state_:(True | False)], StandardForm] := With[{},
ViewBox[CheckObject[state], CheckBoxDecorator[]]
]
now JS part
.js
core.CheckBoxDecorator = async (args, env) => {
let state = false;
//check the raw data from the viewbox to determine the state
if (env.global.EditorWidget.getDoc() == 'CheckObject[True]') state = true;
//make a rectangle
env.element.style.width = "1em";
env.element.style.height = "1em";
const update = (s) => env.element.style.background = s ? 'red' : 'blue';
//color it depending on state
update(state);
//logic for updates when a user click on it
env.element.addEventListener("click", () => {
state = !state;
update(state);
const stringState = state ? 'True' : 'False';
env.global.EditorWidget.applyChanges('CheckObject['+stringState+']');
});
}
Let's test it!
CheckObject[True]
then try to click on it, copy and paste it
whatever you do, it will keep its state synced. No communication with WL Kernel happens, everything is running within the code-editor in the browser.
Or even cooler
Table[CheckObject[True], {i, 3}, {j, 3}] // MatrixForm