Skip to main content

Customising preso creation

Related links:

In this guide, you will learn how to create fields on the preso creation page, and generate & update the context - allowing users to further customise their presos, and giving us a place to store the results of those fields.

Fieldsets in the LivePreso app:

The bottom right area of this page is what we'll be customising in this guide.

image

Adding fieldsets interface to hooks

Let's set it up so that the CDK and LivePreso recognise that you want to deal with fieldsets. First you will want to create a hooks file, use the hooks object in the project.yaml to define where in your deck your hooks.js file is located.

project.yaml

hooks: js/hooks.js

Second, we'll need a fieldsets file. In general, you place this in js/hooks/fieldsets.js. To this file you will add the bare minimum for fieldsets to be loaded.

fieldsets.js

export default function (options) {
const { Fieldset } = options;

return new Fieldset({});
}

hooks.js

Going back to our hooks.js file, you need to export the hook:

import Fieldsets from "./hooks/fieldsets.js";

export default {
fieldsets: Fieldsets,
};

That's it! The CDK and LivePreso now read fieldsets.

Before we get stuck into adding fields to our preso creation page, we are going to need somewhere to save the captured information. This is where the context comes in!

Generating the context

The context is almost entirely generated by fieldsets, and is only added to as your deck needs it. Modifying context can be done globally on three separate events: onLoad, and onSave, at least while there are no fields available.

export default function (options) {
const { Fieldset } = options;

return new Fieldset({
onLoad() {
return {
context: {
onLoadField: true,
},
};
},

onSave() {
return {
context: {
onSaveField: true,
},
};
},
});
}

If you test this in the CDK or LivePreso app, you will notice on load the onLoadField key has been added to the context. When you press Run (analogous to pressing save in the preso create screen), it will add the onSaveField key to the context as well. Note how the context in each function is merged together, rather than overwriting each other.

The result of our context after save is:

{
"fields": {},
"onLoadField": true,
"onSaveField": true
}

Populating the context on preso creation

Now that you know how to add context, it's probably a good idea to start adding useful information to your presentations. Each function exposed by fieldsets exposes an object containing useful information about the state of fieldsets and the preso at different stages of its creation. From field state, to customers and contacts selected, this can be used to create useful context.

Let's make use of data associated with the appointment - specifically adding the names of the contacts to our context, as this is something that might be done often.

export default function (options) {
const { Fieldset } = options;

return new Fieldset({
onSave({ data }) {
return {
context: {
attendees: data.contacts.map((contact) => contact.fullName),
},
};
},
});
}

Generally it's best to only add the data you need into your context, keeping it simpler to navigate and access what you need.

Now if you were wanting to access to the list of your attendees in your presentation, you could use:

Bridge.Context.get("attendees");

See the fieldsets options reference for more information on the data available, and the sharing data between slides guide on how to use the context within your presentation.

Adding a field

Although automatically required context on initialization and save like above is extremely useful (and for the case of some decks, all you'll need), there are times you wish for dynamic data added during presentations. Let's add a basic example:

export default function (options) {
const { Fieldset, Input } = options;

return new Fieldset({
fields: [
new Input({
name: "customerID",
label: "Customer ID",
}),
],
});
}

You'll notice that all the interfaces needed to generate fieldsets comes from the options passed into the fieldsets function. These options contains other types of fields as well, that work fairly similarly. It's very easy to add new inputs.

Accessing events for a field

Sometimes you want to have more granular access to your fields, so all of the global methods are available on each field instance as well - with an added value key accessible in the method options.

export default function (options) {
const { Fieldset, Input } = options;

return new Fieldset({
fields: [
new Input({
name: "customerID",
label: "Customer ID",
onLoad() {
return {
context: {
customerIDLoaded: true,
},
};
},

onSave() {
return {
context: {
customerIDSaved: true,
},
};
},

onChange({ value }) {
return {
context: {
customerID: value,
},
};
},
}),
],
});
}

If you fill out the input, you'll notice the context updates on each key press with the value of that input. After pressing save, you would see the context as the following:

{
"fields": {
"customerID": "Your input here"
},
"customerIDLoaded": true,
"customerID": "Your input here",
"customerIDSaved": true
}
info

Once fields are added to your fieldset, the use of the global onChange method can give you the ability to change context and state on any field's value change.

Meddling with state

Sometimes you want to change the state of the fields... whether you want to change their value, disable them, or completely change the values in a select input. Doing this blindly would be confusing if not for the form state window in the CDK. Viewing the state of the previous input shows the following:

{
"fields": {
"customerID": {
"value": "Your input here",
"disabled": false
}
},
"errors": {}
}

Everything inside the fields object is what we can modify. In this case, we can change the inputs value or whether it's disabled or not, and adding state changes are easy.

Let's make it so that if you write "hello" in the input, the input is disabled.

export default function (options) {
const { Fieldset, Input } = options;

return new Fieldset({
fields: [
new Input({
name: "customerID",
label: "Customer ID",

onChange({ value }) {
if (value.toLowerCase() === "hello") {
return {
state: {
customerID: {
disabled: true,
value: "I've been disabled!",
},
},
};
}
},
}),
],
});
}

You'll see that the state has been changed after finishing typing in the word "hello":

{
"fields": {
"customerID": {
"value": "I've been disabled!",
"disabled": true
}
},
"errors": {}
}

All state changes can be worked out this way, and made both through global events and per-field events.

Using set()

Sometimes you want to modify the state of the fieldset during an asynchronous task - for example while fetching data about a customer you might want to hide fields, set them to disabled, or show a spinner to the user. In this case you can use the set function from the options.

export default function (options) {
const { Promise, Fieldset, Input } = options;

return new Fieldset({
onCustomersChange({ set }) {
set({
state: {
customerID: { loading: true, disabled: true, value: "" }
}
});

// Pretend request
const fakeRequest = Promise.delay(4000).return(123);

return fakeRequest.then((customerID) => ({
state: {
customerID: {
loading: false,
disabled: false,
value: customerID
}
}
}));
},
fields: [
new Input({
name: "customerID",
label: "Customer ID",
}),
],
});
}

Validation

Sometimes you want to validate your inputs, and not allow the preso to be saved until the issue is resolved. This can be done with the onValidate method that is available globally and to each field.

export default function (options) {
const { Fieldset, Input, Select } = options;

return new Fieldset({
fields: [
new Input({
name: "field1",
label: "Field 1",
onValidate() {
return {
errors: ["Unknown error"],
};
},
}),
new Select({
name: "field2",
label: "Field 2",
options: [
{ label: "Option 1", value: "option1" },
{ label: "Option 2", value: "option2" },
],
}),
],
onValidate() {
return {
errors: {
field2: ["Error 1", "Error 2"],
},
};
},
});
}

Note that the onValidate method for a field does not require a name, it automatically assumes the validation for itself. If you were wanting to check multiple fields however, the global method might be more straightforward, but the name of the field must be added.

Also note that you can have multiple error reasons.