Skip to main content

Creating a layout template component

10 タスク

30 分

Platform
表示の対象:All users Applies to: Platform
上級
Customer Journeys
英語

シナリオ

Sweet Life stakeholders like the new NavigateToStep component and want to invest more in improving it. However, they have observed that it does not meet the corporate standards for customer journeys. These standards require the navigation link to be displayed next to the section in the review screen instead of under or above it. They also want a visual separator between each section with a NavigateToStep component.

The Lead System Architect (LSA) for the Sweet Life Pega Center of Excellence has determined that this is not configurable in App Studio without creating a new layout template. After confirming with the business stakeholders that the additional maintenance costs are acceptable, the creation of this new layout component can move forward.

The new layout component should resemble the existing layout templates to reduce the cognitive load for App Studio authors. The stakeholders do not want to specify a TaskID in the NavigateToStep component, which requires switching to Dev Studio. They prefer selecting only the Views for which they want to enable navigation and automatically include the navigation button on the right side of the read-only View.

Your LSA has provided you with the required design to get started:

  • The component must use a Layout Template type with a Details subtype.
  • The config.json is included, which enables the selection of Views in one region only of the property pane in App Studio.
  • The separator should conform to the styling of the Constellation design system theme as configured in App Studio and Dev Studio.

The following table provides the credentials you need to complete the challenge:

Role User name Password
Application Developer developer@SL pega123!

このチャレンジを完了するには、Pegaインスタンスを起動する必要があります。

起動には5分ほどかかることがありますので、しばらくお待ちください。

詳細なタスク

1 Create a new Details layout template

  1. Open the sldxcomponents folder that you created in Creating your first Field component.
  2. In Visual Studio, in a new terminal, enter npm run create.
  3. Using the arrow keys on your keyboard, navigate to the 2) Layout template line, and then press the Enter key.
  4. Using the arrow keys on your keyboard, navigate to the 1) DETAILS, and then press the Enter key.
  5. When prompted to enter the required component name, enter NavigateWrapper, and then press the Enter key.
  6. When prompted to enter the required component label, enter Navigate to step template, and then press the Enter key.   
    Your terminal screen should have similar values to the following figure: 
    Terminal view of the outcome of creating a new Details layout template.
  7. In your Visual Studio Code terminal, enter npm run startStorybook.
    The preview of the sample Details layout template provided by DX Component Builder is displayed, as shown in the following figure:
    Storybook with the example details layout template story.
  8. In Storybook, change the label in the Controls pane, and then turn the showHighlightedData and showLabel values on and off.
  9. When you finish exploring the story, close the web page, and then stop the terminal instance that runs the Storybook in Visual Studio Code.

2 Create your mock data

補足:  In this task, you use the browser dev tools. The following instructions are for Chrome. Microsoft Edge is similar to Chrome; Firefox and Safari have slightly different methods to get to the equivalent dev tools.

If you are having trouble accessing the Developer Tools Console in your browser, follow steps 1 and 2, and then step 10 onwards. 
  1. In the Pega Platform instance for the challenge, enter the following credentials:
    1. In the User name field, enter developer@sl.
    2. In the Password field, enter pega123!.
      The developer@sl operator uses Dev Studio as the default Portal. 
  2. In the header of Dev Studio, click Launch Portal > Customer Portal.
    Launch the Customer Portal in Dev Studio.
  3. In the navigation pane of the Customer Portal, click Create > Incident to create a new Incident Case. 
  4. In the Incident Type list, select Product faulty or unsafe
  5. In the Incident SubType list, select Injury caused by faulty product, and then click Next
  6. Add any valid data to the subsequent forms displayed to progress from the Create Stage to the Select Resolution Method Step. 
    注: Do not progress to the Review Stage yet.
  7. Right-click the browser window that has the Create Incident dialog box displayed, and then select Inspect to open the Developer Tools in Chrome.
  8. Click the Console tab.
  9. Enter PCore.getDebugger().enableXRay(), and then press the Enter key.
    Enabling XRay to capture some mock data.
  10. In the Create Incident dialog box, click Next to progress the case to the final Review Step.
    With the PCore debugger enabled, you can see additional information icons that provide useful information. For now, you only need to obtain data that you can use to update the mock.ts file and make the Storybook story more relevant to the use case.
    XRay View that shows additional debugging information.
  11. Click the i icon next to the View: ReviewInner label.
  12. Select everything in the Component metadata section, and then copy it to your clipboard 
  13. Open your Visual Studio Code project, and then access the src/components/Sl_DXExtensions_NavigateWrapper directory.
  14. Open the mocks.ts file.
  15. Navigate to the top of the file and add two new lines.
  16. In the first line, enter the following code: export const reviewInner =.
  17. Place your pointer after the equal sign, and then paste the text that you copied to the clipboard. 
    The first object in your mocks.ts file should look like the following code:
    export const reviewInner = {
      name: 'ReviewInner',
      type: 'View',
      config: {
        ruleClass: 'SL-TellUsMore-Work-Incident',
        template: 'Details',
        localeReference: '@LR SL-TELLUSMORE-WORK-INCIDENT!VIEW!REVIEWINNER',
        inheritedProps: [
          {
            prop: 'label',
            value: '@L Review Inner'
          },
          {
            prop: 'showLabel',
            value: true
          }
        ]
      },
      children: [
        {
          name: 'A',
          type: 'Region',
          children: [
            {
              type: 'reference',
              config: {
                name: 'DetermineCategory',
                inheritedProps: [
                  {
                    prop: 'label',
                    value: '@L Determine Category'
                  },
                  {
                    prop: 'showLabel',
                    value: true
                  }
                ],
                ruleClass: 'SL-TellUsMore-Work-Incident',
                type: 'view'
              }
            },
            {
              type: 'reference',
              config: {
                name: 'ContactInfo',
                inheritedProps: [
                  {
                    prop: 'label',
                    value: '@L Contact Info'
                  },
                  {
                    prop: 'showLabel',
                    value: true
                  }
                ],
                ruleClass: 'SL-TellUsMore-Work-Incident',
                type: 'view'
              }
            },
            {
              type: 'reference',
              config: {
                name: 'ServiceDetails',
                inheritedProps: [
                  {
                    prop: 'label',
                    value: '@L Service Details'
                  },
                  {
                    prop: 'showLabel',
                    value: true
                  }
                ],
                ruleClass: 'SL-TellUsMore-Work-Incident',
                type: 'view',
                visibility: '@W ServiceIncident_FLOW_1'
              }
            },
            {
              type: 'reference',
              config: {
                name: 'ProductDetials',
                inheritedProps: [
                  {
                    prop: 'label',
                    value: '@L Product Details'
                  },
                  {
                    prop: 'showLabel',
                    value: true
                  }
                ],
                ruleClass: 'SL-TellUsMore-Work-Incident',
                type: 'view',
                visibility: '@W ProductIncident_FLOW_1'
              }
            },
            {
              type: 'reference',
              config: {
                name: 'ResolutionMethod',
                inheritedProps: [
                  {
                    prop: 'label',
                    value: '@L Resolution method'
                  },
                  {
                    prop: 'showLabel',
                    value: true
                  }
                ],
                ruleClass: 'SL-TellUsMore-Work-Incident',
                type: 'view'
              }
            }
          ]
        }
      ],
      classID: 'SL-TellUsMore-Work-Incident',
      isDeclarativeTarget: false
    };
  18. Save the file.
  19. Close the browser window that has the debug console enabled.
補足: This data forms the basis of your mock data for your Storybook story. You still must modify demo.stories.tsx, which contains the Storybook story, to use this new mock data instead of the existing mock data (you progressively remove the existing mock data as you proceed).

3 Clean up the Storybook story and reference the new mock data

補足: Because your component renders only Views (not Fields and Views that the example Details template allows) in Region A of the template, you can clean up your Storybook story to refer to the Region A mock data.
  1. In Visual Studio Code, open the VS Code in your src/components/Sl_DXExtensions_NavigateWrapper component folder.
  2. Open the demo.stories.tsx file, and add the reviewInner object to the list of imported objects from './mock' module:
    import {
      reviewInner,
      pyReviewRaw,
      pyReviewResolved,
      regionChildrenResolved,
      operatorDetails,
      configProps
    } from './mock';
  3. Navigate to the following Region component definition: const Region = (props: any) => {.
  4. Replace the return function in the Region component with the following code: return <>This is a Region with {props.getPConnect().getChildren().length} children</> ;.
  5. Navigate to the return function near the end of the demo.stories.tsx file, and then replace it with the following code:
      return (
        <SlDxExtensionsNavigateWrapper {...props} {...args}>
          <Region
            {...{
              getPConnect: () => ({
                setInheritedProp: () => {},
                getChildren: () =>
                  reviewInner.children[0].children.map((child, index) => {
                    return {
                      getPConnect: () => ({
                        getRawMetadata: () => reviewInner.children[0].children[index],
                        getConfigProps: () => {},
                        resolveConfigProps: () => {
                          return {
                            hideLabel: true
                          };
                        },
                        getComponent: () =>
                          props.getPConnect().createComponent(reviewInner.children[0].children[index])
                      })
                    };
                  })
              })
            }}
          />
        </SlDxExtensionsNavigateWrapper>
      );
  6.  Save the file.
    補足: The getPConnect().getComponent() function is mode data that reuses the mocked props.getPConnect().createComponent() function.  Currently, this function only renders React components for the mock fields, so you must add in support for rendering the mock Views. 
  7. Navigate to the createComponent function, and then replace it with the following code:     
            createComponent: (config: any) => {
              // eslint-disable-next-line default-case
              switch (config.config.value) {
                case '@P .pyStatusWork':
                  return renderField(pyReviewResolved.highlightedData[0].config);
                case '@P .pyID':
                  return renderField(pyReviewResolved.highlightedData[1].config);
                case '@P .pxCreateDateTime':
                  return renderField(pyReviewResolved.highlightedData[2].config);
                case '@USER .pxCreateOperator':
                  return renderField(pyReviewResolved.highlightedData[3].config);
                case '@P .pySLADeadline':
                  return renderField(regionChildrenResolved[0]);
                case '@P .pySLAGoal':
                  return renderField(regionChildrenResolved[1]);
                case '@P .pySLAStartTime':
                  return renderField(regionChildrenResolved[2]);
                default:
                  return <>{config.config.name}</>;
              }
            },
  8. Save the demo-stories.tsx file.
  9. In Visual Studio Code, open a terminal, enter npm run startStorybook, and press they Enter key.
    You now see the highlightedData section and all of the View names from reviewInner concatenated under the highlightedData section, as shown in the following figure:
    Details template after new mock data added and Storybook story updated.
    補足: Now that you have the mock data for our Storybook story, you can begin to enhance the component code. You enhance the code iteratively by checking that the story still works. If not, then you fix the story and component code until it does. As a result, you can be more confident that your final component works in Pega Infinity as designed. 

4 Create the NavigateRender.tsx file to render View references

  1. Open your Visual Studio Code project, and then open the src/components/Sl_DXExtensions_NavigateWrapper directory.
  2. Create a new file in this directory called NavigateRender.tsx
  3. Paste the following starter code:
    import type { ReactElement } from 'react';

    interface NavigateRenderProps {
      children?: never;
      child: NonNullable<ReactElement>;
    }

    const NavigateRender = (props: NavigateRenderProps) => {
      const { child } = props;

      return <>{child}</>;
    };

    export default NavigateRender;
  4. Save NavigateRender.tsx
  5. Open index.tsx
  6. Locate the line:
import HighlightRender from './HighlightRender';
  1. Enter the following import code for the NavigateRender
import NavigateRender from './NavigateRender';
  1. In the return function, replace
<DetailsRender child={child} />

with

<NavigateRender child={child} />
  1. Save index.tsx
  2. In a terminal enter 
npm run startStorybook
  1. Your Storybook story will look like the following figure: 
    Storybook story rendering Region A with a message.
    You will now modify the NavigateRender.tsx file to iterate the children of the Region (as defined in the metadata and not to be confused with React children). 
  2. Replace the existing code in your NavigateRender component with the following code:
import type { ReactElement } from 'react';

interface NavigateRenderProps {
  children?: never;
  child: NonNullable<ReactElement>;
}
const NavigateRender = (props: NavigateRenderProps) => {
  const { child } = props;
  const regionPConn: typeof PConnect = child.props.getPConnect();
  const regionChildren =
    regionPConn
      .getChildren()
      .map((childRef, index: number) => (
        <div key={`child-${index + 1}`}>{childRef.getPConnect().getComponent()}</div>
      )) || [];
  return regionChildren ? <div>{regionChildren}</div> : <div>Error in Region</div>;
};
export default NavigateRender;
  1. Save NavigateRender.tsx

Storybook will automatically rebuild your project and show you the new Story. Your Storybook story screen in the browser should look like the following figure:

Storybook story view after children are iterated in component

You will expand what you display to add a navigation button next to each View. You must change the interface definition of your component and pass in the navigation data from the index.tsx file.   

  1. Open index.tsx 
  2. Add the following type definition above the function declaration of the component:
export type Step = {
 ID: string;
 actionID: string;
 allow_jump: boolean;
 visited_status: string;
 name: string;
};
補足: Navigation Step information is stored in the Case data and can be accessed using the getValue() PConnect api passing in the constant that identifies the required part of the Case data. In this scenario, the Step data is in caseInfo.navigation in the data container. Fetch this data from the array of the Step, and turn it into a Map that has the flow action ID as the key and the Step object as the value.
  1. After the line:
const propsToUse = { label, showLabel, ...getPConnect().getInheritedProps() };
  1. Enter the following code:
const steps: Step[] = getPConnect().getValue(PCore.getConstants().CASE_INFO.NAVIGATION)?.steps;
const stepMap = new Map<string, Step>();  
steps?.forEach((step: Step) => stepMap.set(step.actionID, step));
補足: stepMap must be added to the interface definition in the NavigateRender.tsx file and also the type definition needs to be imported from index.tsx file, and then stepMap added to the interface. 
  1. Open NavigateRender.tsx
  2. Replace the code above the NavigateRender declaration with the following code: 
    import type { ReactElement } from 'react';
    import { Text } from '@pega/cosmos-react-core';
    import type { Step } from './index';

    interface NavigateRenderProps {
      children?: never;
      child: NonNullable<ReactElement>;
      stepMap?: Map<string, Step>;
    }
  3. Replace the contents of NavigateRender function to add the lookup in the stepMap to get the ID needed to call the navigateToStep PConnect api with the following code:
      const { child, stepMap } = props;

      const regionPConn: typeof PConnect = child.props.getPConnect();

      return regionPConn ? (
        <div>
          {regionPConn.getChildren().map((childRef, index: number) => {
            const refPConn: typeof PConnect = childRef.getPConnect();
            const reference: ReactElement = refPConn.getComponent();

            const ID = stepMap?.get(refPConn.getConfigProps()?.name)?.ID;

            return (
              <div key={`child-${index + 1}`}>
                {reference}
                <Text>, TaskID:{ID}</Text>
              </div>
            );
          })}
        </div>
      ) : (
        <div>Error in Region</div>
      );
  4. Save NavigateRender.tsx and index.tsx
  5. Navigate to the Storybook tab in your browser and observe that the Story is now broken with the following error:
    PCore.getConstants is not a function

    For a Storybook story to still work, two things need to happen. The first is that you must update your Storybook mock data to include the navigation data array, and we also need to add mocks for the getValue() PConnect api and getConstants() PCore api

  6. Open the demo.stories.tsx file, and then above the line:
const Region = (props: any) => {
  1. Add the following code:
window.PCore.getConstants = () => {
  return {
    CASE_INFO: { NAVIGATION: 'navigation' }
  };
};

const navigationSteps = {
  steps: [
    { ID: 'AssignmentSF1', actionID: 'DetermineCategory' },
    { ID: 'AssignmentSF2', actionID: 'ProductDetials' },
    { ID: 'AssignmentSF3', actionID: 'ServiceDetails' },
    { ID: 'AssignmentSF5', actionID: 'ResolutionMethod' },
    { ID: 'AssignmentSF4', actionID: 'ContactInfo' }
  ]
};
  1. Replace the code that defines the story with the following code:  
    export const BaseSlDxExtensionsNavigateWrapper: Story = args => {
      const props = {
        getPConnect: () => {
          return {
            getValue: () => navigationSteps,
            getChildren: () => {
              return pyReviewRaw.children;
            },
            getRawMetadata: () => {
              return pyReviewRaw;
            },
            getContextName: () => {
              return 'app/primary_1';
            },
            getInheritedProps: () => {
              return pyReviewRaw.config.inheritedProps;
            },
            createComponent: config => {
              // eslint-disable-next-line default-case
              switch (config.config.value) {
                case '@P .pyStatusWork':
                  return renderField(pyReviewResolved.highlightedData[0].config);
                case '@P .pyID':
                  return renderField(pyReviewResolved.highlightedData[1].config);
                case '@P .pxCreateDateTime':
                  return renderField(pyReviewResolved.highlightedData[2].config);
                case '@USER .pxCreateOperator':
                  return renderField(pyReviewResolved.highlightedData[3].config);
                default:
                  return <>{config.config.name}</>;
              }
            },
            setInheritedProp: () => {
              /* nothing */
            },
            resolveConfigProps: config => {
              return config;
            }
          };
        }
      };

      return (
        <SlDxExtensionsNavigateWrapper {...props} {...args}>
          <Region
            {...{
              getPConnect: () => ({
                setInheritedProp: () => {},
                getChildren: () =>
                  reviewInner.children[0].children.map((child, index) => {
                    return {
                      getPConnect: () => ({
                        getRawMetadata: () => reviewInner.children[0].children[index],
                        getConfigProps: () => reviewInner.children[0].children[index]?.config,
                        getComponent: config =>
                          props.getPConnect().createComponent(reviewInner.children[0].children[index])
                      })
                    };
                  })
              })
            }}
          />
        </SlDxExtensionsNavigateWrapper>
      );
    };
  2. Save demo.stories.tsx. Your Storybook story is displayed in the browser, as shown in the following figure:
    Storybook story with empty Task IDs.

    The TaskIDs are blank because you are not passing the data through from the index.tsx file to the NavigateRender.tsx file.

  3. Add the stepMap prop to the NavigateRender component in index.tsx as shown in the following code: 
<NavigateRender child={child} stepMap={stepMap}/>.
  1. Save index.tsx file.
  2. Open Storybook and observe that the TaskIDs now populate your story.

5 Format and style your layout with Flex Containers and Flex Items

The current code in NavigationRender.tsx demonstrates that you have all the mock data set up correctly, but there is no layout. For layouts, you typically use the Flex and Grid components provided by the Constellation design system. You only need to use the Flex component configured as a container to display Flex items. 

Flex and Grid components are wrappers to CSS Flex and CSS Grid, so familiarity with these core web features helps you lay out your screens. The use of CSS Grid and CSS Flex is beyond the scope of this training. 

  1. Paste the following code into the NavigateRender.tsx file to replace the full content of the file:
    import type { ReactElement } from 'react';
    import { Flex, Text } from '@pega/cosmos-react-core';
    import type { Step } from './index';

    interface NavigateRenderProps {
      children?: never;
      child: NonNullable<ReactElement>;
      stepMap?: Map<string, Step>;
    }

    const NavigateRender = (props: NavigateRenderProps) => {
      const { child, stepMap } = props;

      const regionPConn: typeof PConnect = child.props.getPConnect();

      return regionPConn ? (
        <div>
          {regionPConn.getChildren().map((childRef, index: number) => {
            const refPConn: typeof PConnect = childRef.getPConnect();
            const reference: ReactElement = refPConn.getComponent();

            const ID = stepMap?.get(refPConn.getConfigProps()?.name)?.ID;

            return (
              <Flex key={`container-${index + 1}`} container={{ direction: 'row' }}>
                <Flex item={{ grow: 1 }}>
                   {reference}
                </Flex>
                <Flex style={{ 'margin-block': 'auto' }} item={{ grow: 0 }}>
                  {ID && <Text>Task ID: {ID}</Text>}
                </Flex>
              </Flex>
            );
          })}
        </div>
      ) : (
        <div>Error in Region</div>
      );
    };

    export default NavigateRender;
  2. Save the NavigateRender.tsx file.
    Your story should resemble the following figure:
    Storybook story showing Flex layout with Views on the left and task IDs on the right.

The UX design specifies that a separator needs to be between each of the Views in the Review Step. To do this, you reuse the existing StyledDetailsGridContainer and StyledHighlightedFieldsHrLine styled components with a few style enhancements so that it honors the App Studio theme.

  1.  Open the styles.ts file, and then replace the StyledDetailsGridContainer  and StyledHighlightedFieldsHrLine code with the following code:

    export const StyledDetailsGridContainer = styled.div(
      ({ theme }: { theme: typeof themeDefinition }) => {
        return css`
          margin-line-start: 1;
          padding-top: ${theme.base.spacing};
        `;
      }
    );

    export const StyledHighlightedFieldsHrLine = styled.hr(
      ({ theme }: { theme: typeof themeDefinition }) => css`
        border-top: unset;
        border-radius: unset;
        border-style: unset;
        border-bottom: 0.0625rem solid ${theme.base.palette['border-line']};
        margin-block-end: calc(1.5 * ${theme.base.spacing});
        padding-bottom: ${theme.base.spacing};
      `
    );
  2. Add the following code to the beginning of the styles.ts file:
    import { themeDefinition } from '@pega/cosmos-react-core';
  3. Save the styles.ts file.
  4. Open the NavigateRender.tsx file, and then add the following to the imports block to the beginning of your file:
    import React from 'react';
    import { StyledHighlightedFieldsHrLine } from './styles';
  5. Replace your return code block with the following code:
      return regionPConn ? (
        <div>
          {regionPConn.getChildren().map((childRef, index: number) => {
            const refPConn: typeof PConnect = childRef.getPConnect();
            const reference: ReactElement = refPConn.getComponent();

            const ID = stepMap?.get(refPConn.getConfigProps()?.name)?.ID;

            return (
              <React.Fragment key={`wrapper-${index + 1}`}>
                <Flex container={{ direction: 'row' }}>
                  <Flex item={{ grow: 1 }}>
                    {refPConn.getComponent()}
                  </Flex>
                  <Flex style={{ 'margin-block': 'auto' }} item={{ grow: 0 }}>
                    {ID && <Text>Task ID: {ID}</Text>}
                  </Flex>
                </Flex>
                <StyledHighlightedFieldsHrLine />
              </React.Fragment>
            );
          })}
        </div>
      ) : (
        <div>Error in Region</div>
      );
  6. Preview your Story in the browser. 
    You now see clean separators that are based on the App Studio theme, so the system reflects any changes that impact the styles in your component.
    The following screenshot shows the final layout and styles applied: 
    Storybook story with Flex layout and styles based on the applied App Studio theme.

6 Add the Button component and navigateToStep action

You are now closer to having a fully functioning template component. However, you still need to add the Button component you created in the Creating your first Field component challenge and an Icon component to display the pencil icon.

  1. Open the NavigateRender.tsx file, and then replace the {ID && <Text>Task ID: {ID}</Text>} code with  the following code:

      {ID && (
         <Button variant='link' onClick={() => alert(`Clicked ID: ${ID}`)}>
            <Icon name='pencil' />
              Edit
            </Button>
      )}
  2. Replace the import statements with the following code near the beginning of the NavigateRender.tsx file to add the import statements for Button and Icon from '@pega/cosmos-react-core' as well as importing and registering the Pencil icon:
    import React from 'react';
    import type { ReactElement } from 'react';
    import { Button, Flex, Icon, registerIcon } from '@pega/cosmos-react-core';
    import type { Step } from './index';
    import { StyledHighlightedFieldsHrLine } from './styles';
    import * as pencil from '@pega/cosmos-react-core/lib/components/Icon/icons/pencil.icon';

    registerIcon(pencil);
  3. Save the NavigateRender.tsx file.
    The component is now beginning to resemble the target component with the link variant of the Button component rendered with the Pencil icon. 
  4. Click Edit.  
    An alert is displayed with the TaskID of the item clicked. 
    Storybook story with message box that displays the task ID after clicking the link.

Now you must add the navigateToStep action instead of our alert. First, update your demo.stories.tsx file with the mocked out navigateToStep function. The navigateToStep function requires two parameters: the stepID and the ContainerItemID.  The ContainerItemID can be retrieved using the getContextName() PConnect api and the stepID is the ID from the navigation.steps array. 

  1. Open the demo.stories.tsx file, and then replace the existing return function code with the following code:
      return (
        <SlDxExtensionsNavigateWrapper {...props} {...args}>
          <Region
            {...{
              getPConnect: () => ({
                setInheritedProp: () => {},
                getValue: () => navigationSteps,
                getActionsApi: () => ({
                  navigateToStep: step => alert(`Clicked ${step}`)
                }),
                getChildren: () =>
                  reviewInner.children[0].children.map((child, index) => {
                    return {
                      getPConnect: () => ({
                        getContextName: () => {},
                        getLocalizedValue: val => val,
                        getRawMetadata: () => reviewInner.children[0].children[index],
                        getConfigProps: () => reviewInner.children[0].children[index]?.config,
                        getComponent: config =>
                          props.getPConnect().createComponent(reviewInner.children[0].children[index])
                      })
                    };
                  })
              })
            }}
          />
        </SlDxExtensionsNavigateWrapper>
      );
  2. Save the demo.stories.tsx file.
    The previous code now allows you to replace your alert in the NavigateRender.tsx file with the mocked function calls. 
  3. Open the NavigateRender.tsx file, and then replace the line containing the alert with the following code: 
    <Button variant='link' onClick={() => navigateToStep(ID, refPConn.getContextName())}>

Before we can use this code, we need to add some more code to define the navigateToStep function.  

  1. Under the line beginning with const regionPConn: typeof PConnect = child.props.getPConnect();, add the following code:
      const actionsAPI = regionPConn.getActionsApi();
      const navigateToStep = actionsAPI.navigateToStep.bind(actionsAPI);

    If you published this component as is and applied it to the ReviewInner View as a template, it will work. All the links are clickable, and you can navigate to the correct steps in the Multistep forms. However, there is another code change that you must perform to avoid having extra lines displayed for any View that has a visibility condition set that makes the view not visible at run time. This is a simple fix, and the following code needs to be added in order to do the visibility check. You do this early on in the loop so you can do an early return if the View is not visible. 

  2. Open the NavigateRender.tsx file, and then under the const reference: ReactElement = refPConn.getComponent(); line, add the following visibility check code:
    if (reference.props?.visibility === false) {
      return(<></>);
    }    

    Your complete code for the NavigateRender.tsx file should look like the following code:

    import type { ReactElement } from 'react';
    import { Button, Flex, Icon, registerIcon } from '@pega/cosmos-react-core';
    import type { Step } from './index';
    import { StyledHighlightedFieldsHrLine } from './styles';
    import * as pencil from '@pega/cosmos-react-core/lib/components/Icon/icons/pencil.icon';

    registerIcon(pencil);

    interface NavigateRenderProps {
      children?: never;
      child: NonNullable<ReactElement>;
      stepMap?: Map<string, Step>;
    }

    const NavigateRender = (props: NavigateRenderProps) => {
      const { child, stepMap } = props;

      const regionPConn: typeof PConnect = child.props.getPConnect();
      const actionsAPI = regionPConn.getActionsApi();
      const navigateToStep = actionsAPI.navigateToStep.bind(actionsAPI);

      return regionPConn ? (
        <div>
          {regionPConn.getChildren().map((childRef, index: number) => {
            const refPConn: typeof PConnect = childRef.getPConnect();

            const reference: ReactElement = refPConn.getComponent();

            if (reference.props?.visibility === false) {
              return;
            }

            const ID = stepMap?.get(refPConn.getConfigProps()?.name)?.ID;

            return (
              <React.Fragment key={`wrapper-${index + 1}`}>
                <Flex container={{ direction: 'row' }}>
                  <Flex item={{ grow: 1 }}>
                    {reference}
                  </Flex>
                  <Flex style={{ 'margin-block': 'auto' }} item={{ grow: 0 }}>
                    {ID && (
                      <Button
                        variant='link'
                        onClick={() => navigateToStep(ID, refPConn.getContextName())}
                      >
                        <Icon name='pencil' />
                        Edit
                      </Button>
                    )}
                  </Flex>
                </Flex>
                <StyledHighlightedFieldsHrLine />
              </React.Fragment>
            );
          })}
        </div>
      ) : (
        <div>Error in Region</div>
      );
    };

    export default NavigateRender;
  3. Save the NavigateRender.tsx file, and then look at the Storybook story in your browser. 
    The same display is intact; however, when you click Edit, the system runs the navigateToStep mock function.  

7 Update the component definition (config.json)

The template component is nearly complete. There are two additional changes to make: localization support and updating the component definition (config.json) to restrict what users can add to the template's region in App Studio.

  1. In Visual Studio Code, open the component source folder, and then open the config.json file.
  2. Replace the Region A property with the following code: 
    {
      "name": "A",
      "label": "Views to navigate to",
      "format": "CONTENTPICKER",
      "itemTypes": ["Views"]
    }
  1. Save the config.json file. 
    補足:  This component definition file is only used by App Studio, so the component needs to be published to check its functionality. You defer publishing until you make the final change for localization support. The previous configuration displays a CONTENTPICKER control in the property pane in App Studio when configuring this template. The CONTENTPICKER has been updated to only allow the selection of Views and also to remove the ability to create a field group. For this use case, these Views should belong to the Multi-Step Flow that you want to provide the review feature. With CONTENTPICKER, you can select multiple objects to render at run time. The system passes this metadata to your template when the template is rendered, and you can use it to display the components in any way you want. The default display for each CONTENTPICKER is a Region component. You overrode this default display in your code and render the selected Views by using a different layout.

    Your config.json file should resemble the following code:

    {
      "name": "Sl_DXExtensions_NavigateWrapper",
      "label": "Navigate to views",
      "description": "Navigate to views",
      "organization": "Sl",
      "version": "1.0.0",
      "library": "DXExtensions",
      "allowedApplications": [],
      "componentKey": "Sl_DXExtensions_NavigateWrapper",
      "type": "Template",
      "subtype": "DETAILS",
      "icon": "OneColumnDetails.svg",
      "properties": [
        {
          "name": "showHighlightedData",
          "label": "Enable highlighted fields",
          "format": "BOOLEAN"
        },
        {
          "name": "highlightedData",
          "label": "Highlighted fields",
          "format": "CONTENTPICKER",
          "addTypeList": [
            "Fields"
          ],
          "visibility": "$this.showHighlightedData = true",
          "saveToConfig": true
        },
        {
          "name": "A",
          "label": "Views to navigate to",
          "format": "CONTENTPICKER",
          "addTypeList": [
            "Views"
          ]
        }
      ]
    }

8 Add localization to your component

  1. Open the NavigateRender.tsx file.
    You might have noticed a static string, Edit,  in the return code block. You must localize this string.
  2. Update the line that has the word Edit with the following code: {refPConn.getLocalizedValue('Edit')}.

If you publish now, the component will be fully functional. However, it is not localizable and always displays the value Edit. To make the component fully localizable, you must create another file in the source folder. 

  1. Add a new file called localizations.json.
    Ensure that you spell it correctly. 
    補足: The localizations.json file consists of a set of key/value pairs with the key passed into the getLocalizedValue() PConnect api function. The Constellation orchestration layer then returns the value associated
  2. Copy and paste the following code into your localizations.json file: 
    {
      "fields": {
        "Edit": "Edit"
      }
    }  
  3. Save the localizations.json file.
補足: The system can now localize the component in App Studio. Ensure that you alway add any static strings to this file and reference by them using the getLocalizedValue() PConnect API, and then republish your component. The system merges the localizations.json file to the Rule-UI-Localization record for the Rule-UI-View recording that contains your component.

9 Clean up the example template code that is no longer required

  1. If Visual Studio Code is not already opened in your sldxcomponents project folder, start it, and then open the sldxcomponents project folder.
  2. In the src/components folder, open the Sl_DXExtensions_NavigateWrapper folder.
    Message that states VS Code view of the generated source files for the template component.
  3. Delete the following files:
    • OneColumnDetails.svg
    • DetailsRender.tsx
  4. Open the index.tsx file, and then delete the following import statement near the beginning of the file: import DetailsRender from './DetailsRender';.
  5. Save the index.tsx file.
  6. Oopen the demo.stories.tsx file, and then replace the code with the following code:
    import type { Meta, StoryObj } from '@storybook/react';
    import { Text, FieldValueList, DateTimeDisplay, useTheme } from '@pega/cosmos-react-core';
    import SlDxExtensionsNavigateWrapper from './index';
    import { reviewInner, pyReviewRaw, pyReviewResolved, operatorDetails, configProps } from './mock';
    import StatusWorkRenderer from './StatusWork';
    import Operator from './Operator';

    import type { LocaleUtils } from '@pega/pcore-pconnect-typedefs/locale/locale-utils';

    const meta: Meta<typeof SlDxExtensionsNavigateWrapper> = {
      title: 'SL/Navigate to step wrapper',
      component: SlDxExtensionsNavigateWrapper,
      parameters: {
        type: 'Details'
      }
    };

    export default meta;
    type Story = StoryObj<typeof SlDxExtensionsNavigateWrapper>;

    if (!window.PCore) {
      window.PCore = {} as typeof PCore;
    }

    window.PCore.getLocaleUtils = () => {
      return {
        getLocaleValue: (value: any) => {
          return value;
        }
      } as LocaleUtils;
    };

    window.PCore.getUserApi = () => {
      return {
        getOperatorDetails: () => {
          return Promise.resolve(operatorDetails);
        }
      };
    };
    window.PCore.getConstants = (): Readonly<any> => {
      return {
        CASE_INFO: { NAVIGATION: 'navigation' }
      };
    };

    const navigationSteps = {
      steps: [
        { ID: 'AssignmentSF1', actionID: 'DetermineCategory' },
        { ID: 'AssignmentSF2', actionID: 'ProductDetials' },
        { ID: 'AssignmentSF3', actionID: 'ServiceDetails' },
        { ID: 'AssignmentSF5', actionID: 'ResolutionMethod' },
        { ID: 'AssignmentSF4', actionID: 'ContactInfo' }
      ]
    };
    const Region = (props: any) => {
      return <>This is a Region with {props.getPConnect().getChildren().length} children</>;
    };

    const renderField = (resolvedProps: any) => {
      const {
        displayAsStatus = false,
        displayMode,
        value = '',
        label = '',
        key,
        // eslint-disable-next-line react-hooks/rules-of-hooks
        theme = useTheme()
      } = resolvedProps;

      const variant = displayMode === 'LABELS_LEFT' ? 'inline' : 'stacked';

      let val =
        value !== '' ? (
          <Text variant='h1' as='span' key={key}>
            {value}
          </Text>
        ) : (
          ''
        );

      if (label === 'Create date/time')
        val = (
          <DateTimeDisplay
            value={value}
            variant='datetime'
            format='long'
            clockFormat={undefined}
            key={key}
          />
        );

      if (displayAsStatus === true) val = <StatusWorkRenderer value={value} key={key} />;

      const [_label] =
        label === 'Create operator'
          ? [configProps.createLabel, configProps.createOperator, configProps.createDateTime]
          : label === 'Update operator'
            ? [configProps.updateLabel, configProps.updateOperator, configProps.updateDateTime]
            : [configProps.resolveLabel, configProps.resolveOperator, configProps.resolveDateTime];

      if (label === 'Create Operator')
        val = (
          <Operator
            label={configProps.hideLabel ? '' : _label}
            name={configProps.createOperator.userName}
            id={configProps.createOperator.userId}
            value={undefined}
            validatemessage=''
            hideLabel={false}
            readOnly={false}
            required={false}
            disabled={false}
            externalUser={undefined}
            metaObj={undefined}
            testId=''
            helperText=''
            getPConnect={(): typeof PConnect => {
              throw new Error('Function not implemented.');
            }}
          />
        );

      if (variant === 'inline') {
        val = value || (
          <span aria-hidden='true' key={key}>
            &ndash;&ndash;
          </span>
        );
      } else {
        val = (
          <Text variant='h1' as='span' key={key}>
            {val}
          </Text>
        );
      }
      return <FieldValueList variant={variant} fields={[{ name: label, value: val }]} key={key} />;
    };

    export const BaseSlDxExtensionsNavigateWrapper: Story = (args: any) => {
      const props = {
        getPConnect: () => {
          return {
            getValue: () => navigationSteps,
            getChildren: () => {
              return pyReviewRaw.children;
            },
            getRawMetadata: () => {
              return pyReviewRaw;
            },
            getContextName: () => {
              return 'app/primary_1';
            },
            getInheritedProps: () => {
              return pyReviewRaw.config.inheritedProps;
            },
            createComponent: (config: any) => {
              // eslint-disable-next-line default-case
              switch (config.config.value) {
                case '@P .pyStatusWork':
                  return renderField(pyReviewResolved.highlightedData[0].config);
                case '@P .pyID':
                  return renderField(pyReviewResolved.highlightedData[1].config);
                case '@P .pxCreateDateTime':
                  return renderField(pyReviewResolved.highlightedData[2].config);
                case '@USER .pxCreateOperator':
                  return renderField(pyReviewResolved.highlightedData[3].config);
                default:
                  return <>{config.config.name}</>;
              }
            },
            setInheritedProp: () => {
              /* nothing */
            },
            resolveConfigProps: (config: any) => config
          };
        }
      };

      return (
        <SlDxExtensionsNavigateWrapper {...props} {...args}>
          <Region
            {...{
              getPConnect: () => ({
                setInheritedProp: () => {},
                getValue: () => navigationSteps,
                getActionsApi: () => ({
                  navigateToStep: (step: any) => alert(`Clicked ${step}`)
                }),
                getChildren: () =>
                  reviewInner.children[0].children.map((child, index) => {
                    return {
                      getPConnect: () => ({
                        getContextName: () => {},
                        getLocalizedValue: (val: any) => val,
                        getRawMetadata: () => reviewInner.children[0].children[index],
                        getConfigProps: () => reviewInner.children[0].children[index]?.config,
                        getComponent: () =>
                          props.getPConnect().createComponent(reviewInner.children[0].children[index])
                      })
                    };
                  })
              })
            }}
          />
        </SlDxExtensionsNavigateWrapper>
      );
    };

    BaseSlDxExtensionsNavigateWrapper.args = {
      showLabel: true,
      label: 'Details template',
      showHighlightedData: true
    };
    補足:  Replacing the demo.stories.tsx file with the previous code does not change the overall functionality of the Storybook story, but it does remove some linting errors and also adds the story to the SL group in the Storybook navigator. 
  7. Save the demo.stories.tsx file.
  8. Switch to the Visual Studio Code terminal, and then end the startStorybook process.
  9. Close the Storybook browser tab.
  10. In Visual Studio Code, open a new terminal, and then enter npm run startStorybook.
  11. Observe the new structure of the Storybook story.
注: Your code must compile after deleting these files, because the new components should no longer reference them. If it does not, go back and confirm that you have followed the steps exactly.

10 Publish and configure your component

  1. Initialize your challenge system.
  2. Open Visual Studio Code, and then open your project.
  3. Open the tasks.config.json file, and then search for the "server" entry.
  4. Check that the entry for the challenge system Pega Infinity host is still correct.
  5. Optional: If the challenge system hostname changed, update the file with the new Pega Infinity hostname, and then save the tasks.config.json file.
  6. In Visual Studio Code, open a new terminal in your project folder.
  7. Enter npm run authenticate, and then enter the credentials:
    1. In the user name line, enter author@SL.
    2. In the password line, enter pega123!.
  8. After you authenticate, close the authentication tab, and then return to your terminal in Visual Studio Code.
  9. In your terminal, enter npm run publish, and then press the Enter key.
  10. Navigate to the Sl_DXExtensions_NavigateWrapper component, and then press the Enter key.
  11. When prompted for a ruleset name and ruleset version, press the Enter key to accept the defaults set up in earlier tasks. 
  12. In the Generate development build? prompt, enter y, and then press the Enter key.
  13. Accept the defaults for the rest of the questions by pressing the Enter key until there are no more prompts.  
    Your component is now published.
  14. In the header of Dev Studio, click Dev Studio > App Studio, and then open the Incident Case Type.
  15. Click the Review form Step in the Create Stage.
  16. In the Step pane, click Configure view.
  17. Click the ReviewInner View, and click Edit to change the template.
  18. Select the Navigate to step template, and then click the Submit.
  19. On the View editor, click Submit to return to the Case Life Cycle.
  20. In the header of App Studio, click Previewto launch the Customer Portal
    The new template that renders the View with navigation links is displayed.
    Template layout component rendered with clickable buttons that navigate to multi-step form steps.


このモジュールは、下記のミッションにも含まれています。

トレーニングを実施中に問題が発生した場合は、Pega Academy Support FAQsをご確認ください。

このコンテンツは役に立ちましたか?

改善できるところはありますか?

We'd prefer it if you saw us at our best.

Pega Academy has detected you are using a browser which may prevent you from experiencing the site as intended. To improve your experience, please update your browser.

Close Deprecation Notice