Channel Manager SPA API - Enterprise Java Content management system - Hippo CMS

Channel Manager SPA API

This BloomReach Experience Manager feature requires a standard or premium license. Please contact BloomReach for more information.

Introduction

Integrating your SPA with the Channel Manager enables dynamic management of parts of your SPA. If you integrate your SPA with the Channel Manager, CMS webmasters can customize, add, move, and remove components of your SPA, just like they can (and may be used to) for non-SPA pages. Such manageable components must be known to HST, and HST exposes them through the Page Model API to the SPA, which must be aware of the concept of components, as well as of the specific set of components in use by / available to the SPA.

Channel Manager - SPA integration is optional. While it is possible to only integrate your SPA with BloomReach Experience Manager's Page Model API, you then miss the ability to let CMS users manage the components exposed by the Page Model API.

Conceptually, the integration between your SPA and the Channel Manager is bi-directional: If a CMS user changes parts of the Page Model, the SPA must be notified, and if the visitor of the (preview) page, displayed in the Channel Manager, changes the state/route of the SPA, the Channel Manager must be notified. In both scenarios, the goal is that your SPA and the Channel Manager stay in sync.

API

The integration API between your SPA and the Channel Manager consists of two parts, described in separate sub-sections:

  1. A JavaScript part
  2. A DOM part

Both parts are implemented in the Demo Project, which you can use as a reference. Specifically, you may want to check out the page.js file, and the utilities it engages.

Javascript

The Javascript part of the API covers both directions of the Channel Manager - SPA integration. For this part of the API, the Channel Manager requires the SPA to have constructed the following, page-global object:

window.SPA = {
  init: (cms) => {
    // Remember the cms object for subsequent callbacks
  },
  renderComponent: (id, propertiesMap) => {
    // Implement logic for re-rendering the component with the provided ID,
    // given a map of component properties
  }
};

By means of this SPA object, your SPA declares to the Channel Manager how the Channel Manager can communicate with your SPA. The Channel Manager listens to the load event of the preview page, then checks if the new page defines the SPA object, thereby declaring that it is an "SPA page". If the SPA object is found, the Channel Manager immediately calls the init function to register a cms object, through which the SPA can call back the Channel Manager. The Channel Manager does not expect any return value from the init function. The structure of the cms object is described below. First, let's discuss the renderComponent function.

If the Channel Manager has detected that the current preview page is a "SPA page", most component related operations (i.e. add, move, delete) trigger a clean reload of the page in order to bring your SPA in sync with the changed Page Model. For the case of editing the properties of a component, this wouldn't work, because the Channel Manager hasn't yet persisted the changes to the component properties to the back-end, and the Page Model wouldn't be able to provide these changes to the SPA in case of a reload. In order to facilitate showing the effect of the changed properties to the CMS user, the SPA object must therefore implement the renderComponent function. When a CMS user is editing the properties of a component, the Channel Manager calls this function, providing the SPA with the ID of the component and a map of properties, which should be used to re-render said component. The SPA must take these properties and forward them to the Page Model API, requesting an updated model for just that component, and then update its internal state and DOM accordingly. The renderComponent function may return false to trigger the normal component rendering logic (which replaces the DOM of the component). Any other return value (we recommend true by default) instructs the Channel Manager to take no further action with respect to re-rendering the component. Re-rendering a component may result in a need for the Channel Manager to sync its component overlay to the updated page. The next paragraph describes how this works.

During page initialization, the Channel Manager registers a cms object with the SPA, by calling the init function. The cms object exposes the following functions:

{
  createOverlay: () => {
    // Call this function when the set / ordering of components
    // on the page has changed
  },
  syncOverlay: () => {
    // Call this function when the dimensions of one or more components
    // on the page have changed without a DOM change
  }
}

The createOverlay and syncOverlay functions must be called by the SPA to trigger the corresponding Channel Manager-internal actions. Both functions are simple trigger functions for the Channel Manager, taking no arguments, and neither of them produces a meaningful return value. When triggered, the Channel Manager analyses the DOM to take further action. See the DOM section below for more details.

The SPA must call the createOverlay function when the set or order of components currently displayed has changed. Most prominently, this is the case when the page is fully drawn initially, based on the retrieved Page Model. Also, if the visitor of the preview page navigates to a different state / route of the SPA, which displays a different set (or order) of components, the createOverlay trigger must be called as well.

The SPA must call the syncOverlay function when the dimensions of one or more components currently displayed on the page have changed. Most prominently, this is the case when (the chain of events triggered by) a renderComponent call from the Channel Manager (i.e. the editing of component properties) has completed in the SPA. The Channel Manager then makes sure that its component overlay matches the components currently displayed on the page.

The Channel Manager monitors the DOM of the preview page for changes, and already syncs the overlay for every DOM change it sees. The SPA therefore only needs to call the syncOverlay function itself when the dimensions of components change without a DOM change. An example is loading an image without explicit dimensions: once the image is loaded, the layout of the page probably changes yet without any changes to the DOM.

DOM

The DOM part of the API is needed so that the SPA can tell the Channel Manager where in the page DOM it can find which component / container. When running the Enterprise version (BloomReach Experience Manager), the Page Model includes additional metadata in the Channel Manager Preview version of the Page Model API which the SPA must push into the page DOM (at the appropriate locations), such that the Channel Manager can detect and deal with components and containers. This metadata has the form of HTML comments and they are used to wrap the start and end DOM location of a component or container: The SPA must transparently insert the start comment (<component-model>._meta.beginNodeSpan[0].data) before the DOM element representing the component or container, and the end comment (<component-model>._meta.endNodeSpan[0].data) after the DOM element representing the component or container.

For the Channel Manager's component drag & drop functionality to work well, HST data representing SPA pages should make use of containers of xtype other than hst.nomarkup.

On top of the component and container markers, the SPA must also insert potentially multiple pieces of meta-data at the page level. This data is, again, made available as meta-data in the Page Model, and the SPA can access it at <page-model>.page._meta.endNodeSpan[n].data, where n iterates over the entries in the endNodeSpan list. Please refer to the sample SPA integration code of the Demo Project for details on how to add these comments to the page DOM.

To retrieve the preview version of the Page Model API, the SPA must be served over the same host as the CMS runs (for SSO purposes). If the SPA initial html is served by the HST by using the SpaSitePipeline, then this is automatically the case. By default, the Page Model API Preview is available at

http://{cms-host}/site/_cmsinternal/resourceapi  

where the _cmsinternal is the default but configurable and resourceapi is also configurable via the hst:pagemodelapi property. When having an SSO between the CMS webapp and the site webapp (e.g. by logging in into the CMS and opening the SPA channel), you can access the above Page Model API Preview resulting in an output that

  1. Shows the preview of the content
  2. Includes a beginNodeSpan and endNodeSpan elements.

Then when, for example, accessing 

http://localhost:8080/site/_cmsinternal/resourceapi/blog/2018/06/first-blog-post.html

the Page Model API Preview response for a single HstComponent looks something like

{
    "id": "r6_r1_r2_r1",
    "name": "content",
    "componentClass": "org.onehippo.cms7.essentials.components.EssentialsContentComponent",
    "type": "CONTAINER_ITEM_COMPONENT",
    "label": "Blog Detail",
    "models": {
      "document": {
        "$ref": "\/content\/ua95a21d480194f1abe1181c532775102"
      }
    },
    "_meta": {
      "params": {
        
      },
      "beginNodeSpan": [
        {
          "type": "comment",
          "data": "<!-- { \"HST-Label\":\"Blog Detail\", \"HST-LastModified\":\"1528271870520\", \"HST-XType\":\"hst.item\", \"uuid\":\"c5d11a29-307b-4ad4-b3a8-e0799f94789a\", \"HST-Type\":\"CONTAINER_ITEM_COMPONENT\", \"refNS\":\"r6_r1_r2_r1\", \"url\":\"\/site\/_cmsinternal\/resourceapi\/blog\/2018\/06\/first-blog-post.html?_hn:type=component-rendering&_hn:ref=r6_r1_r2_r1\"} -->"
        }
      ],
      "endNodeSpan": [
        {
          "type": "comment",
          "data": "<!-- { \"uuid\":\"c5d11a29-307b-4ad4-b3a8-e0799f94789a\", \"HST-End\":\"true\"} -->"
        }
      ]
    },
    "_links": {
      "componentRendering": {
        "href": "\/site\/_cmsinternal\/resourceapi\/blog\/2018\/06\/first-blog-post.html?_hn:type=component-rendering&_hn:ref=r6_r1_r2_r1"
      }
    }
}

The SPA code must place the beginNodeSpan and endNodeSpan around the component's HTML. Most likely in future versions, instead of this html comment, only an identifier is needed to be added to the root HTML element of the React/Angular component, but for now the entire comments are needed.

Channel Manager SPA API

This BloomReach Experience Manager feature requires a standard or premium license. Please contact BloomReach for more information.

Introduction

Integrating your SPA with the Channel Manager enables dynamic management of parts of your SPA. If you integrate your SPA with the Channel Manager, CMS webmasters can customize, add, move, and remove components of your SPA, just like they can (and may be used to) for non-SPA pages. Such manageable components must be known to HST, and HST exposes them through the Page Model API to the SPA, which must be aware of the concept of components, as well as of the specific set of components in use by / available to the SPA.

Channel Manager - SPA integration is optional. While it is possible to only integrate your SPA with BloomReach Experience Manager's Page Model API, you then miss the ability to let CMS users manage the components exposed by the Page Model API.

Conceptually, the integration between your SPA and the Channel Manager is bi-directional: If a CMS user changes parts of the Page Model, the SPA must be notified, and if the visitor of the (preview) page, displayed in the Channel Manager, changes the state/route of the SPA, the Channel Manager must be notified. In both scenarios, the goal is that your SPA and the Channel Manager stay in sync.

API

The integration API between your SPA and the Channel Manager consists of two parts, described in separate sub-sections:

  1. A JavaScript part
  2. A DOM part

Both parts are implemented in the Demo Project, which you can use as a reference. Specifically, you may want to check out the page.js file, and the utilities it engages.

Javascript

The Javascript part of the API covers both directions of the Channel Manager - SPA integration. For this part of the API, the Channel Manager requires the SPA to have constructed the following, page-global object:

window.SPA = {
  init: (cms) => {
    // Remember the cms object for subsequent callbacks
  },
  renderComponent: (id, propertiesMap) => {
    // Implement logic for re-rendering the component with the provided ID,
    // given a map of component properties
  }
};

By means of this SPA object, your SPA declares to the Channel Manager how the Channel Manager can communicate with your SPA. The Channel Manager listens to the load event of the preview page, then checks if the new page defines the SPA object, thereby declaring that it is an "SPA page". If the SPA object is found, the Channel Manager immediately calls the init function to register a cms object, through which the SPA can call back the Channel Manager. The Channel Manager does not expect any return value from the init function. The structure of the cms object is described below. First, let's discuss the renderComponent function.

If the Channel Manager has detected that the current preview page is a "SPA page", most component related operations (i.e. add, move, delete) trigger a clean reload of the page in order to bring your SPA in sync with the changed Page Model. For the case of editing the properties of a component, this wouldn't work, because the Channel Manager hasn't yet persisted the changes to the component properties to the back-end, and the Page Model wouldn't be able to provide these changes to the SPA in case of a reload. In order to facilitate showing the effect of the changed properties to the CMS user, the SPA object must therefore implement the renderComponent function. When a CMS user is editing the properties of a component, the Channel Manager calls this function, providing the SPA with the ID of the component and a map of properties, which should be used to re-render said component. The SPA must take these properties and forward them to the Page Model API, requesting an updated model for just that component, and then update its internal state and DOM accordingly. The renderComponent function may return false to trigger the normal component rendering logic (which replaces the DOM of the component). Any other return value (we recommend true by default) instructs the Channel Manager to take no further action with respect to re-rendering the component. Re-rendering a component may result in a need for the Channel Manager to sync its component overlay to the updated page. The next paragraph describes how this works.

During page initialization, the Channel Manager registers a cms object with the SPA, by calling the init function. The cms object exposes the following functions:

{
  createOverlay: () => {
    // Call this function when the set / ordering of components
    // on the page has changed
  },
  syncOverlay: () => {
    // Call this function when the dimensions of one or more components
    // on the page have changed without a DOM change
  }
}

The createOverlay and syncOverlay functions must be called by the SPA to trigger the corresponding Channel Manager-internal actions. Both functions are simple trigger functions for the Channel Manager, taking no arguments, and neither of them produces a meaningful return value. When triggered, the Channel Manager analyses the DOM to take further action. See the DOM section below for more details.

The SPA must call the createOverlay function when the set or order of components currently displayed has changed. Most prominently, this is the case when the page is fully drawn initially, based on the retrieved Page Model. Also, if the visitor of the preview page navigates to a different state / route of the SPA, which displays a different set (or order) of components, the createOverlay trigger must be called as well.

The SPA must call the syncOverlay function when the dimensions of one or more components currently displayed on the page have changed. Most prominently, this is the case when (the chain of events triggered by) a renderComponent call from the Channel Manager (i.e. the editing of component properties) has completed in the SPA. The Channel Manager then makes sure that its component overlay matches the components currently displayed on the page.

The Channel Manager monitors the DOM of the preview page for changes, and already syncs the overlay for every DOM change it sees. The SPA therefore only needs to call the syncOverlay function itself when the dimensions of components change without a DOM change. An example is loading an image without explicit dimensions: once the image is loaded, the layout of the page probably changes yet without any changes to the DOM.

DOM

The DOM part of the API is needed so that the SPA can tell the Channel Manager where in the page DOM it can find which component / container. When running the Enterprise version (BloomReach Experience Manager), the Page Model includes additional metadata in the Channel Manager Preview version of the Page Model API which the SPA must push into the page DOM (at the appropriate locations), such that the Channel Manager can detect and deal with components and containers. This metadata has the form of HTML comments and they are used to wrap the start and end DOM location of a component or container: The SPA must transparently insert the start comment (<component-model>._meta.beginNodeSpan[0].data) before the DOM element representing the component or container, and the end comment (<component-model>._meta.endNodeSpan[0].data) after the DOM element representing the component or container.

For the Channel Manager's component drag & drop functionality to work well, HST data representing SPA pages should make use of containers of xtype other than hst.nomarkup.

On top of the component and container markers, the SPA must also insert potentially multiple pieces of meta-data at the page level. This data is, again, made available as meta-data in the Page Model, and the SPA can access it at <page-model>.page._meta.endNodeSpan[n].data, where n iterates over the entries in the endNodeSpan list. Please refer to the sample SPA integration code of the Demo Project for details on how to add these comments to the page DOM.

To retrieve the preview version of the Page Model API, the SPA must be served over the same host as the CMS runs (for SSO purposes). If the SPA initial html is served by the HST by using the SpaSitePipeline, then this is automatically the case. By default, the Page Model API Preview is available at

http://{cms-host}/site/_cmsinternal/resourceapi  

where the _cmsinternal is the default but configurable and resourceapi is also configurable via the hst:pagemodelapi property. When having an SSO between the CMS webapp and the site webapp (e.g. by logging in into the CMS and opening the SPA channel), you can access the above Page Model API Preview resulting in an output that

  1. Shows the preview of the content
  2. Includes a beginNodeSpan and endNodeSpan elements.

Then when, for example, accessing 

http://localhost:8080/site/_cmsinternal/resourceapi/blog/2018/06/first-blog-post.html

the Page Model API Preview response for a single HstComponent looks something like

{
    "id": "r6_r1_r2_r1",
    "name": "content",
    "componentClass": "org.onehippo.cms7.essentials.components.EssentialsContentComponent",
    "type": "CONTAINER_ITEM_COMPONENT",
    "label": "Blog Detail",
    "models": {
      "document": {
        "$ref": "\/content\/ua95a21d480194f1abe1181c532775102"
      }
    },
    "_meta": {
      "params": {
        
      },
      "beginNodeSpan": [
        {
          "type": "comment",
          "data": "<!-- { \"HST-Label\":\"Blog Detail\", \"HST-LastModified\":\"1528271870520\", \"HST-XType\":\"hst.item\", \"uuid\":\"c5d11a29-307b-4ad4-b3a8-e0799f94789a\", \"HST-Type\":\"CONTAINER_ITEM_COMPONENT\", \"refNS\":\"r6_r1_r2_r1\", \"url\":\"\/site\/_cmsinternal\/resourceapi\/blog\/2018\/06\/first-blog-post.html?_hn:type=component-rendering&_hn:ref=r6_r1_r2_r1\"} -->"
        }
      ],
      "endNodeSpan": [
        {
          "type": "comment",
          "data": "<!-- { \"uuid\":\"c5d11a29-307b-4ad4-b3a8-e0799f94789a\", \"HST-End\":\"true\"} -->"
        }
      ]
    },
    "_links": {
      "componentRendering": {
        "href": "\/site\/_cmsinternal\/resourceapi\/blog\/2018\/06\/first-blog-post.html?_hn:type=component-rendering&_hn:ref=r6_r1_r2_r1"
      }
    }
}

The SPA code must place the beginNodeSpan and endNodeSpan around the component's HTML. Most likely in future versions, instead of this html comment, only an identifier is needed to be added to the root HTML element of the React/Angular component, but for now the entire comments are needed.