現在表示中:

単一ページアプリケーション(SPA)により、Web サイトのユーザーに魅力的なエクスペリエンスを提供することができます。開発者は SPA フレームワークを使用してサイトを構築したいと考え、作成者はそうして構築されたサイトのコンテンツを AEM 内でシームレスに編集したいと考えています。

SPA オーサリング機能には、AEM 内で SPA をサポートするための包括的なソリューションが用意されています。この記事では、サンプルの SPA アプリケーションを紹介し、その設定方法を説明するほか、独自の SPA の運用を速やかに開始する方法についても説明します。

警告:

単一ページアプリケーション(SPA)エディターの機能は AEM 6.4 で導入され、現在は技術プレビューがおこなわれています。SPA エディターは、まもなくリリースされる AEM 6.3 および 6.4 用のサービスパックで AEM クイックスタートに個別に提供されます。

  • この機能はまだ開発段階なので、ドキュメントの内容は変わる可能性があります。
  • SPA エディターは、SPA フレームワークを基にしたクライアント側レンダリング(React など)が必要なプロジェクトで有効なソリューションです。

概要

この記事では、シンプルな SPA の基本的な機能と、SPA を運用するための最低条件の概要を説明します。

AEM での SPA の動作について詳しくは、次のドキュメントを参照してください。

注意:

SPA 内のコンテンツを作成するには、コンテンツを AEM に格納し、コンテンツモデルによって公開する必要があります。

AEM 以外で開発された SPA については、コンテンツモデルのコントラクトに準拠していない場合、オーサリングをおこなうことはできません。

このドキュメントでは、このサンプルの SPA の構造と仕組みを説明します。お客様の SPA にも応用できる内容になっています。

依存関係、設定、ビルド

サンプルの SPA では、必要な React の依存関係以外に、追加のライブラリも利用して SPA の作成を効率化できます。

依存関係

package.json ファイルでは、SPA パッケージ全体の要件を定義します。以下のようになります。

{
  "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"
  },
}

このサンプルは React フレームワークに基づいているので、package.json ファイルには必須となる React 固有の依存関係が 2 つあります。

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

また、aem-clientlib-generator を利用して、クライアントライブラリの作成をビルドプロセスの一部として自動化しています。

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

これは必須ではありませんが、アプリの作成を簡略化できるのでお勧めです。詳しくは、GitHub のこちらのページを参照してください。

aem-clientlib-generator は、clientlib.config.js ファイルで次のように設定されています。

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"
            ]
        }
    }
};

ビルド

アプリの実際のビルドでは、クライアントライブラリの自動作成用の aem-clientlib-generator 以外に、トランスパイル用に Webpack も利用します。そのため、build コマンドは以下のようになります。

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

ビルドが完了したら、パッケージを AEM インスタンスにアップロードできます。

サンプルのアプリケーションの構造

依存関係を追加してサンプルの SPA をビルドすると、AEM インスタンスにアップロードできる SPA パッケージが作成されます。

このドキュメントの次の節では、SPA 自体の構造と、アプリケーションの動作にかかわる重要なファイルのほか、それらのファイルがどのように連携するのかについて説明します。

画像コンポーネントを例として使用していますが、このアプリケーションのコンポーネントはすべて同じ概念に基づいています。

index.js

SPA のエントリポイントはもちろん index.js ファイルです。このファイルの内容を以下に示していますが、重要な部分のみに焦点を当てるために簡略化されています。

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

...

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

...

index.js の主要機能は、ReactDOM.render 関数を使用して、DOM 内でアプリケーションをインジェクトする場所を決めることです。

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

これはこの関数の標準的な使用方法です。このサンプルアプリ独自の使用方法ではありません。

App.js

アプリをレンダリングすることで、index.jsApp.js を呼び出します。このファイルの内容を以下に示していますが、重要な部分のみに焦点を当てるために簡略化されています。

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

...

class App extends Component {

    render() {
        return <Page/>
    }
}

export default App;

App.js の主な役目は、アプリを構成するルートコンポーネントをラップすることです。

Page.js

ページをレンダリングすることで、App.js は以下の Page.js を呼び出します。

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);

Page クラスは、以降で使用できるインナーコンテンツメソッドを含む Container を拡張します。

Container は、ページモデルの JSON 表現を取り込み、コンテンツを処理してページの各要素をラップ/デコレートします。Container のその他の詳細については、SPA ブループリントのドキュメントを参照してください。

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

props オブジェクトは、React フレームワークによって提供されます。すべてのコンポーネントで利用することが可能で、そのコンポーネントの API を表します。

cq_model オブジェクトは、要素のラッピングの結果で、現在のコンポーネントのモデルを含みます。

withModel 関数について詳しくは、このドキュメントの使用例の節を参照してください。

Image.js

ページがレンダリングされると、以下の Image.js などのコンポーネントがレンダリング可能になります。

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);

AEM の SPA の中核概念は、SPA コンポーネントを AEM コンポーネントにマッピングし、コンテンツが変更されたときにコンポーネントも更新する(またはその逆も含む)というものです。この通信モデルの概要については、SPA オーサリングの概要のドキュメントを参照してください。

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

MapTo メソッドは、SPA コンポーネントを AEM コンポーネントにマッピングします。単一の文字列または文字列の配列の使用に対応しています。

ImageEditConfig は、エディターがプレースホルダーを生成するために必要なメタデータを提供することで、コンポーネントのオーサリング機能の有効化に関与する設定オブジェクトです。

コンテンツがない場合は、空のコンテンツを表すラベルがプレースホルダーとして提供されます。

使用例

ページの部分レンダリング(静的参照)

path プロパティを渡すと、ページの特定の部分をレンダリングできます。このようなレンダリングは、作成するコンテンツが他のコンテンツの中間に位置しているケースなどで、ページモデルの子をレンダリング対象にしたい場合に必要になります。

index.js は次のようになります。

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'));
});
...

App.js は次のようになります。

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;

編集可能コンポーネントのエクスポート

コンポーネントをエクスポートして編集可能な状態を維持することができます。

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);

MapTo 関数は、指定された PageClass を、オーサリングを有効にするクラスの名前と属性で拡張する構成の結果である Component を返します。このコンポーネントは後でエクスポートし、アプリケーションのマークアップでインスタンス化できます。

MapTo または withModel 関数を使用してエクスポートされた Page コンポーネントは、ModelProvider コンポーネントによってラップされます。この ModelProvider コンポーネントは、標準コンポーネントに、ページモデルの最新バージョンに対するアクセス権またはそのページモデル内の詳細な位置を提供します。

詳しくは、SPA ブループリントのドキュメントを参照してください。

注意:

デフォルトでは、withModel 関数を使用するとコンポーネントのモデル全体を受け取ります。

本作品は Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License によってライセンス許可を受けています。  Twitter™ および Facebook の投稿には、Creative Commons の規約内容は適用されません。

法律上の注意   |   プライバシーポリシー