Previously in this series, I had to take a momentary detour to restructure the viewer to use
Redux for streamlined application state management as the viewer has grown to the point where having a
Flux architecture was essential for viewer development to continue at a maintainable level.
The challenging part of this process was to get the map viewer component to play "dumb" (so that we can wrap a "smart" redux-aware presentational component around it), namely because this component is the one that wraps
OpenLayers, and we had to modify our usage in a way that facilitates
uni-directional data flow that is required by Redux. Instead of making direct calls to OpenLayers to pan/zoom/etc, it now pans/zooms/etc based on the component props and any changes to those props.
This presents a slightly awkward at first glance (but necessary) situation where any events from OpenLayers that would change the map view and display parameters, we have to intercept these changes and flow it back to the Redux store first, that would then propagate back to its "smart" parent component that will set the necessary prop changes to carry out the map actions.
This work is now mostly complete, so now we can focus back on continuing evolving the viewer.
As I've been implementing the following features described below, I've gotten this great feeling of validation in my choice of using React, because there is actually high conceptual synergy between React and our current Fusion viewer framework.
- Fusion is based on widgets. React is based on components
- A Fusion application is composed by widgets described by a Flexible Layout document. Our react-based viewer is composed by various components, which I intend to compose together through a configuration abstraction similar to a Web/Flexible Layout document in combination with our (now) centralized application state in the Redux store.
But unlike Fusion, we have a single source of truth for application state. In Fusion, application state is all over the place with some state requiring manual event subscription to be notified of any changes. Our react-based viewer has none of these problems thanks to Redux.
So now, here's what I've achieved since the last post in this series.
Layout Templates
We now have the notion of layout template components. This is the Fusion template analogue ported over to our React-based viewer. To support this template concept in the React world, we need a supporting cast:
1. We have various registry modules that allow various components and commands to be registered. This is also an extension point that allows for external components and commands to be registered as well.
2. We also have a notion of "placeholder" components which reference a particular component by its registered id. Layout template components consists of these placeholder components and various layout/placement code, which once the application has been initialized, these placeholder components will mount and will render the actual components from the registry by their component ids. The placeholder allows for a clean error boundary that allows us to show informative errors in its place should we try to render a component that has not been registered.
In practice what placeholder components give us can be illustrated like so:
Here's the viewer using the "AJAX Viewer" layout.
Should some components not be registered, say the Task Pane and Legend components, we get a clear error message that spells it all out.
In Fusion, such a situation would probably be a silent failure. Even in this state, the new viewer is still mostly functional as all these components are now independent of each other. Their only shared dependency is the centralized redux store. So with this clean separation of components and presentational layout now in place, I've also explored some other template designs. In particular, a port of my
dream responsive map viewer layout.
Say hello to the "Sidebar" template, named after the
sidebar-v2 template it was originally derived from:
Unlike the original template, all the jQuery that drives the original sidebar behavior has been ripped out and replaced with React components that do the same thing. Also it has some extra niceties, such as the main toolbar docked vertically, and some in-built smarts to auto-flyout Task Pane content when URL commands are invoked, which is important on mobile devices, as illustrated below when I try to do a buffer on an emulated smartphone display.
Web Layout compatibility
Here's something about the two templates above. They are both driven by the same Web Layout document. Why invent a new configuration mechanism when our existing Web Layout and Flexible Layout documents already do this for us?
Being able to support Web Layouts (and in the future, Flexible Layouts) is important as it means the authoring process for MapGuide applications using this new viewer
remains unchanged. The only difference is you point the Web/Flexible Layout to this viewer instead of your AJAX/Fusion viewer. Now this support is not 100% compatible, there's still missing commands I've yet to port across, but feed a Web Layout document to this viewer and it should be able to recognize most of the commands and configuration options within.
As hinted in the previous paragraph, I intend to support Flexible Layouts as well. As mentioned previously, Fusion has such conceptual similarity to React that most of the things that drive Fusion should be easily portable to our new viewer as well.
Other cool stuff
Thanks to
ol3-contextmenu, we got a functional context menu now. This is driven by context menu settings from the Web Layout.
The viewer now has basic modal dialog support as well, which is an important component for an "Aqua" style layout template when I get round to it, and also as an alternative target for Invoke URL command content.
Current weigh-in
Before I close out this post, here's the current weigh-in for our viewer bundle.
I have no doubt we'll cross the 1MB barrier soon, but once I start seriously looking at "paying for only what we use" with regards to all the libraries we're using, the final bundle size should hopefully approach something more reasonable