XO Form Schemas are now compiled into a full recursive component model, before recursively rendering the component tree.
A tree of components is now instantiated at JSON/JS parsing time, with every component having a parent
and children
, and the rendering process is fully delegated to the individual controls.
It all starts with the root
control (ExoRootControl
), that takes care of setting up the page/pages and the navigation control container (ExoNavControl
).
There is now a clear separation of parsing and accepting field properties in controls (acceptProperties()
), that takes place in the control component constructor, and application of property values (mapAcceptedProperties()
), where you can apply the state as passed through the JSON/JS field properties directly, or through databinding, to the state of your component.
As mentioned above, each control that can have children (fieldset, multiinput, group, root, etc.) is now responsible for rendering subcomponents. This means that anyone can create a nesting and the schema can be extended in many ways.
Reactive data binding has been drastically improved. You can now simply bind nearly every schema property directly to a property in one of the model instances.
For instance, enabling controls based on state in the model is now much simpler, because you can now use model variables directly.
See below (enabled: "#/ui/useemail"
):
const schema = {
model: {
instance: {
ui: {
useemail: true
},
data: {
email: ""
}
}
},
theme: "thin",
pages: [
{
legend: "My First XO Form",
fields: [
{
type: "checkbox",
caption: "Use email",
bind: "#/ui/useemail"
},
{
type: "email",
caption: "Your email",
bind: "#/data/email",
required: true,
placeholder: "john@doe.com",
enabled: "#/ui/useemail",
prefix: {
icon: "ti-email"
}
}
]
}
]
};
The XO Form engine has been optimized in many ways,
XO now uses consideraby less memory and cleans up used memory earlier.
The integrity of the XO platform is fully tested using a built-in test suite.
We are continously working on the expansion of the number of tests.
We have completely rewritten XO theming, for easier use and extension.
A new theme, thin
has been added, and default form layout has been improved so that custom styling is a lot easier.
See Themes & Styling
See XO Studio now uses the Microsoft Monaco Editor (yes, the one that powers #VSCode), complete with custom XO Form intellisense!
The underlying XO Form Control is xoformeditor
, which is a wrapper around the new monacoeditor
, which is more generic.
{
type: "monacoeditor",
caption: "Monacoeditor",
bind: "#/data/html",
language: "html",
class: "full-height",
options: {
minimap: {
enabled: false
}
}
}
Note: See Monaco documentation for IEditorConstructionOptions for the
options
property.
Binding syntax is now:
#/instancename/property
instead ofinstance.instancename.property
Also, the binding syntax is globally the same, so you no longer need to use a '@' outside the
bind
property.
So if your model looks like this:
model: {
instance: {
data: {
email: "john@doe.com";
}
}
}
... You can bind the email property using this syntax:
{
type: "email",
caption: "Email address",
bind: "#/data/email"
}
We added a rules engine that model based. Rules can be triggered based on state in your model.
You can now add defaults for the options passed to each form xo.form.run()
when creating the xo form context:
const context = await xo.form.factory.build({
defaults: {
validation: "inline",
},
runOptions: {
rules: {
actions: {
myCustomAction: (a, b) => {
// my custom implementation
},
},
},
},
});
In the example above, the Rules Engine is being instructed that, apart from the internal actions the XO Rules Engine provides, a myCustomAction
action exists, and all forms that are run can use it.
Just don't forget to pass the context created to the form runner:
let fr = await xo.form.run(schema, {
context: context,
});
The ruleContextReady
is dispatched when the Rules Engine is ready to execute rules.
Use it to get access to the context
of the rules engine:
let fr = await xo.form.run(schema, {
on: {
ruleContextReady: (e) => {
this.ruleContext = e.detail.context;
},
},
rules: {
actions: {
myCustomAction: (a, b) => {
alert(this.ruleContext.var(a));
},
},
},
});
The sandbox control now has a subform
property (boolean).
Setting subform
to true
will make the form loaded in the sandbox bind to a node in the master model.
const schema = {
model: {
instance: {
data: {
test: "My Data",
sub: {
text: "Subform Control Data",
},
},
},
},
pages: [
{
fields: [
{
caption: "Test Field",
type: "text",
bind: "#/data/test",
},
{
type: "sandbox",
bind: "#/data/sub",
form: {
schema: {
pages: [
{
fields: [
{
type: "text",
caption: "Subform Textbox",
bind: "#/data/text",
},
],
},
],
},
},
},
],
},
],
};
Note the bind: "#/data/text" in the subform: the subform cannot have a model of itself in this case, and the node used in the
bind
property translates to an instance in the subform called 'data'.
The tags
control now also supports autocomplete
, like all text controls.
const schema = {
submit: true,
model: {
instance: {
you: {
pets: ["Cat"],
},
},
},
pages: [
{
fields: [
{
type: "tags",
bind: "#/you/pets",
caption: "What pets do you have?",
autocomplete: {
strict: true,
items: [
"Dog",
"Cat",
"Parrot",
"Rabbit",
"Pig",
"Snake",
"Turtle",
"Other",
],
},
info: "You can select multiple pets",
},
],
},
],
};
- The
autocomplete
settings now include astrict
boolean setting, to allow only values from the autocompleteitems
.
- The action
navigate
was added to the list of available actions in the Rules Engine.
You can now turn a listview
control into a master-detail editor.
See ListView Control
You can now point an instance to a URL. See Data Binding
The XO Form LiveEditor can now be automatically started, using the auto: true
setting.
const schema = {
model: {
instance: {
data: {
name: "",
},
},
},
pages: [
{
fields: [
{
type: "text",
bind: "#/data/name",
caption: "Enter your name",
},
],
},
],
};
document.getElementById("form").appendChild(
await xo.form.run(schema, {
on: {
post: (e) => {
alert(JSON.stringify(e.detail.postData, null, 2));
},
interactive: (e) => {
let exo = e.detail.host;
new xo.form.factory.LiveEditor(exo, {
auto: true,
}).on("schema-change", (e) => {
debugger;
});
},
},
})
);
The XO Studio finally has side-by-side code & rendering, for immediate feedback on your code changes.
Also, you will now directly see model changes as well as posted form values in the 'Data' panel.
We have done a lot to improve the error handling & debugging experience. Errors in your schema now lead to a friendlier and clearer message shown in the form rendering panel.
XO now has better support for OpenAPI. For instance, when selecting OpenAPI in the Start wizard, you will now be able to select a schema to work with in the OpenAPI definition.
A new Router is available, and the PWA Router has been rewrittenn to use this history and hash routing suporting router. The new router can also be used outside the PWA component structure.
- Autocomplete search results can now return a complete HTML element, using the
element
property of the returned object array.
A new function getInstance()
, is in fact a shortcut to get the data of any bound model instance.
return await xo.form.run(schema, {
on: {
post: (e) => {
let contact = e.detail.host.getInstance("contact");
// work with contact instance data
},
},
});
- You can now pass details when calling
trigger
action to trigger an event in the second parameter:
{
type: "button",
caption: "Log in",
value: "clicked",
bind: "#/temp/loginbutton",
actions: [
{
if: {
is: "clicked"
},
do: {
trigger: ["signed-in", {
account: "#/temp/userName"
}]
}
}
]
}
List inputs (radiobuttonlist, checkboxlist) now allow adding entries to the underlying array of items
.
const schema = {
model: {
instance: {
data: {
who: "",
people: ["John", "Pete", "Harry"],
},
},
},
pages: [
{
legend: "Welcome to XO",
intro: "This is a generated web form",
fields: [
{
type: "radiobuttonlist",
bind: "#/data/who",
items: "#/data/people",
addcaption: "Other person",
},
],
},
],
};
List inputs with addcaption
now dispath events on the adding behavior:
const schema = {
model: {
instance: {
data: {
who: "",
selected: {},
people: ["John", "Pete", "Harry"],
},
},
},
pages: [
{
legend: "Using 'addcaption'",
intro: "This radiobuttonlist allows you to add options",
fields: [
{
type: "radiobuttonlist",
bind: "#/data/who",
items: "#/data/people",
addcaption: "Other person",
},
{
type: "multiinput",
name: "editor",
disabled: true,
bind: "#/data/selected",
caption: "Edit",
fields: {
name: {
type: "text",
caption: "First name",
disabled: true,
},
fullname: {
type: "text",
caption: "Full name",
},
email: {
type: "email",
caption: "Email address",
},
},
},
],
},
],
};
const fr = await xo.form.run(schema, {
on: {
dataModelChange: (e) => {
this.exo = e.detail.host;
if (e.detail.changeData?.path === "#/data/who") {
let person = {
fullname: e.detail.changeData?.newValue + " Doe",
};
e.detail.model.instance.data.selected = person;
e.detail.host.get("editor").disabled = false;
}
},
dom: {
"add-item": (e) => {
debugger;
},
"select-add": (e) => {
this.exo.get("editor").disabled = true;
},
},
},
});
this.app.UI.areas.main.add(fr);
- Progress mode
steps
: fix in navigating to shown page headers - New Rules Engine action: focus. See Rules Engine
- Command:
submit: false
, to suppress the automatically generated submit button. The command needs to be placed at the top level of your schema.
const schema = {
submit: false,
pages: [
{
fields: [
{
type: "text",
name: "name",
caption: "Your name",
placeholder: "John Doe",
},
],
},
],
};
- Fix in databinding: multiple fields binding to single value in model.
- Fixed scrolling in
autocomplete
controls - Error handling in
getInstance()
method on form improved - Validation checking didn't take disabled and invisible controls out of the equasion
- Rules engine didn't change visible and disabled state on control, but instead directly modified DOM state.
- Fixed
trigger
action in rules engine. - Fixed bug in PWA Router
route
method