Content Inheritance
CrafterCMS support content inheritance out of the box, and supports it via a pluggable mechanism that allows developers to augment or override what’s out of the box.
Content Inheritance Basics
Content objects in CrafterCMS are essentially structured markup, XML by default, and house data authored via Crafter Studio by content authors. Content objects are typically organized into a tree structure, which naturally suits the notion of inheriting from a parent (not to say that the inheritance mechanics are limited to that topology). Inheritance works as follows:
Assume we have two objects, one called Parent and one called Child and they’re set up as follows:
Parent: Below you’ll see a typical level descriptor which will be the parent of another object. You’ll note the level descriptor defines multiple elements that are common to everything at this level in the hierarchy and below it. This level descriptor defines a primary CSS file main.css
, a common header component default-header.xml
and a common footer component default-footer.xml
.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <component>
3 <content-type>/component/level-descriptor</content-type>
4 <display-template/>
5 <merge-strategy>inherit-levels</merge-strategy>
6 <objectGroupId>4123</objectGroupId>
7 <objectId>41d1c0c5-bfc9-8fe8-2461-dc57a82b6cab</objectId>
8 <file-name>crafter-level-descriptor.level.xml</file-name>
9 <folder-name/>
10 <cssGroup>
11 <item>
12 <key>/static-assets/css/main.css</key>
13 <value>/static-assets/css/main.css</value>
14 <fileType_s>css</fileType_s>
15 </item>
16 </cssGroup>
17 <jsGroup/>
18 <createdDate>2/7/2021 19:40:03</createdDate>
19 <lastModifiedDate>10/8/2021 19:58:30</lastModifiedDate>
20 <defaultHeader>
21 <item>
22 <key>/site/components/components/header/default-header.xml</key>
23 <value>Default Header</value>
24 <include>/site/components/components/header/default-header.xml</include>
25 <disableFlattening>false</disableFlattening>
26 </item>
27 </defaultHeader>
28 <defaultFooter>
29 <item>
30 <key>/site/components/components/footer/default-footer.xml</key>
31 <value>Default Footer</value>
32 <include>/site/components/components/footer/default-footer.xml</include>
33 <disableFlattening>false</disableFlattening>
34 </item>
35 </defaultFooter>
36 <lastModifiedDate_dt>10/8/2021 19:58:30</lastModifiedDate_dt>
37 </component>
Child: Below is the XML file of a page residing under the above level descriptor and is setup to inherit from it. You’ll note the definition of the merge-strategy
as inherit-levels
, this invokes the level-based inheritance mechanics that require CrafterCMS to look at current and higher levels for files named crafter-level-descriptor.level.xml
(this is configurable). You’ll also note that this page doesn’t specify the CSS file/group of files to include, nor will it need to specify the header nor footer components.
1 <?xml version="1.0" encoding="UTF-8"?>
2 <page>
3 <content-type>/page/one-col-parallax</content-type>
4 <display-template>/templates/web/pages/one-col-parallax.ftl</display-template>
5 <merge-strategy>inherit-levels</merge-strategy>
6 <objectGroupId>9cef</objectGroupId>
7 <objectId>001f0955-6da3-8b7a-4e6b-6b373139d0ba</objectId>
8 <file-name>index.xml</file-name>
9 <folder-name>child-page</folder-name>
10 <internal-name>Child</internal-name>
11 <navLabel>CHILD</navLabel>
12 <title>Child Page</title>
13 <headerOverlap>no-overlap</headerOverlap>
14 <placeInNav>true</placeInNav>
15 <orderDefault_f>12000</orderDefault_f>
16 <description>This is the Child page.</description>
17 <disabled>false</disabled>
18 <createdDate>7/31/2021 16:52:39</createdDate>
19 <lastModifiedDate>8/1/2021 18:55:09</lastModifiedDate>
20 <body>
21 <h1>Hello World</h1>
22 </body>
23 </page>
CrafterCMS will invoke the inheritance mechanics implemented in the merge strategy inherit-levels
to merge the page and the level descriptor and the merge strategy will pull in the elements defined in the level descriptor into the child page before handing the new model (XML) to the rendering system. This means that when the page renders, the model will automatically contain the meta-data defined in the parent level descriptor. In our example above, the page will automatically inherit the fields cssGroup
, defaultHeader
, and defaultFooter
.
Note
When an element is defined by the level descriptor and then subsequently defined by a child, the child’s definition overrides the level descriptor.
This mechanism allows you to define data that flows down the information architecture of the site such that an entire site can have defaults, and those defaults can be overwritten by sections individual page. Some examples of real-life use of inheritance:
Site logo
Global stylesheet and JS includes
Global headers and footers
Section meta-data (flows to all pages/subsections)
Note
The
inherit-levels
mechanism allows you to set level descriptors at various levels of the information architecture with lower levels overriding upper levels.
What we discussed thus far is a single inheritance strategy implementation, inherit-levels
, the code to which is available here: InheritLevelsMergeStrategy.java. There are more inheritance strategies implemented out of the box with CrafterCMS and you can build your own to suit your needs.
Out of the Box Strategies
Strategy
|
Description
|
---|---|
single-file |
No content should be inherited.
|
inherit-levels |
Content from Crafter level descriptors (crafter-level-descriptor.xml)
in the current and upper levels should be inherited.
|
explicit-parent |
The parent descriptor to inherit is specified explicitly in the XML
tag
parent-descriptor . |
targeted-content |
The page will be merged with other pages in a targeted content
hierarchy, including level descriptors. For example,
/en_US/about-us will generate the following merging list:/en_US/about-us/index.xml ,/en_US/about-us/crafter-level-descriptor.xml ,/en/about-us/index.xml ,/en/about-us/crafter-level-descriptor.xml ,/about-us/index.xml , /about-us/crafter-level-descriptor.xml ,/crafter-level-descriptor.xml . |
Example of Out of the Box Strategy “inherit-levels”
Let’s take a look at an example of the out of the box strategy inherit-levels
used in the Website Editorial blueprint.
Let’s begin by looking at the home page of a site created using the Website Editorial blueprint. Take note of the top (header) and left (left-rail) side of the page.
Click on one of the category, say Entertainment
and again take note of the top (header) and left (left-rail) side of the page.
The top part of the page is the header and the left side is the left-rail. As shown above, the two pages we previewed uses the same information for the header, while the left-rail uses the same information on the top part of it, but different information on the bottom part.
The Website Editorial blueprint uses a Section Defaults
component content type (the level descriptor) to provide inherited values to all children and sibling content items, which for the example we are working on, is the header and the left-rail. Below is the Section Defaults content type, showing us the content type as /component/level-descriptor
with the merge strategy inherit-levels
used, and two components, the header and the left-rail:
In the site explorer screens below, we have two section defaults (crafter-level-descriptor.level.xml
) used, one residing under the Home folder, and another residing under articles folder.
As you preview the pages under Home, (Style, Health, Techonology, Entertainment, Search Results) you’ll notice that the header and left-rail displayed is the same for all the pages. Once you preview pages, under articles, we can see the left-rail displayed is now different. This shows us how the Section Defaults under articles has overridden the Section Defaults under Home.
Here’s the Section Defaults under Home
1<component>
2 <content-type>/component/level-descriptor</content-type> <display-template/>
3 <merge-strategy>inherit-levels</merge-strategy>
4 <placeInNav>false</placeInNav>
5 <file-name>crafter-level-descriptor.level.xml</file-name>
6 <objectGroupId>0a68</objectGroupId>
7 <objectId>0a68e8ad-77d8-0a58-e7bf-09a71fb3077b</objectId>
8 <folder-name/>
9 <header_o> <item> <key>/site/components/headers/header.xml</key>
10 <value>Header</value>
11 <include>/site/components/headers/header.xml</include>
12 <disableFlattening>false</disableFlattening>
13 </item></header_o>
14 <createdDate>2021-3-13T20:26:50.000Z</createdDate>
15 <createdDate_dt>2021-3-13T20:26:50.000Z</createdDate_dt>
16 <lastModifiedDate>2021-5-18T15:38:58.000Z</lastModifiedDate>
17 <lastModifiedDate_dt>2021-5-18T15:38:58.000Z</lastModifiedDate_dt>
18 <left-rail_o> <item> <key>/site/components/left-rails/left-rail-with-no-articles.xml</key>
19 <value>Left Rail with No Articles</value>
20 <include>/site/components/left-rails/left-rail-with-no-articles.xml</include>
21 <disableFlattening>false</disableFlattening>
22 </item></left-rail_o>
23</component>
Here’s the Section Defaults under articles
1<component>
2 <content-type>/component/level-descriptor</content-type> <display-template/>
3 <merge-strategy>inherit-levels</merge-strategy>
4 <objectGroupId>d210</objectGroupId>
5 <objectId>d210349e-3f77-95c1-37b3-cab10816347f</objectId>
6 <file-name>crafter-level-descriptor.level.xml</file-name>
7 <folder-name/>
8 <header/>
9 <left-rail_o> <item> <key>/site/components/left-rails/left-rail-with-related-articles.xml</key>
10 <value>Left Rail with Related Articles</value>
11 <include>/site/components/left-rails/left-rail-with-related-articles.xml</include>
12 <disableFlattening>false</disableFlattening>
13 </item></left-rail_o>
14 <createdDate>2021-3-17T18:56:59.000Z</createdDate>
15 <createdDate_dt>2021-3-17T18:56:59.000Z</createdDate_dt>
16 <lastModifiedDate>2021-5-18T15:38:1.000Z</lastModifiedDate>
17 <lastModifiedDate_dt>2021-5-18T15:38:1.000Z</lastModifiedDate_dt>
18</component>
As we can see from above, the left-rail component used for the Section Defaults under Home is different compared to the left-rail component used for the Section Defaults under articles.
If a new article page is created under articles/2019/10/27
, it will inherit the Section Defaults under articles.
Create Your Own Level Descriptor
Let’s take a look at how to add another level descriptor to the Website Editorial blueprint.
Create a new content type with name such as custom-level-descriptor
:
Add file-name
with default value crafter-level-descriptor.level
and check Readonly
option. You can also add more fields to the content type as needed. In this example, a Custom Headline text is added.
Go to Site Explorer, create a new folder articles2
under Home
, then copy some articles to this new location:
Create a new content with type /component/custom-level-descriptor
under articles2
:
Update article.ftl
template to include a new variable:
1<#if contentModel.customHeadline_s??>
2 <h1>${contentModel.customHeadline_s}</h1>
3</#if>
Click to preview article from article2
folder, confirm that new variable from the level descriptor has been included: