Manual field extensions vs overrideFieldExtensions
So far, we have used the overrideFieldExtensions hook to programmatically apply our extensions to fields. There is an alternative way of working with field extensions that passes through a second hook that you can implement, namely manualFieldExtensions:
With this setup, we are still automatically applying our "Lorem ipsum" generator to every text field in our project, but the "Star rating" is becoming a manual extension. That is, it's the end-user that will have to manually apply it on one or more fields of type "integer" through the "Presentation" tab in the field settings:
When to use one strategy or the other?
At this point a question may arise... when does it make sense to force an extension with overrideFieldExtensions and when to let the user install it manually? Well, it all depends on the type of extension you're developing, and what you imagine to be the most comfortable and natural way to offer its functionality!
Let's try to think about the extensions we have developed so far, and see what would be the best strategy for them:
Given that the "Star rating" extension will most likely be used in a few specific spots, rather than in all integer fields of the project, letting the user manually apply it when needed feels like the best choice.
On the other hand, our "Lorem Ipsum generator" is probably convenient in all text fields: requiring the end user to manually install it everywhere could be unnecessarily tedious, so the choice to programmatically force the addon on all text fields is probably the right one.
If we feel that a carpet-bombing strategy for the "Lorem ipsum" extension might bee too much, and we wanted to make the installation more granular but still automatic, we could add some global settings to the plugin to allow the user to configure some application rules (ie. "only add the addon if the API key of the text field ends with _main_content"):
If you can't make up your mind on the best strategy for your field extension, there's always a third option: let the end user be in charge of the decision! Plugin settings are always available in every hook, so you can read the user preference and act accordingly:
Add per-field config screens to manual field extensions
In the manualFieldExtensions() hook, we can pass the configurable: true option to declare that we want to present a config screen to the user when they're installing the extension on a field:
To continue our example, let's take our "Star rating" editor and say we want to offer end-users the ability, on a per-field basis, to specify the maximum number of stars that can be selected and the color of the stars.
Just like global plugin settings, these per-field configuration parameters are completely arbitrary, so it is up to the plugin itself to show the user a form through which they can be changed.
Don't use form management libraries!
Unlike the global config screen, where we manage the form ourselves, here we are "guests" inside the field edit form. That is, the submit button in the modal triggers the saving not only of our settings, but also of all the other field configurations, which we do not control.
The SDK, in this location, provides a set of very simple primitives to integrate with the form managed by the DatoCMS application, including validations. The use of React form management libraries is not suitable in this hook, as most of them are designed to "control" the form.
The hook provided to render the config screen is renderManualFieldExtensionConfigScreen, and it will be called by DatoCMS when the user adds the extension on a particular field.
Inside the hook we simply initialize React and a custom component called StarRatingConfigScreen. The argument ctx provides a series of information and methods for interacting with the main application, and for now all we just pass the whole object to the component, in the form of a React prop:
we use ctx.parameters as the initial value for our internal state formValues;
as the user changes values for the inputs, we're use ctx.setParameters() to propagate the change to the main DatoCMS application (as well as updating our internal state).
Always use the canvas!
It is important to wrap the content inside the Canvas component, so that the iframe will continuously auto-adjust its size based on the content we're rendering, and to give our app the look and feel of the DatoCMS web app.
Enforcing validations on configuration options
Users might insert invalid values for the options we present. We can implement another hook called validateManualFieldExtensionParameters to enforce some validations on them:
1
constisValidCSSColor=(strColor:string)=>{
2
const s =newOption().style;
3
s.color = strColor;
4
return s.color !=='';
5
};
6
7
connect({
8
validateManualFieldExtensionParameters(
9
fieldExtensionId:string,
10
parameters: Record<string,any>,
11
){
12
const errors: Record<string,string>={};
13
14
if (
15
isNaN(parseInt(parameters.maxRating)) ||
16
parameters.maxRating <2||
17
parameters.maxRating >10
18
) {
19
errors.maxRating ='Rating must be between 2 and 10!';
20
}
21
22
if (!parameters.starsColor ||!isValidCSSColor(parameters.starsColor)) {
23
errors.starsColor ='Invalid CSS color!';
24
}
25
26
return errors;
27
},
28
});
Inside our component, we can access those errors and present them below the input fields:
Now that we have some settings, we can access them in the renderFieldExtension hook through the ctx.parameters object, and use them to configure the star rating component:
'Lorem Ipsum is simply dummy text of the printing and typesetting industry',
5
choices: [
6
{
7
label:'Positive',
8
value:'positive',
9
intent:'positive',
10
},
11
{
12
label:'Negative',
13
value:'negative',
14
intent:'negative',
15
},
16
],
17
cancel:{
18
label:'Cancel',
19
value:false,
20
},
21
});
22
23
if (result) {
24
ctx.notice(`Success! ${result}`);
25
}else{
26
ctx.alert('Cancelled!');
27
}
These properties provide access to "entity repos", that is, the collection of
resources of a particular type that have been loaded by the CMS up to this
moment. The entity repos are objects, indexed by the ID of the entity itself.
All the models of the current DatoCMS project, indexed by ID.
All the fields currently loaded for the current DatoCMS project, indexed by
ID. If some fields you need are not present, use the loadItemTypeFields
function to load them.
All the fieldsets currently loaded for the current DatoCMS project, indexed
by ID. If some fields you need are not present, use the
loadItemTypeFieldsets function to load them.
All the regular users currently loaded for the current DatoCMS project,
indexed by ID. It will always contain the current user. If some users you
need are not present, use the loadUsers function to load them.
All the SSO users currently loaded for the current DatoCMS project, indexed
by ID. It will always contain the current user. If some users you need are
not present, use the loadSsoUsers function to load them.
These methods let you open the standard DatoCMS dialogs needed to interact
with records.
Opens a dialog for creating a new record. It returns a promise resolved
with the newly created record or null if the user closes the dialog
without creating anything.
const itemTypeId =prompt('Please insert a model ID:');
2
3
const item =await ctx.createNewItem(itemTypeId);
4
5
if (item) {
6
ctx.notice(`Success! ${item.id}`);
7
}else{
8
ctx.alert('Closed!');
9
}
Opens a dialog for selecting one (or multiple) record(s) from a list of
existing records of type itemTypeId. It returns a promise resolved with
the selected record(s), or null if the user closes the dialog without
choosing any record.
Opens a dialog for editing an existing record. It returns a promise
resolved with the edited record, or null if the user closes the dialog
without persisting any change.
These methods can be used to update both plugin parameters and manual field
extensions configuration.
Updates the plugin parameters.
Always check ctx.currentRole.meta.final_permissions.can_edit_schema
before calling this, as the user might not have the permission to perform
the operation.
Performs changes in the appearance of a field. You can install/remove a
manual field extension, or tweak their parameters. If multiple changes are
passed, they will be applied sequencially.
Always check ctx.currentRole.meta.final_permissions.can_edit_schema
before calling this, as the user might not have the permission to perform
the operation.
ctx.notice(`Successfully edited field ${field.attributes.api_key}`);
36
}
These methods let you open the standard DatoCMS dialogs needed to interact
with Media Area assets.
Opens a dialog for selecting one (or multiple) existing asset(s). It
returns a promise resolved with the selected asset(s), or null if the
user closes the dialog without selecting any upload.
const uploadId =prompt('Please insert an asset ID:');
2
3
const item =await ctx.editUpload(uploadId);
4
5
if (item) {
6
ctx.notice(`Success! ${item.id}`);
7
}else{
8
ctx.alert('Closed!');
9
}
Opens a dialog for editing a "single asset" field structure. It returns a
promise resolved with the updated structure, or null if the user closes
the dialog without persisting any change.