In this weekly series, Martin describes a question that was raised by a Fonto developer, how it was resolved and why Fonto behaved like that in the first place. This week, a partner would like to get some CMS data during a custom mutation!
A support ticket came in that went a little like this:
Hey there!
I’m currently working on a custom mutation, which has to make an ajax request, which is async. I tried to return the CustomMutationResult dependent on the result of the promise. But I’m getting an exception: something about not returning a CustomMutationResult.
Do I need a special thing to make this work, or is there another way?
A Fonto partner
We should take a look at the code how it is now:
addCustomMutation('appendGeneratedBlock', async function appendGeneratedBlock(stepdata, blueprint, format, selection) { try { const result = await cmsClient.setRequest('GET', '/document/custom/generatedBlock', requestData); const body = result.body; const generatedBlock = parseXmlDocument(body.xml); const contextNode = blueprint.lookup(stepData.sourceNodeId); const blockNode = blueprint.appendChild(contextNode, generatedBlock); blueprint.setAttribute(blockNode, "unit", stepdata.unit); // ... Doing something with the result: inserting it into the XML return CustomMutationResult.ok(); } catch (err: unknown) { console.error(err); return CustomMutationResult.notAllowed(); } });
This already warns with TypeScript errors: the return type of the callback for a custom mutation should be a CustomMutationResult
, not a promise to one.
The best way to fix this is by adding a transform to the operation that does the asynchronous part. Because there is a piece of XML retrieved from the CMS, it is best to only retrieve it in the transform and write it to stepData
.
addTransform('setGeneratedBlock', async function (stepData) { try { const result = await cmsClient.setRequest('GET', '/document/custom/generatedBlock', requestData); stepData.generatedBlock = result.body; } catch (err: unknown) { console.error(err); return stepData; } }); addCustomMutation('appendGeneratedBlock', async function appendGeneratedBlock(stepdata, blueprint, format, selection) { if (!stepData.generatedBlock) { return CustomMutationResult.notAllowed(); } // ... Doing something with the result: parsing the XML, mutating it and inserting it into the DOM return CustomMutationResult.ok(); });
Wiring up the operation makes it work at first sight: the generated block is fetched from the CMS and inserted into the XML. However, the CMS is also queried when moving the cursor around. This is because how Fonto determines the state of an operation. By running the operation without actually mutating the XML, but still performing schema validation, we know if an operation will be allowed if the author actually executes it.
For transforms without an explicit getState
callback set, Fonto will assume there are no side effects to the transform. Fonto will just execute the run
callback. In our case, we do have a side effect: the CMS is queried. We should implement the getState
callback to return some kind of stubbing data.
addTransform('setGeneratedBlock', async function (stepData) { try { const result = await cmsClient.setRequest('GET', '/document/custom/generatedBlock', requestData); stepData.generatedBlock = result.body; } catch (err: unknown) { console.error(err); return stepData; } }, function getState (stepData) { stepData.generatedBlock = '<my-generated-block />'; });
I hope this explained how Fonto works and why it works like that. During the years we built quite the product, and we are aware some parts work in unexpected ways for those who have not been with it from the start. If you have any points of Fonto you would like some focus on, we are always ready and willing to share! Reach out on Twitter to Martin Middel or file a support issue!
Developer advocate / Evangelist. Has been with Fonto since it all began in 2013. He’s currently designing the next steps in Fonto Developer APIs with the input of our valuable partners.
In his spare time, Martin is an avid home brewer.
Receive updates on new Fonto Why & How blog posts by email