Last updated Sep 24, 2024

Rendering extensions

This is a continuation of the work done in 1. Creating an extension point.

In this section you will learn how to:

  • Render extension using their attributes
  • Use handlers
  • Render complex extensions like modals and panels

You'll continue working on the tutorial page: http://localhost:7990/bitbucket/plugins/servlet/extension-points

Using attributes

On the previous guide, you defined a schema for an extension point and imported the helpers using the CSE schema-loader to fetch and validate the extensions.

You can call the useExtensions hook helper to retrieve a list of extension descriptors. An extension descriptor is nothing more than an object that contains the attributes of an extension like:

1
2
interface ExtensionAttribute {
    key: string;
    location: string;
    attributes: {
        // the attributes of an extension
    };
}

After you get the extension, you can make use of their attributes as you see fit. For example, if you want to render link and button extensions, you can do so as follows:

On ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx, write:

1
2
import React from 'react';

import { useExtensions } from './schema.cse.graphql';

const MyPage = () => {
    const extensions = useExtensions();

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'link':
                return (
                    <a href={attributes.url} key={key}>
                        {attributes.label}
                    </a>
                );
            case 'button':
                return (
                    <button type="button" onClick={attributes.onAction} key={key}>
                        {attributes.label}
                    </button>
                );

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/

Go to http://localhost:7990/bitbucket/plugins/servlet/extension-points, and you should see a link and a button extension rendered on the screen.

Using handlers

Rendering the same type of extensions can become repetitive, and even time consuming for more complex examples. That's why CSE provides a set of components called handlers, that will take the attributes and render them, also matching the style of Atlassian products automatically for you.

You could rewrite the previous code to use Link and Button handlers as follows:

On ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx, write:

1
2
import React from 'react';
import { LinkHandler, ButtonHandler } from '@atlassian/clientside-extensions-components';

import { useExtensions } from './schema.cse.graphql';

const MyPage = () => {
    const extensions = useExtensions();

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'link':
                return (
                    <LinkHandler href={attributes.url} key={key}>
                        {attributes.label}
                    </LinkHandler>
                );
            case 'button':
                return (
                    <ButtonHandler onAction={attributes.onAction} key={key}>
                        {attributes.label}
                    </ButtonHandler>
                );

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/

The handler code for links and buttons is similar, except that the button is rendered with the same styles as Atlassian products without extra effort.

To learn more about handlers, refer to the handlers reference guide.

Modals

Modal extensions allow developers to render a button that displays a modal with custom content when clicked.

The API for Modal extensions is quite complex to set up due to all its different options. In this scenario, you’ll need to use handlers.

First, modify your schema to accept extensions of type ModalExtension on /src/main/my-app/extensions/extension-points-tutorial/schema.cse.graphql:

1
2
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: SupportedExtensions!

    label: String

    url: String

    onAction: Function
}

union SupportedExtensions = LinkExtension | ButtonExtension | ModalExtension

Then, modify your extension point to render modals with the modal handler on ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx:

1
2
import React from 'react';
import { ModalWithActionHandler } from '@atlassian/clientside-extensions-components';

import { useExtensions } from './schema.cse.graphql';

const MyPage = () => {
    const extensions = useExtensions();

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            case 'modal':
                return (
                    <ModalWithActionHandler render={attributes.onAction} key={key}>
                        {attributes.label}
                    </ModalWithActionHandler>
                );

            /** other cases **/

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/

The provided modal handler will create a Modal API object and pass it to the extensions, and will also create an implementation of this API using an Atlaskit modal dialog.

If you refresh the page, you should see a button that opens a modal when clicked.

Panels

Panel extensions allow developers to render any HTML content inside a given container.

Even though the API for panels is simpler than modals, it can still be challenging to handle the rendering and clean-up cycles efficiently every time the extension point re-renders.

The panel handle takes care of this problem by creating an empty container and handling the rendering and cleanup cycles for you.

First, modify your schema to accept extensions of type PanelExtension on /src/main/my-app/extensions/extension-points-tutorial/schema.cse.graphql:

1
2
"""
---
extensionPoint: extension.points.tutorial
---
"""
type Schema {
    type: SupportedExtensions!

    label: String

    url: String

    onAction: Function
}

union SupportedExtensions = LinkExtension | ButtonExtension | ModalExtension | PanelExtension

Then, modify your extension point to render panels with the panel handler on ./src/main/my-app/extensions/extension-points-tutorial/extension-points-page.jsx:

1
2
import React from 'react';
import { PanelHandler } from '@atlassian/clientside-extensions-components';

import { useExtensions } from './schema.cse.graphql';

const MyPage = () => {
    const extensions = useExtensions();

    const renderExtension = (extension) => {
        const { key, attributes } = extension;
        switch (attributes.type) {
            /** other cases **/

            case 'panel':
                return <PanelHandler render={attributes.onAction} key={key} />;

            default:
                return null;
        }
    };

    return (
        <PageContainer>
            <h2>extension.points.tutorial</h2>
            {extensions.map(renderExtension)}
        </PageContainer>
    );
};

/** page declaration for guides only **/

You should now see an extension rendered with a title "Look!" and a paragraph that counts the times the panel has been re-rendered.

Recap and next steps

So far, you've learned:

  • How to use attributes to render extensions
  • How to use handlers to render extensions

Next, you're going to learn how to provide context to extensions.

Rate this page: