Parts

Contents

Enonic provides a highly customizable component type called parts. Similar to page components parts must be defined in both Enonic and Next in order to fully work.

Task: Add part to Enonic

We’ll start off by adding a simple component that lists child items in the tree structure.

  1. Add the child-list part to the Enonic app

    src/main/resources/site/parts/child-list/child-list.xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <part xmlns="urn:enonic:xp:model:1.0">
        <display-name>Child list</display-name>
        <description>Lists children of current item</description>
        <form>
            <input name="sorting" type="ComboBox">
                <label>Sort order</label>
                <occurrences minimum="0" maximum="1"/>
                <config>
                    <option value="displayName ASC">Ascending</option>
                    <option value="displayName DESC">Descending</option>
                </config>
                <default>displayName ASC</default>
            </input>
        </form>
    </part>
    This component also contains a form, similar to content types. This means editors can configure it - sorting Ascending or Descending.

    To work seamlessly with Content Studio’s WYSIWYG preview, in the Enonic app, parts need a special controller file that helps the preview reload the component when there are changes, without having to refreshing the entire page.

  2. Add the preview controller file for the child-list part

    This controller simply is triggered when a component is updated in the page editor, and simply forwards the request to the proxy - like all other requests.

    src/main/resources/site/parts/child-list/child-list.js
    var proxy = require('/lib/nextjs/proxy');
    
    exports.get = proxy.get;
  3. Build and deploy your Enonic app once more to make the new part accessible to from Content Studio.

  4. Add parts to the page

    Once the app has deployed, head over to Content Studio. This time, edit the Persons folder item. Create the page and drag’n drop the new part into the region (right-click → Insert, or drag-n-drop from the Insert panel` on the right).

    The part will appear without making any changes to Next, this is because the Enonic Adapter contains fallback part rendering, as long as NextJS is running in DEV mode.

    missing component

Task: Configure part rendering in Next

Heading over to the front-end code, we must register a new component that support actual rendering of this part.

  1. Add the part implementation to the Next app

    src/components/parts/ChildList.tsx
    import React from 'react'
    import {getUrl} from '@enonic/nextjs-adapter/UrlProcessor';
    import {Context} from '@enonic/nextjs-adapter';
    import {VariablesGetterResult} from '@enonic/nextjs-adapter/ComponentRegistry';
    import {PartProps} from '@enonic/nextjs-adapter/views/BasePart';
    
    const ChildList = (props: PartProps) => {
        const {data, meta} = props;
        const children = data.get.children;
        if (!children || children.length === 0) {
            return null;
        }
        return (
            <main style={{
                margin: `0 auto`,
                maxWidth: 960,
                padding: `0 1.0875rem`,
            }}>
                {
                    children &&
                    <ul>{
                        children.map((child: any, i: number) => (
                            <li key={i}>
                                <a href={getUrl(child._path, meta)}>
                                    {child.displayName}
                                </a>
                            </li>
                        ))
                    }</ul>
                }
            </main>
        );
    };
    
    export default ChildList;
    
    export const getChildList = {
        query: function (path: string, context?: Context, config?: any): string {
            return `query($path:ID!, $order:String){
                  guillotine {
                    getSite {
                      displayName
                    }
                    get(key:$path) {
                      displayName
                      children(sort: $order) {
                          _path(type: siteRelative)
                          _id
                          displayName
                      }
                    }
                  }
                }`
        },
        variables: function (path: string, context?: Context, config?: any): VariablesGetterResult {
            return {
                path,
                order: config?.sorting
            }
        }
    };
    
    export async function childListProcessor(common: any, context?: Context, config?: any): Promise<any> {
        common.modifiedBy = 'childListProcessor';
        return common;
    }
  2. Then, register the components in _mappings.ts:

    Update _mappings.ts with the following new lines:

    src/components/_mappings.ts
    import ChildList, {childListProcessor, getChildList} from './parts/ChildList';
    
    ...
    
    ComponentRegistry.addPart(`${APP_NAME}:child-list`, {
        query: getChildList,
        processor: childListProcessor,
        view: ChildList
    });

    As you may have noticed this component has a query, a view and a processor. Processors are optional JavaScript functions that can do whatever you need it to. For instance fetch data from another source than the CMS, or post-process the query response before it is passed to the view etc.

    The Enonic adapter will execute the query, pass the result via the processor, then to the view which then renders the component.

    child list rendered

    Try customizing the part configuration and see what happens.

Optional Task: The heading part

For more components to play with, you may add another part:

  1. Add files to the Enonic app

    This part definition includes a form, with an input field called heading. This makes it possible for editors to override the heading (aka displayName) coming from the content item.

    src/main/resources/site/parts/heading/heading.xml
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <part xmlns="urn:enonic:xp:model:1.0">
        <display-name>Heading</display-name>
        <description>Demo heading</description>
        <form>
            <input type="TextLine" name="heading">
                <label>Override heading</label>
            </input>
        </form>
    </part>
    src/main/resources/site/parts/heading/heading.js
    var proxy = require('/lib/nextjs/proxy');
    
    exports.get = proxy.get;
  2. Redeploy the Enonic app and add the part to your page

  3. Add and register the heading component in Next

    src/components/parts/Heading.tsx
    import React from 'react'
    import {APP_NAME} from '@enonic/nextjs-adapter';
    import {PartData} from '@enonic/nextjs-adapter/guillotine/getMetaData';
    
    // fully qualified XP part name:
    export const HEADING_PART_NAME = `${APP_NAME}:heading`;
    
    export interface HeadingData {
        part: PartData;
        common: any;
    }
    
    const HeadingView = ({part, common}: HeadingData) => (
        <h2>{part?.config?.heading || common?.get?.displayName}</h2>
    );
    
    export default HeadingView;

    Add the following lines to _mappings.ts:

    src/components/_mappings.ts
    import Heading from './parts/Heading';
    
    ...
    
    ComponentRegistry.addPart(`${APP_NAME}:heading`, {
        view: Heading
    });

    You should now have two configurable parts to play with.

In the next chapter we will make page composition even more interesting, with the introduction of layouts.


Contents