Fonto Why & How: Why are my objects not updating?! Templated views and dependency tracking

Fonto Why & How: Why are my objects not updating?! Templated views and dependency tracking

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 configured an element as an object, created an edit flow using a popover, but it never seemed to update in the view!

Let’s look at how the code works. The element is configured as an object with a custom createInnerJsonMl callback:

// configureSxModule.js:
configureAsObject(
    sxModule,
    xq`self::disposal_section_metadata`,
    t('size range'),
    {
        clickOperation: 'select-node-contents',
        createInnerJsonMl: createSizeRangeJsonMl,
        popoverComponentName: 'SizeRangePopover',
    }
);

The object has a click operation that selects the contents of the node, which explains the yellow background color in the XML source view. The popover is quite a simple popover that just renders two text inputs and two drop-downs. The bug is likely in the createInnerJsonMl callback because that is the one that renders the element. Let’s take a look at that one:

// createSizeRangeJsonMl

const createSizeRangeJsonMl = (
	sourceNode: NodeProxy,
	renderer: JsonRenderer
): JsonML => {
	let sizeRangeText = '';

	const actualNode = documentsManager.getNodeById(sourceNode.nodeId)

	const sizeRangeStartText = generateSizeRangeText(actualNode, 'start');
	const sizeRangeEndText = generateSizeRangeText(actualNode, 'end');

	if (sizeRangeStartText) {
		sizeRangeText += sizeRangeStartText;
	}

	if (sizeRangeEndText && sizeRangeStartText) {
		sizeRangeText += ` and ${sizeRangeEndText}`;
	}

	if (sizeRangeEndText && !sizeRangeStartText) {
		sizeRangeText += sizeRangeEndText;
	}

	const innerJsonMl: JsonML = [
		'div',
		{ style: `color: ${color('text-color')}; opacity: 0.4;` },
		sizeRangeText || t('select the size range'),
	];
	return createFrameJsonMl(innerJsonMl, sourceNode, renderer, {
		backgroundColor: 'white',
		expression: 'context',
		showWhen: 'always',
		blockBefore: [createMarkupLabelWidget()],
		blockBeforeWidth: 'extra-wide',
	});
};

The generateSizeRangeText function is looking into the passed node to build up a string summary of it. The implementation is absent for brevity.

The first thing that stands out is the usage of DocumentsManager#getNodeById to look up the actual node from the NodeProxy that is passed in! That causes dependency tracking to be circumvented: the object template won’t have a clue it’s supposed to rerender because some random XML elements are updated, so it won’t. The developer explained they used this function because they saw it being used somewhere else as well, likely to get hold of the ‘actual’ node to be used in a Map lookup or something comparable.

We rewrote the code to skip the usage of DocumentsManager#getNodeById. This is the result:

Why though

The bug is fixed. But it’s more important to know why it happened in the first place. For that, we should talk about how Fonto works and why it works like that.

This part of Fonto is called Templated Views, it is the part of Fonto that uses templates to give an HTML view over an XML document. It is one of the oldest parts of Fonto, it has been there since the very beginning and the APIs to it have gone largely unchanged. Templated views uses dependency tracking to determine what minimally needs to be updated in the HTML when the XML is updated. It does this by using a NodeProxy class: a proxy over normal nodes that intercepts calls to properties that can change (such as its childnodes, the data of a textNode or the attributes of an element. In recent years we introduces new patterns to get this same level of dependency tracking (blueprints). But because of the age of templated views, we have not gotten around to breaking this API for something that aligns more to modern APIs.

For example, if a template executes sourceNode.getAttribute("class"), the system records it. If we later get a signal in the system that the class attribute of said node is updated, we know the template that evaluated that code is invalidated and needs to update to reflect the new XML.

This is described more in-depth in a paper we presented at XML Prague: the Schematron engine we experimented with was built on the same principles as these minimal updates. Read it here: Soft validation in an editor environment.

I hope this explained how Fonto works and why it works like that. During the years we have 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 (@dr_rataplan) 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