Creating your first Jahia component

May 22, 2024

Understanding the solid template structure

This template has the following structure:

It is composed of a header of a footer, and in between a succession of sections. We will create a content element for each section, and for some of these, we will also create sub-content elements.

For the sake of simplicity and not to make this tutorial too long, the header and the footer will not be modified.

For each content section, you’ll now need to:

  • Identify the HTML block and its structure
  • Create a content type definition in the corresponding component definition file
  • Create a component view that will render the component content in HTML

Creating the content type definition

Let’s start with the hero section.

By looking at the section elements, we can see the elements we want to model, and we detail here what the above content type elements represent:

  • The solidTemplate: before the type name is a namespace and is used to scope the definitions to our template set and will be defined in the header of the content definition (CND) file.
  • The content type definition is composed of properties, themselves composed of:
    • a name
    • a type
    • optional parameters
    • default values
    • constraints

In this case, most of the properties will be of type string and can be internationalized (i18n), but we also use two of type weakreference to build links to pages (jnt:page). You can learn more about Jahia’s content type definitions.

Almost all the work that needs to be done is in the definitions.cnd.

Add the following content to the file:

[solidTemplate:heroSection] > jnt:content, solidTemplatemix:solidTemplateComponents
 - title (string) = 'Landing template for startups' i18n
 - paragraph (string) = 'Our landing page template works on all devices, so you only have to set it up once, and get beautiful results forever.' i18n 
 - button1Text (string) = 'Pre order now' i18n 
 - button1Link (weakreference) < jnt:page
 - button2Text (string) = 'Get in touch' i18n 
 - button2Link (weakreference) < jnt:page    

We have also added default values, making testing our component a little faster. You can find more information about Jahia's content type definition here.

Adding a static view

Now that we have a model, we can also build the related view, meaning the JSX component that will generate the HTML for this particular content element.

Now we have to create our view so let’s create the associated view.

First, let’s create a sections directory in $NPM_TEMPLATE/src/server/views and then create the file:

$NPM_TEMPLATE/ src / server / views / heroSection / HeroSectionDefault.jsx    

We add a Default suffix to the nodetype in the file name to indicate that this is the default view for rendering this nodetype. A single nodetype may have different views, and the default one is used when no name is specified for the view.

And add this code (which will be the similar base code of all our views):

import React from 'react';
import {defineJahiaComponent} from "@jahia/js-server-core";

export const HeroSectionDefault = () => {
    return (
        
    );
}

HeroSectionDefault.jahiaComponent = defineJahiaComponent({ // this object is used to register the view in Jahia
  nodeType: 'solidTemplate:heroSection', // The content node type the template applies to
    displayName: 'Hero Section', // The display name of the view
  componentType: 'view' // the component type is set to view (as opposed to template component types)
})

    

If we look at the HTML for the section in $NPM_TEMPLATE/src/server/templates/page/PageHome.jsx, in lines 50 to 124, this section looks like this:

        <section className="hero">
          <div className="container">
            <div className="hero-inner">
              <div className="hero-copy">
                <h1 className="hero-title mt-0">
                  Landing template for startups
                </h1>
                <p className="hero-paragraph">
                  Our landing page template works on all devices, so you only have to set it up once, and get beautiful results forever.
                </p>
                <div className="hero-cta">
                  <a
                    className="button button-primary"
                    href="#"
                  >
                    Pre order now
                  </a>
                  <a
                    className="button"
                    href="#"
                  >
                    Get in touch
                  </a>
                </div>
              </div>
              <div className="hero-figure anime-element">
                <svg
                  className="placeholder"
                  height="396"
                  viewBox="0 0 528 396"
                  width="528"
                >
                  <rect
                    height="396"
                    style={{
                      fill: 'transparent'
                    }}
                    width="528"
                  />
                </svg>
                <div
                  className="hero-figure-box hero-figure-box-01"
                  data-rotation="45deg"
                />
                <div
                  className="hero-figure-box hero-figure-box-02"
                  data-rotation="-45deg"
                />
                <div
                  className="hero-figure-box hero-figure-box-03"
                  data-rotation="0deg"
                />
                <div
                  className="hero-figure-box hero-figure-box-04"
                  data-rotation="-135deg"
                />
                <div className="hero-figure-box hero-figure-box-05" />
                <div className="hero-figure-box hero-figure-box-06" />
                <div className="hero-figure-box hero-figure-box-07" />
                <div
                  className="hero-figure-box hero-figure-box-08"
                  data-rotation="-22deg"
                />
                <div
                  className="hero-figure-box hero-figure-box-09"
                  data-rotation="-52deg"
                />
                <div
                  className="hero-figure-box hero-figure-box-10"
                  data-rotation="-50deg"
                />
              </div>
            </div>
          </div>
        </section>
    

Copy and paste the above HTML section into the new view file return parenthesis.

Injecting content inside the view

We will now need to position the content object values in the view. Jahia provides a helper function called getNodeProps, that makes it easy to retrieve multiple properties of a node in a single call. So we can retrieve all the properties of our node using something like this:

const props = getNodeProps(currentNode, ['title', 'paragraph', 'button1Text', 'button1Link', 'button2Text', 'button2Link']);    

We can then access the properties much simply by using their names, such as props.title, or props.button1Link

Of course, we need some setup to have all the proper imports and variables.

For that, add the following import statement at the top of your code:

import {useServerContext, buildUrl, getNodeProps} from '@jahia/js-server-core';    

And add the following at the beginning of the component function:

const {currentNode, renderContext, currentResource} = useServerContext();
const props = getNodeProps(currentNode, ['title', 'paragraph', 'button1Text', 'button1Link', 'button2Text', 'button2Link']);    

Your code should now look like this:

 

import React from 'react';
import {useServerContext, buildUrl, getNodeProps} from '@jahia/js-server-core';

export const HeroSection = () => {
    const {currentNode, renderContext, currentResource} = useServerContext();
    const props = getNodeProps(currentNode, ['title', 'paragraph', 'button1Text', 'button1Link', 'button2Text', 'button2Link']);    
    return (
        <section className="hero">
        <div className="container">
            <div className="hero-inner">
                <div className="hero-copy">
...
    

We will now modify the view by inserting all the properties defined in the content definition to look like this:

        <section className="hero">
            <div className="container">
                <div className="hero-inner">
                    <div className="hero-copy">
                        <h1 className="hero-title mt-0">{props.title}</h1>
                        <p className="hero-paragraph">{props.paragraph}</p>
                        <div className="hero-cta">
                            <a className="button button-primary" href={props.button1Link ? buildUrl({path:props.button1Link.getPath()}, renderContext, currentResource) : '#'}>{ props.button1Text }</a>
                            <a className="button" href={props.button2Link ? buildUrl({path:props.button2Link.getPath()}, renderContext, currentResource) : '#'}>{props.button2Text}</a>
                        </div>
                    </div>
                    <div className="hero-figure anime-element">
	              ...content removed for brevity but should be included...
                    </div>
                </div>
            </div>
        </section>
    

Note that for the page links, we had to do something a little more complex. As we need the path on the property, we must first ensure that it exists before generating the URL. This is done using a ternary function as in the following example:

<a className="button button-primary" href={props.button1Link ? buildUrl({path:props.button1Link.getPath()}) : '#'}>{ props.button1Text }</a>    

Exporting the view and cleaning up the template

We now need to export the view, so create a src/server/views/heroSection/index.js file with the following content:

export * from './HeroSectionDefault';    

And to export it to the root of the src folder we need to add to the src/server/views/index.js file with the following content:

export * from './heroSection';    

We can now get rid of the whole <section> for the heroSection in the src/server/templates/page/PageHome.jsx file by removing the following content

                    <section className="hero">
		... remove all children tags as well ...
                    </section>    

Be careful to only remove the first section tag and children, we still need the others for the next tutorials.

Test our updates

Since we are using the “watch” system, simply reloading the Jahia Page Composer browser window will be sufficient to load all the changes.

Now when reloading the page in the Jahia Page Composer you will see a screen that looks like this:

You can now click the New content button and add the Hero section by selecting it in the content type selection modal:

Select heroSection and click CREATE you will be shown the Content Editor screen for the heroSection definition that is prepopulated with the default values:

And if all went well, after click SAVE and the back arrow button you should see your changes reflected in the page as illustrated below:

Doesn’t this look great?

Congratulations, you have created your first content definition and its associated view and used the Jahia Page composer UI to create an instance of your new object.

Next step

Continue to creating the Feature section