Вы находитесь на странице справочной информации о версии:

Single page applications (SPAs) can offer compelling experiences for website users. Developers want to be able to build sites using SPA frameworks and authors want to seamlessly edit content within AEM for a site built using SPA frameworks.

The SPA authoring feature offers a comprehensive solution for supporting SPAs within AEM. This article presents a sample SPA application, explains how it is put together, and allows you to get up-and-running with your own SPA quickly.

Внимание.

The Single-Page Application (SPA) Editor feature was introduced with AEM 6.4 and is currently a technology preview. The SPA Editor will be delivered separately to the AEM quickstart in an upcoming service pack for AEM 6.3 and 6.4.

  • This feature is still in development and documentation is subject to change.
  • The SPA Editor is the recommended solution for projects that require SPA framework based client-side rendering (e.g. React).

Introduction

This article summarizes the basic functioning of a simple SPA and the minimum that you need to know to get yours running.

For detail on how SPAs work in AEM, see the following documents:

Примечание.

In order to be able to author content within an SPA, the content must be stored in AEM and be exposed by the content model.

An SPA developed outside of AEM will not be authorable if it does not respect the content model contract.

This document will walk through the structure of this example SPA and illustrate how it works so you can apply this information to your own SPA.

Dependencies, Configuration, and Building

In addition to the expected React dependency, the sample SPA can leverage additional libraries to make the creation of the SPA more efficient.

Dependencies

The package.json file defines the requirements of the overall SPA package and is listed here.

{
  "name": "react-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "start": "react-scripts start",
    "build": "webpack && clientlib --verbose"
  },
  "proxy": {
    "/content": {
      "target": "http://localhost:4502"
    }
  },
   "dependencies": {
    "@adobe/cq-react-editable-components": "^0.0.28",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-open-weather": "^0.3.0",
    "react-router-dom": "^4.2.2"
  },
}

Because this example is based on the React framework, there are two React-specific dependencies which are obligatory in the package.json file:

"react": "^16.2.0",    
"react-dom": "^16.2.0",

In addition, the aem-clientlib-generator is leveraged to make the creation of client libraries automatic as part of the build process.

"aem-clientlib-generator": "^1.4.1",

Although this is not obligatory, it is recommended to simplify the creation of your app. Further details about it can be found on GitHub here.

The aem-clientlib-generator is configured in the clientlib.config.js file as follows.

module.exports = {
    // default working directory (can be changed per 'cwd' in every asset option)
    context: __dirname,

    // path to the clientlib root folder (output)
    clientLibRoot: "./../content/jcr_root/apps/we-retail-journal/react/clientlibs",

    libs: {
        name: "we-retail-journal-react",
        allowProxy: true,
        categories: ["we-retail-journal-react"],
        serializationFormat: "xml",
        assets: {
            js: [
                "dist/**/*.js"
            ],
            css: [
                "dist/**/*.css"
            ]
        }
    }
};

Building

Actually building the app leverages Webpack for transpilation in addition to the aem-clientlib-generator for automatic client library creation. Therefore the build command will resemble:

"build": "webpack && clientlib --verbose"

Once built, the package can be uploaded to an AEM instance.

Sample Application Structure

Including the dependencies and building the sample SPA will leave you with a working SPA package which you can upload to your AEM instance.

The next section of this document will take you through how the SPA itself is structured, the important files which drive the application, and how they work together.

The image component is used as an example, but all components of the application are based on the same concept.

index.js

The entry point into the SPA is of course the index.js file shown here simplified to focus on the important content.

import ReactDOM from 'react-dom';
import App from './App';

...

ReactDOM.render(<App/>, document.getElementById('page'));

...

The main function of index.js is to leverage the ReactDOM.render function to determine where in the DOM to inject the application.

ReactDOM.render(<App/>, document.getElementById('page'));

This is a standard use of this function, not unique to this sample app.

App.js

By rendering the app, index.js calls App.js, which is shown here in a simplified version to focus on the important content.

import React, {Component} from 'react';
import Page from './components/Page';

...

class App extends Component {

    render() {
        return <Page/>
    }
}

export default App;

App.js primarially serves to wrap the root components that compose the app.

Page.js

By rendering the page, App.js calls Page.js listed here.

import React from 'react';
import { Container, withModel } from '@cq/cq-react-editable-components';

class Page extends Container {

    render() {
        let classNames = this.props.cq_model && this.props.cq_model.cssClassNames;

        return <div className={classNames}>{this.innerContent}</div>;
    }
}

export default withModel(Page);

The Page class extends Container, which contains the inner-content methods that can then be used.

The Container ingests the JSON representation of the page model and processes the content to wrap/decorate each element of the page. Further details on the Container can be found in the document SPA Blueprint.

let classNames = this.props.cq_model && this.props.cq_model.cssClassNames;

The props object is provided by the React Framework and available in all components, representing the API of that component.

The cq_model object is the result of wrapping the element and contains the model for the current component.

For more information about the withModel function see the section Example Use Cases within this document.

Image.js

With the page rendered, the components such as Image.js as shown here can be rendered.

import React, {Component} from 'react';
import { MapTo } from '@cq/cq-react-editable-components';

require('./Image.css');

/**
 * Default Edit configuration for the Image component that interact with the Core Image component and sub-types
 *
 * @type EditConfig
 */
const ImageEditConfig = {

    dragDropName: 'image',

    emptyLabel: 'Image',

    isEmpty: function() {
        return !this.props || !this.props.cq_model || !this.props.cq_model.src || this.props.cq_model.src.trim().length < 1;
    }
};

/**
 * Expected usage of the Image Component.
 *
 * <Image
 *      cq_model='the whole Object holding information for the image from the model.json response' />
 *
 * Please see the package.json for the proxy settings.
 */
class Image extends Component {

    hasLink() {
        return this.props && this.props.cq_model && this.props.cq_model.link;
    }

    get content() {
        let alt;
        let displayPopupTitle;
        let src;
        let title;
        let id;

        if (this.props.cq_model) {
            alt = this.props.cq_model.alt;
            displayPopupTitle = this.props.cq_model.displayPopupTitle; // TODO: it is missing in the JSON
            src = this.props.cq_model.src;
            title = this.props.cq_model.title;
            id = this.props.cq_model.path.substr(this.props.cq_model.path.lastIndexOf('/')+1);
        }

        return <img id={id} src={src} alt={alt} title={displayPopupTitle && title}/>
    }

    get linkedContent() {
        let alt;
        let fileReference;
        let link;
        let title;

        if (this.props.cq_model) {
            alt = this.props.cq_model.alt;
            fileReference = this.props.cq_model.fileReference; // TODO: it is missing in the JSON
            link = this.props.cq_model.link;
            title = this.props.cq_model.title;
        }

        return <a href={link} data-title={title || alt} data-asset={fileReference}>
                {this.content}
            </a>
    }

    render() {
        let innerContent;

        if (this.hasLink()) {
            innerContent = this.linkedContent;
        } else {
            innerContent = this.content
        }

        return (<div className="cmp-image">
                {innerContent}
            </div>);
    }
}

MapTo('weretail/components/content/image')(Image, ImageEditConfig);

The central idea of SPAs in AEM is the idea of mapping SPA components to AEM components and updating the component when the content is modified (and vice versa). See the document SPA Authoring Introduction for an overview of this communication model.

MapTo('weretail/components/content/image')(Image, ImageEditConfig);

The MapTo method maps the SPA component to the AEM component. It supports the use of a single string or an array of strings.

ImageEditConfig is a configuration object that contributes to enabling the authoring capabilities of a component by providing the necessary metadata for the editor to generate placeholders

If there is no content, labels are provided as placeholders to represent the empty content.

Example Use Cases

Rendering Part(s) of the Page (Static References)

If you pass in the path property, you can render specific parts of the page. This would be necessary if children of the page model need to be targeted such as when the content you wish to author is in between other content.

Your index.js will look like this:

import ReactDOM from 'react-dom';
import App from './App';
import { PageModelManager } from '@cq/cq-react-editable-components';

...

PageModelManager.init().then(function() {
    ReactDOM.render(<App/>, document.getElementById('page'));
});
...

Your App.js will look like this:

import React, {Component} from 'react';
import Page from './components/Page';
import Text from './components/Text';

...

class App extends Component {

    render() {
        return <Page cq_model_path="path/to/page1"/>
            <Text cq_model_path="path/to/text1"/>
    }
}

export default App;

Exporting Editable Component

You can export a component and keep it editable.

import React, { Component } from 'react';
import { MapTo } from '@cq/cq-react-editable-components';

...

const EditConfig = {...}

class PageClass extends Component {...};

...

export default MapTo('we-retail-journal/react/components/structure/page')(PageClass, EditConfig);

The MapTo function returns a Component which is the result of a composition that extends the provided PageClass with the class names and attributes that enable the authoring. This component can be exported to later be instantiated in the markup of your application.

When exported using the MapTo or withModel functions, the Page component, is wrapped with a ModelProvider component which provides standard components access to the latest version of the page model or a precise location in that page model.

For more information see the SPA Blueprint document.

Примечание.

By default you receive the entire model of the component when using the withModel function.

Эта работа лицензируется в соответствии с лицензией Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported  На посты, размещаемые в Twitter™ и Facebook, условия Creative Commons не распространяются.

Правовые уведомления   |   Политика конфиденциальности в сети Интернет