Using an SVG file as DataSource

We needed a way to somehow facilitate updating the highlighted countries on a map of Europe from within the Backoffice and found a nice way to keep the options synced.

The Map

The task at hand was to display a map of Europe where some of the countries could be highlighted - but also to make it easy from within Umbraco to change the selected countries, so editors wouldn't have to create a new map image every time there was an update.

Something like this:

A map of Europe in a dark blue color with some countries in a lighter blue color

The desired result - a map of Europe where some countries have been highlighted

So first off we'd need an SVG of Europe - preferably with each of the countries in separate shapes. Turns out that was very easy to find.

In Umbraco, my first thought was then to build a "repository" tree with the selected countries. That way, the editor(s) could add a new content node for any new country, and/or delete any node for a country that should no longer be highlighted.

If we could match up the ids (or more likely the aliases) with <path> / <polygon> in the SVG, it would be possible to switch them on and off in the SVG.

But then I realised that the SVG really has all the info - so why not do some kind of lookup in that, and then just record the on/off state in Umbraco?

Buttons

Well, an SVG file is an XML file, and let's just say that I have some experience in that area :) So I immediately thought of Lee Kelleher's nothing short of brilliant DataList property editor in his Contentment package.

One of the (many) options for specifying a data source here is XML Data where you specify a file and then use XPath to grab the label + value, plus maybe even an icon and a description.

The datasource configuration dialog showing the "Items XPath" (//ns1:*[@id][@aria-label]), the "Name XPath" (@aria-label) and "Value XPath" (@id)

The XML Data Source's configuration screen

So we amended the SVG with id and aria-label attributes for each country:


<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600">
	<g id="albania" aria-label="Albania">
		<path d="M457.699,506.254l-0.217,0.133l-0.008,0.191..."/>
	</g>
	<g id="andorra" aria-label="Andorra">
		<path d="..." />
	</g>
	<g id="austria" aria-label="Austria">
		<path d="..." />
	</g>
	<g id="belarus" aria-label="Belarus">
		<path d="..." />
	</g>
	<g id="belgium" aria-label="Belgium">
		<path d="..." />
	</g>

	<!-- More country paths... -->
</svg>

The SVG file after adding `id` and `aria-label` attributes

We then pointed the Data source at the file... and got nice buttons for each of the countries in the file!

The DataType configuration dialog for the Contentment DataList property editor. The Data source has been set to "XML Data" and the List editor has been configured to use the "Buttons" editor. The Preview shows a cluster of buttons for the countries in Europe, arranged alphabetically.

The Data Type's configuration screen

Tip

Just as there's a multitude of options for the Data source in Contentment, the List editor also has multiple possibilities - and they're all hot-swappable, so you can try them out and pick the one that fits your project best; we chose Buttons for this one because it can present a fair amount of options in a somewhat compact space.

Oh, and if you really need it - Lee made it possible to add your own Custom Data source and/or Custom List editor to the mix - you can read all the gory details in the GitHub repository.

Rendering

For rendering the SVG on the frontend I turned to XSLT, mostly because I'd done a very similar thing on a previous project, but also because its sole purpose is to transform XML, which is precisely what we're doing here.

If you don't know XSLT, I'm pretty confident you'll be able to find some other means to do the transformation of the SVG.

My XSLT is using an Identity Transform to process the file, so I added this template to handle adding a CSS class to any picked country:


<!-- Template for processing a `<path>` or `<g>` country element in the SVG -->
<xsl:template match="svg:path[@id] | svg:g[@id]">
	<!-- Grab this country path's alias -->
	<xsl:variable name="countryAlias" select="@id" />
	<xsl:copy>
		<xsl:copy-of select="@*" />
		<!--
		If the list of picked countries contains this one,
		add a 'selected' class to it -->
		<xsl:if test="$countries/country[. = $countryAlias]">
			<xsl:attribute name="class"><xsl:value-of select="concat(@class, ' selected')" /></xsl:attribute>
		</xsl:if>
		<xsl:apply-templates />
	</xsl:copy>
</xsl:template>

The XSLT template that applies the `selected` class

Closing thoughts

One of the benefits of using the SVG itself as the datasource for the options is of course that if/when there's a change in the underlying data, we can update the SVG file and instantly have the new options available. While the list of countries in Europe doesn't change that often, the client could very well extend their services to e.g. Africa, which would then require an update to the map - with this approach, as long as we add the aliases and labels we can keep the Backoffice in sync with the map being rendered.

I'm also sure there's plenty of ways you could have rendered the selected countries as a chunk of JSON somewhere and then let a JavaScript function handle the highlighting when the page loads - I just like the fact that even if the JavaScript should fail to load for some reason, the map still works as intended.

Thank you for reading all the way to the end - have a great Christmas and don't hesitate to get in touch if you have questions/opinions etc. 🙌


Identity transform: A way of processing an XML file, where every element is copied by default but with the possibility of providing a template for overriding any specific element/attribute's output. ↩ī¸


Chriztian Steinmeier
Chriztian Steinmeier
GitHub
greystate
Twitter
greystate
Mastodon
greystate

Tech Tip: JetBrains Rider

Check it out here