Fonto Why and How: How do I make a dynamic toolbar!?

Fonto Why and How: How do I make a dynamic toolbar!?

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 wondered how to make their toolbar dynamic. How do you hide buttons when they are not applicable?!

A partner reached out. They have an editor loading two kinds of documents that work similar, but they have some small differences. While one document type has a concept of ‘boxed paragraphs’, the other does not know of that. This ‘boxed paragraph’ element should be managed from the toolbar. The different documents can be recognized by having a different type attribute on the root element.

If that button would always be shown, it would be disabled half of the time. Only enabled when authors open the correct type of document. This is not the best from a usability aspect because it would still take up space.

Our initial reaction was to use the isVisibleTabQuery on the FxEditorMastheadTab component to only show a toolbar with this button in certain documents. While this might be a solution to the problem, it is not the most straight-forward. Basically, we would have to copy the entire tab and maintain two versions: one with the button and one without.

const tabs = [
	{
		isVisibleTabQuery: xq`/@type = "my-special-type"`,
		content: <TabWithButton/>
	},
	{
		isVisibleTabQuery: xq`/@type != "my-special-type"`,
		content: <TabWithoutButton/>		
	}
];

An easier way is to use the fact that the toolbar is just React code! This means we can do whatever we want in there, as long as it is valid React!

This means we can use the useXPath hook in here. Do know that React hooks cannot be called conditionally, so we need to call the hook always, before we have any early returns for example.

const tab = {
	content: (
		<MastheadToolbar>
			{/*...*/}
			<MastheadToolbarButtons>
				{
					useXPath(
						xq`fonto:selection-common-ancestor()/root()/@type="my-type"`,
						null,
						{
							expectedResultType: ReturnTypes.BOOLEAN
						}
					) &&
					<FxOperationButton operationName="toggle-boxed-paragraph"/>
				}
			</MastheadToolbarButtons>
			{/*...*/}
		<MastheadToolbar>
	)
};

Note we are using the fonto:selection-common-ancestor function here. This XPath function will return the common ancestor container of the selection. Passing that into the fn:root function gives us the document node. From there we can query the type attribute of the document element to check the value.

For other toolbars that could use some re-use (pun intended), we can also use a similar pattern. Take for example a special button that should only be shown if a certain scope parameter is set. We can do something like this:

import configurationManager from 'fontoxml-configuration/src/configurationManager';

const scope = configurationManager.get('scope');

const tab = {
	content: (
		<MastheadToolbar>
			{/*...*/}
			<MastheadToolbarButtons>
				{
					scope.isAdmin === true && <FxOperationButton operationName="my-special-admin-function"/>
				}
			</MastheadToolbarButtons>
			{/*...*/}
		<MastheadToolbar>
	)
};

But why?

But why is the toolbar in React? And why is it not in a single configuration file? That way partners would have to write less React boilerplate and the logic would stay at the Fonto side, right?

We actually had a declarative toolbar API before, written in Jade. This used to be the case when we used AngularJS as our front-end framework. All the way back when we released Fonto 7.0 in 2017 we made a move to React and we removed the Jade-based configuration to plain React.

We made this choice because we were basically rebuilding all the tricks in Angular in the Jade template, including loops, conditional visibility and more complex set-ups. We decided when we built the same in React that we should build on top of React, and not try to implement concepts React already had.

The sky is the limit! Of course, keep the toolbar manageable and predictable to your authors and place your most-used buttons at the front where they can best be found. You can make the toolbar code more maintainable by making it more dynamic and you can make the toolbar friendlier to your authors by not showing buttons they could never use anyway!

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