Fonto Why & How: My operation is disabled?!

Fonto Why & How: My operation is disabled?!

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 developer was confused why their operation became disabled after they added a step to it!

A developer is creating the following: whenever a user clicks on a button in the toolbar an operation should start. First, the operation should show a modal asking for some information, after which the lock for the document should be acquired. Lastly, the document metadata should be changed. The developer had an operation working, but when they added the explicit step to acquire the lock, it became disabled.

As always, the code! The developer sent a few pieces of code that were clearly still in development. First the operation definition:

"add-metadata-comment": {
	"label": "Open comment",
	"description": "Open the comment modal.",
	"icon": "pencil",
	"steps": [
		{
			"type": "modal/AddCommentModal"
		},
		{
			"type": "operation/acquire-document-locks",
			"data": {
				"documentIds": ["{{documentId}}"]
			}
		},
		{
			"type": "action/setDocumentMetadata",
			"data": {
				"documentId": "{{documentId}}",
				"documentMetadata": {
					"changes": "bountiful"
				}
			}
		}
	]
}

Secondly, a snippet from the modal code:

const documentId = documentsManager.getDocumentIdByRemoteDocumentId(
	"My-main-map.ditamap"
);

const handleSubmitButtonClick = useCallback(
	() =>
	submitModal({
		documentId,
	}),
	[documentId]
);

The code is clearly still in development: there is a hard-coded document id in the modal, the metadata is not really changed to anything dynamic, etcetera. But we should check why the operation is not disabled.

There are three steps in the operation. First, a modal is opened. This modal will (when it is submitted) pass a document id on to the next step, which will try to acquire the lock for the document. Finally, document metadata is written to and we’re done. The error is in the first step. Because a modal is not evaluated during the getState phase of an operation, the documentId never gets to the acquire-document-locks step. Which disables itself if it receives bad input, like zero document ids, or document ids set to undefined.

There are multiple solutions. The easiest one is to add a transform to the operation that sets the document id early. While exists no transform that immediately sets that, we can join two existing ones together to reach our intended result: transform/setDocumentIdToDocumentContainingContextNode and transform/setContextNodeIdToSelectionAncestor. If we use them like this, the operation is enabled!

"add-metadata-comment": {
	"label": "Open comment",
	"description": "Open the comment modal.",
	"icon": "pencil",
	"steps": [
		{
			"type": "transform/setContextNodeIdToSelectionAncestor",
			"data": { 
				"selectionAncestorNodeSpec": "self::*"
			}
		},
		{
			"type": "transform/setDocumentIdToDocumentContainingContextNode"
		},
		{
			"type": "modal/AddCommentModal"
		},
		{
			"type": "operation/acquire-document-locks",
			"data": {
				"documentIds": ["{{documentId}}"]
			}
		},
		{
			"type": "action/setDocumentMetadata",
			"data": {
				"documentId": "{{documentId}}",
				"documentMetadata": {
					"changes": "bountiful"
				}
			}
		}
	]
}

But why?

The bug is fixed and the operation is working. But why did it occur in the first place? and how will Fonto change its APIs to never have this bug again?

In the operations pipeline of Fonto, we have a number of different steps types:

  • Custom mutations, which can change XML. They are sometimes called in a sandboxed environment to see whether the outcome is valid to the schema.
  • Transforms, which accept data and (synchronously or asynchronously) output new data. They are used to collect information and transform it to new information. They should have no side-effects so we can call them whenever we want.
  • Actions, which change application state. They should have a separate callback which can compute the ‘enabledness’ of the calling operation.
  • Modals, which are in-between actions and transforms. they both set data and have side-effects. They have no logical flow to get the ‘enabledness’. Instead they are ignored when we want to know if an operation is enabled.

In a previous blogpost, I covered a way to set the ‘initial value’ of attributes, using the initialData field. Because we expect this feature will not use any hard-coded document ids, we choose to use transforms instead to get the data. The transforms will target the focused document, so we can call the operation safely, whatever documents are loaded.

But why was the operation enabled before? The action/setDocumentMetadata also has a documentId stepdata property that is required. Why did this not block the operation?

That action has an issue with it. For the separate callback that is called for getting state we forget to check (the presence and validity of) the documentId. We fixed this and this check will be present for Fonto 8.1. This will make Fonto more predictable.

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!

Stay up-to-dateFonto Why & How posts direct in your inbox

Receive updates on new Fonto Why & How blog posts by email

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top