Skip to content
/ aem Public
forked from storybookjs/aem

Adobe Experience Manager Storybook app with events, knobs, docs, addons, and more

License

Notifications You must be signed in to change notification settings

nhirrle/aem

 
 

Repository files navigation

This project is no longer supported. If you are interested in continuing this project, please let us know by creating an issue here: https://github.com/storybookjs/aem/issues

Storybook AEM App Logo

Storybook Adobe Experience Manager (AEM) App

Table of Contents

About The project

This project has been created to provide native Storybook support for Adobe Experience Manager. It is a work in progress and has not been published yet. If you are interested in helping out or learning more about this project, you can join the discord channel here to see what we've been up to.

Libraries

  • @storybook/aem - an application that provides Storybook support for Adobe Experience Manager(AEM)
  • @storybook/aem-cli - a cli tool that helps build out your storybook stories and much more based on your AEM componentry and much more

Technologies

Getting Started

Installation

If your AEM project is using the suggested structure, you will want to first run npm init from your ui.apps directory. Running that command will create a package.json file that will allow you to include the necessary libraries.

To get started with your Storybook AEM instance, from the ui.apps directory, run npm install @storybook/aem --save-dev to pull down the proper Storybook library. If you would like further help setting up your Storybook configurations, you can install the Storybook AEM CLI Tool (optional) by running npm install @storybook/aem-cli. You can find more information about the cli tool here.

Usage

In the root directory of your ui.apps folder (or whereever youve chosen to include your package.json file, make sure to include a .storybook folder. You can follow this tutorial to help you set that up: https://www.learnstorybook.com/intro-to-storybook/react/en/get-started/. You can also see our example configuration here.

When creating a new HTL component, we suggest that you create a storybook configuration within the same directory as your code so that you can easily associate which HTL component goes along with which storybook file. To see an example of a story for an HTL List Component click here!

Like any other Storybook Framework app, Storybook AEM supports both CSF and MDX formats.

Story Configuration

As a part of the storybook configuration setup there are options you can use to customize your use case:

  • Template (required): HTL/HTML File Reference or Inline HTML
  • Content (optional): Mocked authored content that can be used in conjunction with knobs/controls
  • AEM Metadata: An assortment of metadata used to provide your component context such as:
    • Component dependencies: for nested components, you only need to provide a template but you must include require all nested component's xml files in order for them to render (They can also be defined at the story config level or in the preview using the aemMetadata decorator)
    • Decoration tags: tags/ classes that can be applied to the outside of your component as a wrapper and can be used to mock the java tag annotations({} or null)
    • Models (required): Used to render a component and can either be a proper use-class, a content object (model.json) or a resource path (string). When using the later, the respective content needs to be provided with the content object.
import Example from ('./example.html'); // HTL File or HTML File
export const Example = () => {
  return {
    template: MyText,
    content: {
      text: text('text', 'Hello, world.' ),
      isRichText: boolean('isRichText', false),
    },
    aemMetadata: {
      components: [
        require('../core/wcm/components/text/.content.xml'),
      ],
      decorationTag: {
        cssClasses: ['text','component'],
        tagName: 'article' // type of wrapper element
      }
      models: {
        'com.adobe.cq.wcm.core.components.models.Text': GenericModel
      },
    }
  };
};

AEM Metadata Decorator

The aem metadata decorator allows for the application of properties such as the decoration tag and the component includes to all of the stories (depending on where its used - in the preview or in the story config). Use the following syntax to apply the decorator:

Using the Decorator in the Preview.js File
import { aemMetadata } from '@storybook/aem';

addDecorator(aemMetadata({
  components: [
    require('../core/wcm/components/accordion/.content.xml'),
    require('../core/wcm/components/list/.content.xml'),
    require('../core/wcm/components/text/.content.xml'),
    require('../core/wcm/components/person/.content.xml'),
  ],
  decorationTag: {
    cssClasses: ['text','component'],
    tagName: 'article'
  },
  models: {
    'com.adobe.cq.wcm.core.components.models.Text': GenericModel
    'person': require('../models/person'),
  },
}));
Using the Decorator in the Story Config File
export default {
  title: 'Accordion',
  decorators: [
    aemMetadata({
      components: [
        require('../core/wcm/components/accordion/.content.xml'),
        require('../core/wcm/components/text/.content.xml'),
      ],
      decorationTag: {
        cssClasses: ['text','component'],
        tagName: 'article'
      }
    }),
  ]
};

Use Classes and Sling Models

In AEM, most HTL scripts bind java classes in the data-sly-use attribute which makes the business logic available to the scripts. Most often, those classes implement Sling Models which offer a simple annotation based way to define the resource properties that should be exported to the script. The Sling Models are also used to generated the *.model.json view of a resource.

For Example: Text

With the htlengine used in JavaScript, it is not possible to use the java classes directly. Further, since loading of the model modules is not trivial, they need to be registered in the story.

There are several ways to provide the required functionality to the javascript world.

1. GenericModel

Storybook provides a GenericModel that you can register as model. The GenericModel uses the underlying content and a heuristic to automatically export the properties.

The models needs to be registered in the story:

  import { GenericModel } from '@storybook/aem';

  models: {
    'com.adobe.cq.wcm.core.components.models.Text': GenericModel
  }
2. Javascript Use Classes

A more sophisticated way is to actually implement a use-class in javascript that can generate some of the dynamic, computed properties, similar to your java class. After the class is loaded, it is instantiated with the runtime global object passed as argument to the constructor.

A very simple example can be found here: com.adobe.cq.wcm.core.components.models.Text

The models needs to be registered in the story or in the preview using the aemMetadata Decorator:

For example:

    models: {
      'com.adobe.cq.wcm.core.components.models.Text': require('../../../../models/com.adobe.cq.wcm.core.components.models.Text'),
      'com.adobe.cq.wcm.core.components.models.Title': require('../../../../models/com.adobe.cq.wcm.core.components.models.Title')
    }

Note: In the future, there might be functionality to register multiple use-classes automatically.

3. Extending the GenericModel

In case the GenericModel doesn't satisfy all the needs for rapid prototyping, it can easily be extended. For example to provide some computed or more complex content that will eventually by provided by a java class in AEM.

For Example:

person.js

export default class Person extends GenericModel {
  get fullName() {
    return `${this.content.firstName} ${this.content.lastName}`;
  }
}

person.stories.js

    models: {
      'person': require('../models/person')
    },

person.html

<div data-sly-use.personModel="person">
    <dl>
        <dt>First Name:</dt><dd>${personModel.firstName}</dd>
        <dt>Last Name:</dt><dd>${personModel.lastName}</dd>
        <dt>Full Name:</dt><dd>${personModel.fullName}</dd>
    </dl>
</div>

Extending from other projects.

Extending from other projects is mostly straight forward, just import the components and models accordingly. A 3rd party project might choose to deliberately export the list of components and models, so just import their definition. see the examples/aem-core-components and how they imported in examples/aem-kitchen-sink.

However, if the 3rd party projects use template references, the HTL compiler can't resolve them. So they need to be specified during build time. This is currently only possible by using the AEMRegisterJcrRoot function. See aem-kitchen-sink/.storybook/main.js.

The AEMRegisterJcrRoot function can also be used, if your project's content root is not the project directory. See aem-core-components/.storybook/main.js.

Contributing

For more information about how to start contributing to this project, see our contributing file.

Check out our issues here: https://github.com/storybookjs/aem/issues

We especially need help with figuring out the proper way to support 3rd party libraries that are defined in the POM and use Java models such as the AEM Core components. If you have any ideas about how to solve this please comment on this issue: storybookjs#45

About

Adobe Experience Manager Storybook app with events, knobs, docs, addons, and more

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 60.8%
  • HTML 19.4%
  • TypeScript 15.8%
  • Less 3.4%
  • CSS 0.6%