JSS (React) Dynamic Layouts - Part 1: LayoutFactory
By default, the bootstrapped JSS React app created by the 'jss create' command uses a single hardcoded layout. Let us see if we can change that.
If you've read my previous post about first impressions of JSS 11.0, you'll know that one of the things I noticed was that JSS uses a single hardcoded global layout, no matter what layout you put on the presentation details of your route item in Sitecore. JSS's primary aim is to allow you to build single-page applications that can leverage all the power of Sitecore, and for this purpose a single layout will serve you perfectly fine in most cases. No doubt this is why the bootstrapped app is built this way. But, if you would like a little more flexibility for the layout of your various routes, you can easiliy achieve this. So lets!
In the original app, the single layout that is used (Layout.js) is placed simply in ./src, the first level of our source code structure. If we are to have more than one layout, I think it would behove us (yes, behove, I said it) to tidy things up. So let's:
- Create a layout folder in ./src.
- Then we'll take the exisiting default layout (./src/Layout.js) and move it to our new folder. I also renamed it DefaultLayout.js, to clarify things (You might get warnings/prompts to update imports of this file. It doesn't really matter, as we will modify or remove those imports shortly anyways. However, make sure you update the imports with relative paths in the actual layout file, for example the asset imports).
- Create some layout to use for testing that the layout resolvement works. I simply duplicated DefaultLayout.js and named the duplicate TestLayout.js.
- In your test layout (In my example: TestLayout.js), make some small modification that ensures you can verify wether this layout is being used or not. I simply added an h2-element with some text in it. See image below.
- Now we want to make sure that we can import all layouts added to the layouts-folder from other parts of out code in an easy fashion. We do that by adding an index.js file where we import all out layouts, and then export them. This allows us to control by what name the layouts are exported, as well as making it easier for callers to import.
Alright! In the image below you can see the new structure as well as the contents of our index.js file.
Next step, LayoutFactory!
This is the essential cog in the handling the layouts. I want to preface this by saying that there is, of course, a lot of different ways to solve this. One way, that in my opinion is a bit cleaner that the one I actually went with, would be to extend the layout service to send the name of the layout that is used in the route data (I did end up doing this in part 3 of this series, check it out.). By default it only sends the layout ID, which means we must maintain some sort of ID -> Layout map. And I actually went with this approach for now, my argument being that this way I do not need to alter any standard sitecore functionality which could potentially be affected by future updates. The added overhead of maintaining the ID -> Layout map is rather small. If it would have required more effort, I probably would have gone with extending the layout service instead.
Anyway, let's create a new layout item in sitecore to correspond with our new TestLayout.js. I created mine under the layer project folder for the app (my app is called satellite, do not ask me why). Since my TestLayout.js is basically an exact duplicate of the default layout, I simply duplicated the original layout item in Sitecore as well. That way we get the correct placeholder settings.
Now, let's see the code. There's a hundred different ways to make this LayoutFactory. I decided to create a class and export a singleton, my only arguments being that I like to work with classes and I want to make sure the layout map is only constructed the one time.
Notice the ID added to the layoutMap map-object corresponds to the ID of the layout item we created. You can see it highlighted in the image above the code sample.
If you are using the original RouteHandler, this is the amendments you'll have to make in it to allow for the dynamic layout resolvement.
- First, remove the import of the original layout (if you still have it) and replace it with an import for LayoutFactory.
- Next, make sure the correct layout is resolved in the render function. See example below:
Done for now
And that's it! We are now able to resolve layout based on the layoutId we get in the layout service JSON responses.
Admittedly, this is just a piece of the puzzle. For starters, we would like to incorporate our layouts into the app deployment, so as to allow for a code-first workflow, as currently we need to manually create the layout items in Sitecore. However, if you know you will only work by a sitecore-first workflow, then you can be done here if you want!
Subsequent posts in the series will be about including layouts in the app deployment process, and we'll also take a look at how to auto-generate the layout map that is used in LayoutFactory, so that we don't need to keep handling IDs and component names manually. You can check out part 2 here!
14 Jan 2019, by Bonny Nilsson |
JSS, Sitecore 9.1, Layout, Layout service, Code-first