├── README.md
├── packages
└── sdk
│ ├── .npmignore
│ ├── src
│ ├── utils
│ │ ├── get-nested-object.js
│ │ ├── date.js
│ │ ├── find-child-by-id.js
│ │ ├── get-configuration-for-path.js
│ │ ├── image-url.js
│ │ ├── link-rewriter.js
│ │ ├── add-html-comment.js
│ │ ├── fetch.js
│ │ ├── create-link.js
│ │ └── cms-urls.js
│ ├── cms-components
│ │ └── core
│ │ │ ├── placeholder.js
│ │ │ ├── undefined.js
│ │ │ ├── component.js
│ │ │ ├── cms-edit-button.js
│ │ │ ├── render-cms-component.js
│ │ │ ├── content-component-wrapper.js
│ │ │ ├── container-item.js
│ │ │ ├── container.js
│ │ │ └── page.js
│ ├── context.js
│ └── index.js
│ ├── babel.config.js
│ ├── rollup.config.js
│ ├── package.json
│ └── README.md
├── examples
├── server-side-rendered
│ ├── .babelrc
│ ├── .env
│ ├── src
│ │ ├── routes.js
│ │ ├── index.js
│ │ ├── next.config.js
│ │ ├── static
│ │ │ └── custom.css
│ │ ├── components
│ │ │ ├── menu-item.js
│ │ │ ├── menu.js
│ │ │ ├── news-item.js
│ │ │ ├── banner.js
│ │ │ ├── content.js
│ │ │ └── news-list.js
│ │ └── pages
│ │ │ ├── _document.js
│ │ │ └── index.js
│ ├── package.json
│ └── README.md
└── client-side-rendered
│ ├── .env
│ ├── server.js
│ ├── package.json
│ ├── public
│ ├── static
│ │ └── custom.css
│ └── index.html
│ ├── src
│ ├── components
│ │ ├── news-item.js
│ │ ├── banner.js
│ │ ├── content.js
│ │ ├── menu.js
│ │ └── news-list.js
│ └── index.js
│ └── README.md
├── .gitignore
├── Procfile
├── .editorconfig
├── .travis.yml
├── NOTICE
├── .eslintrc
├── package.json
└── LICENSE
/README.md:
--------------------------------------------------------------------------------
1 | packages/sdk/README.md
--------------------------------------------------------------------------------
/packages/sdk/.npmignore:
--------------------------------------------------------------------------------
1 | .eslintrc
2 | babel.config.js
3 | rollup.config.js
4 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "next/babel"
5 | ]
6 | ]
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | /packages/*/dist/
3 | /examples/*/build/
4 | /examples/server-side-rendered/src/.next/
5 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | # start the CSR or SSR example, depending on an environment variable
2 | web: yarn run start:example-$CSR_OR_SSR
3 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/.env:
--------------------------------------------------------------------------------
1 | # The PUBLIC_URL variable will set a prefix to the application assets.
2 | # See https://create-react-app.dev/docs/using-the-public-folder for more information.
3 | # PUBLIC_URL=http://localhost:3000
4 |
5 | REACT_APP_BR_ORIGIN=http://localhost:8080
6 | REACT_APP_BR_CONTEXT_PATH=site
7 | REACT_APP_BR_CHANNEL_PATH=
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '12'
4 |
5 | install:
6 | - yarn
7 |
8 | script:
9 | - yarn workspace bloomreach-experience-react-sdk lint
10 | - yarn build
11 |
12 | deploy:
13 | edge: true
14 | provider: npm
15 | cleanup: false
16 | src: packages/sdk
17 | api_token: $NPM_AUTH_TOKEN
18 | on:
19 | branch: master
20 | tags: true
21 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/.env:
--------------------------------------------------------------------------------
1 | # The PUBLIC_URL variable will set the 'assetPrefix' option in Next. By default it is set to '/'
2 | # You should use this variable in your templates as well, when linking static resources like style sheets and images:
3 | # e.g.
4 | # PUBLIC_URL=http://localhost:3000
5 |
6 | BR_ORIGIN=http://localhost:8080
7 | BR_CONTEXT_PATH=site
8 | BR_CHANNEL_PATH=
9 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Bloomreach Experience React SDK
2 | Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 |
4 | This product includes software developed by:
5 | Hippo B.V., Amsterdam, The Netherlands (http://www.onehippo.com/);
6 | The Apache Software Foundation (http://www.apache.org/).
7 |
8 | NOTICE: Only our own original work is licensed under the terms of the
9 | Apache License Version 2.0. The licenses of some libraries might impose
10 | different redistribution or general licensing terms than those stated in the
11 | Apache License. Users and redistributors are hereby requested to verify these
12 | conditions and agree upon them.
13 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "airbnb-base",
5 | "plugin:react/recommended"
6 | ],
7 | "env": {
8 | "browser": true
9 | },
10 | "rules": {
11 | "class-methods-use-this": "off",
12 | "global-require": "off",
13 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/*.spec.js"]}],
14 | "import/prefer-default-export": "off",
15 | "linebreak-style": "off",
16 | "max-len": ["error", { "code": 120 }],
17 | "no-console": "off",
18 | "no-param-reassign": "off",
19 | "no-prototype-builtins": "off",
20 | "no-restricted-properties": "off",
21 | "no-underscore-dangle": "off",
22 | "react/prop-types": "off",
23 | "react/react-in-jsx-scope": "off"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/routes.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const routes = require('next-routes')();
18 |
19 | routes
20 | .add('index', '/(.*)');
21 |
22 | module.exports = routes;
23 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/get-nested-object.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | export default function getNestedObject(nestedObj, pathArr) {
18 | return pathArr.reduce((obj, key) => ((obj && obj[key] !== 'undefined') ? obj[key] : null), nestedObj);
19 | }
20 |
--------------------------------------------------------------------------------
/packages/sdk/babel.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | module.exports = {
18 | presets: [
19 | '@babel/preset-env',
20 | '@babel/preset-react',
21 | ],
22 | plugins: [
23 | '@babel/plugin-proposal-object-rest-spread',
24 | ['babel-plugin-transform-async-to-promises', { inlineHelpers: true }],
25 | ],
26 | };
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "repository": "https://github.com/bloomreach/experience-react-sdk",
4 | "bugs": "https://issues.onehippo.com/projects/CMS",
5 | "homepage": "https://github.com/bloomreach/experience-react-sdk",
6 | "author": "Bloomreach B.V.",
7 | "license": "Apache-2.0",
8 | "workspaces": {
9 | "packages": [
10 | "packages/*",
11 | "examples/*"
12 | ],
13 | "nohoist": [
14 | "**/eslint-formatter-friendly"
15 | ]
16 | },
17 | "scripts": {
18 | "build": "yarn workspaces run build",
19 | "start:example-csr": "yarn --cwd examples/client-side-rendered start",
20 | "start:example-ssr": "yarn --cwd examples/server-side-rendered start"
21 | },
22 | "devDependencies": {
23 | "eslint": "^6.3.0",
24 | "eslint-config-airbnb-base": "^14.0.0",
25 | "eslint-formatter-friendly": "^7.0.0",
26 | "eslint-plugin-babel": "^5.3.0",
27 | "eslint-plugin-import": "^2.18.2",
28 | "eslint-plugin-react": "^7.14"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/placeholder.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 |
19 | export default class Placeholder extends React.Component {
20 | // placeholder component is used for when components data is not set
21 | // this is the case when a new component is added to a container
22 | render() {
23 | return
Click to configure { this.props.name }
;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const express = require('express');
18 | const path = require('path');
19 | const port = process.env.PORT || 3000;
20 | const app = express();
21 |
22 | app.use(express.static(path.join(__dirname, 'build')));
23 |
24 | app.get('/*', function (req, res) {
25 | res.sendFile(path.join(__dirname, 'build', 'index.html'));
26 | });
27 | app.listen(port);
28 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/undefined.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 |
19 | export default class UndefinedComponent extends React.Component {
20 | // fallback component when unknown/undefined component type is used
21 | render() {
22 | return (
23 |
24 | Component { this.props.name } not defined
25 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const next = require('next');
18 | const { createServer } = require('http');
19 | const routes = require('./routes');
20 |
21 | const app = next({
22 | dev: process.env.NODE_ENV !== 'production',
23 | dir: './src',
24 | });
25 | const handler = routes.getRequestHandler(app);
26 |
27 | app.prepare().then(() => {
28 | createServer(handler).listen(process.env.PORT || 3000);
29 | });
30 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/next.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const dotenv = require('dotenv').config();
18 |
19 | if (dotenv.error) {
20 | throw dotenv.error;
21 | }
22 |
23 | module.exports = {
24 | assetPrefix: process.env.PUBLIC_URL || '/',
25 | publicRuntimeConfig: {
26 | brOrigin: process.env.BR_ORIGIN,
27 | brContextPath: process.env.BR_CONTEXT_PATH,
28 | brChannelPath: process.env.BR_CHANNEL_PATH,
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bloomreach-experience-react-sdk-ssr-example",
3 | "version": "0.6.4",
4 | "description": "Example server-side React App for the Bloomreach Experience SDK for React",
5 | "private": true,
6 | "author": "Bloomreach B.V.",
7 | "license": "Apache-2.0",
8 | "dependencies": {
9 | "bloomreach-experience-react-sdk": "^0.6.4",
10 | "dotenv": "^8.2.0",
11 | "isomorphic-unfetch": "^3.0.0",
12 | "next": "^9.1.6",
13 | "next-routes": "^1.4.2",
14 | "react": "^16.12.0",
15 | "react-dom": "^16.12.0"
16 | },
17 | "devDependencies": {
18 | "eslint": "^6.7.2",
19 | "eslint-config-airbnb-base": "^14.0.0",
20 | "eslint-formatter-friendly": "^7.0.0",
21 | "eslint-plugin-babel": "^5.3.0",
22 | "eslint-plugin-import": "^2.19.1",
23 | "eslint-plugin-react": "^7.17"
24 | },
25 | "scripts": {
26 | "dev": "node src/index.js",
27 | "build": "next build src/",
28 | "lint": "eslint src/ --format node_modules/eslint-formatter-friendly",
29 | "start": "NODE_ENV=production node src/index.js"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bloomreach-experience-react-sdk-csr-example",
3 | "version": "0.6.4",
4 | "description": "Example client-side React App for the Bloomreach Experience SDK for React",
5 | "private": true,
6 | "dependencies": {
7 | "bloomreach-experience-react-sdk": "^0.6.4",
8 | "react": "^16.12.0",
9 | "react-dom": "^16.12.0",
10 | "react-router-dom": "^5.1.2",
11 | "react-scripts": "^3.3.0"
12 | },
13 | "scripts": {
14 | "dev": "react-scripts start",
15 | "build": "react-scripts build",
16 | "start": "node server.js",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | },
20 | "author": "Bloomreach B.V.",
21 | "license": "Apache-2.0",
22 | "homepage": ".",
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | },
35 | "devDependencies": {
36 | "express": "^4.17.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/static/custom.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | body {
18 | padding-bottom: 0;
19 | }
20 |
21 | .container {
22 | padding-top: 2rem;
23 | }
24 |
25 | footer {
26 | margin-top: 30px;
27 | }
28 |
29 | .jumbotron img {
30 | width: 100%;
31 | }
32 |
33 | .navbar .navbar-brand {
34 | margin-right: 3rem;
35 | }
36 |
37 | .navbar-nav .nav-link {
38 | text-transform: capitalize;
39 | }
40 |
41 | .blog-post-date {
42 | margin-right: 10px;
43 | }
44 |
45 | .has-edit-button {
46 | position: relative;
47 | }
48 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/public/static/custom.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | body {
18 | padding-bottom: 0;
19 | }
20 |
21 | .container {
22 | padding-top: 2rem;
23 | }
24 |
25 | footer {
26 | margin-top: 30px;
27 | }
28 |
29 | .jumbotron img {
30 | width: 100%;
31 | }
32 |
33 | .navbar .navbar-brand {
34 | margin-right: 3rem;
35 | }
36 |
37 | .navbar-nav .nav-link {
38 | text-transform: capitalize;
39 | }
40 |
41 | .blog-post-date {
42 | margin-right: 10px;
43 | }
44 |
45 | .has-edit-button {
46 | position: relative;
47 | }
48 |
--------------------------------------------------------------------------------
/packages/sdk/src/context.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 |
19 | export const PageModelContext = React.createContext({});
20 | export const PreviewContext = React.createContext('');
21 | export const ComponentDefinitionsContext = React.createContext({});
22 | export const CreateLinkContext = React.createContext();
23 |
24 | export function withPageModel(Component) {
25 | return function PageModelComponent(props) {
26 | return (
27 |
28 | {(pageModel) => }
29 |
30 | );
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/date.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | const MONTHS = [
18 | 'January',
19 | 'February',
20 | 'March',
21 | 'April',
22 | 'May',
23 | 'June',
24 | 'July',
25 | 'August',
26 | 'September',
27 | 'October',
28 | 'November',
29 | 'December',
30 | ];
31 |
32 | export default function parseDate(date) {
33 | const parsedDate = parseFloat(date);
34 | // eslint-disable-next-line no-restricted-globals
35 | if (isNaN(parsedDate)) {
36 | return null;
37 | }
38 |
39 | const dateObj = new Date(parsedDate);
40 |
41 | return `${MONTHS[dateObj.getMonth()]} ${dateObj.getDate()}, ${dateObj.getFullYear()}`;
42 | }
43 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/find-child-by-id.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // returns parent and index of child referenced by ID,
18 | // so that we can easily replace the child
19 | export default function findChildById(object, id, parent, idx) {
20 | const props = Object.keys(object);
21 | // eslint-disable-next-line no-plusplus
22 | for (let i = 0; i < props.length; i++) {
23 | const prop = props[i];
24 | if (typeof object[prop] === 'object' && object[prop] !== null) {
25 | const result = findChildById(object[prop], id, object, prop);
26 | if (result) {
27 | return result;
28 | }
29 | } else if (prop === 'id' && object.id === id) {
30 | return { parent, idx };
31 | }
32 | }
33 |
34 | return null;
35 | }
36 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/component.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import CmsContainer from './container';
19 |
20 | export default class CmsComponent extends React.Component {
21 | render() {
22 | const { configuration } = this.props;
23 |
24 | if (!configuration || !configuration.components || !configuration.components.length) {
25 | return null;
26 | }
27 |
28 | return (
29 |
30 | { configuration.components.map((component) => {
31 | if (component.type === 'CONTAINER_COMPONENT') {
32 | return ;
33 | }
34 |
35 | return ;
36 | }) }
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/components/menu-item.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { createLink } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class CmsMenuItem extends React.Component {
21 | render() {
22 | const { configuration } = this.props;
23 |
24 | if (!configuration) {
25 | return null;
26 | }
27 |
28 | const activeElm = configuration.selected ? (current) : null;
29 | // createLink takes linkText as a function so that it can contain HTML elements
30 | const linkText = () => {configuration.name}{activeElm};
31 | const className = 'nav-link';
32 |
33 | return (
34 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/src/components/news-item.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { createLink, parseDate } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class NewsItem extends React.Component {
21 | render() {
22 | const { content, manageContentButton } = this.props;
23 | // createLink takes linkText as a function so that it can contain HTML elements
24 | const linkText = () => content.title;
25 |
26 | return (
27 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/components/news-item.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { createLink, parseDate } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class NewsItem extends React.Component {
21 | render() {
22 | const { content } = this.props;
23 | const { manageContentButton } = this.props;
24 | // createLink takes linkText as a function so that it can contain HTML elements
25 | const linkText = () => content.title;
26 |
27 | return (
28 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/README.md:
--------------------------------------------------------------------------------
1 | # Example client-side React App
2 |
3 | Example client-side React app using the BloomReach Experience SDK for React. The app is created using [create-react-app](https://github.com/facebook/create-react-app).
4 |
5 | ## Install and run
6 |
7 | First, download and install the [BloomReach SPA demo project](https://github.com/onehippo/hippo-demo-spa-integration)
8 | by following the instructions in the *Build Demo CMS project* section of the above link. Then run it by following the
9 | instructions in *Run Demo CMS project*.
10 |
11 | Next, install the [UrlRewriter](https://documentation.bloomreach.com/library/enterprise/enterprise-features/url-rewriter/installation.html)
12 | and configure that according to [this document](https://documentation.bloomreach.com/library/concepts/spa-plus/url-rewriter-rules.html).
13 |
14 | Then, customize `.env` file to contain a correct [PUBLIC_URL](https://create-react-app.dev/docs/using-the-public-folder) path, for example:
15 | ```
16 | PUBLIC_URL=http://localhost:3000
17 | ```
18 |
19 | Beware of [this issue](https://github.com/facebook/create-react-app/pull/7259). The PUBLIC_URL may not work in development mode.
20 |
21 | In the same `.env` file, also specify the brXM instance to fetch the page model from. The default configuration
22 | connects to `http://localhost:8080/site/`:
23 |
24 | ```
25 | REACT_APP_BR_ORIGIN=http://localhost:8080
26 | REACT_APP_BR_CONTEXT_PATH=site
27 | REACT_APP_BR_CHANNEL_PATH=
28 | ```
29 |
30 | Finally, build and run the React app as follows:
31 |
32 | ```bash
33 | yarn
34 | yarn run build
35 | yarn run start
36 | ```
37 |
38 | The CMS should now be accessible at , and it should render the client-side React app in preview
39 | mode in the Channel Manager. The SPA itself can be accessed directly via .
40 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import Document, { Head, Main, NextScript } from 'next/document';
18 |
19 | export default class DefaultDocument extends Document {
20 | render() {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 | React App
29 |
30 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/src/components/banner.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { createLink, getImageUrl, parseAndRewriteLinks } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class Banner extends React.Component {
21 | render() {
22 | const { content, manageContentButton, preview } = this.props;
23 | const image = getImageUrl(content.image, this.props.pageModel, preview);
24 |
25 | let contentHtml;
26 | if (content.content && content.content.value) {
27 | contentHtml = parseAndRewriteLinks(content.content.value, preview);
28 | }
29 |
30 | const link = content.link ? content.link.$ref : null;
31 | // createLink takes linkText as a function so that it can contain HTML elements
32 | const linkText = () => 'Learn more';
33 | const className = 'btn btn-primary btn-lg';
34 |
35 | return (
36 |
{ link && createLink('ref', link, linkText, className) }
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/components/banner.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { createLink, getImageUrl, parseAndRewriteLinks } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class Banner extends React.Component {
21 | render() {
22 | const { content, manageContentButton, preview } = this.props;
23 | const image = getImageUrl(content.image, this.props.pageModel, preview);
24 |
25 | let contentHtml;
26 | if (content.content && content.content.value) {
27 | contentHtml = parseAndRewriteLinks(content.content.value, preview);
28 | }
29 |
30 | const link = content.link ? content.link.$ref : null;
31 | // createLink takes linkText as a function so that it can contain HTML elements
32 | const linkText = () => 'Learn more';
33 | const className = 'btn btn-primary btn-lg';
34 |
35 | return (
36 |
{ link && createLink('ref', link, linkText, className) }
44 |
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/components/content.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { getImageUrl, parseAndRewriteLinks, parseDate } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class Content extends React.Component {
21 | render() {
22 | const { content, manageContentButton, preview } = this.props;
23 | const image = getImageUrl(content.image, this.props.pageModel, preview);
24 |
25 | let contentHtml;
26 | if (content.content && content.content.value) {
27 | contentHtml = parseAndRewriteLinks(content.content.value, preview);
28 | }
29 |
30 | return (
31 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/src/components/content.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { getImageUrl, parseAndRewriteLinks, parseDate } from 'bloomreach-experience-react-sdk';
19 |
20 | export default class Content extends React.Component {
21 | render() {
22 | const { content, manageContentButton, preview } = this.props;
23 | const image = getImageUrl(content.image, this.props.pageModel, preview);
24 |
25 | let contentHtml;
26 | if (content.content && content.content.value) {
27 | contentHtml = parseAndRewriteLinks(content.content.value, preview);
28 | }
29 |
30 | return (
31 |
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/link-rewriter.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import ReactHtmlParser from 'react-html-parser';
19 | import globalCmsUrls, { FULLY_QUALIFIED_LINK } from './cms-urls';
20 | import createLink from './create-link';
21 |
22 | function getChildren(node) {
23 | if (!node.children) {
24 | return '';
25 | }
26 |
27 | return node.children.reduce(
28 | (linkText, childNode) => linkText
29 | + getChildren(childNode)
30 | + (childNode.type === 'text' ? childNode.data : ''),
31 | '',
32 | );
33 | }
34 |
35 | export default function parseAndRewriteLinks(html, preview) {
36 | return ReactHtmlParser(html, {
37 | // eslint-disable-next-line consistent-return
38 | transform: (node) => {
39 | if (node.type === 'tag' && node.name === 'a' && node.attribs['data-type']
40 | && node.attribs['data-type'] === 'internal') {
41 | const { class: className, href } = node.attribs;
42 | const linkText = () => getChildren(node);
43 | const link = createLink('href', href, linkText, className);
44 |
45 | return React.cloneElement(link, { key: node.parent ? node.parent.children.indexOf(node) : 0 });
46 | }
47 | if (
48 | node.type === 'tag'
49 | && node.name === 'img'
50 | && node.attribs.src
51 | && !node.attribs.src.match(FULLY_QUALIFIED_LINK)
52 | ) {
53 | // transform image URLs in fully qualified URLs, so images are also loaded when requested from React app
54 | // which typically runs on a different port than CMS / HST
55 | const baseCmsUrl = globalCmsUrls[preview ? 'preview' : 'live'].baseUrl;
56 | node.attribs.src = baseCmsUrl + node.attribs.src;
57 | }
58 | },
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/render-cms-component.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import CmsComponent from './component';
19 | import CmsContainer from './container';
20 | import CmsContainerItem from './container-item';
21 | import { withPageModel, PreviewContext } from '../../context';
22 | import getConfigurationForPath from '../../utils/get-configuration-for-path';
23 |
24 | class RenderCmsComponent extends React.Component {
25 | renderPageComponent(configuration) {
26 | switch (configuration.type) {
27 | case 'CONTAINER_COMPONENT':
28 | return ;
29 | case 'CONTAINER_ITEM_COMPONENT':
30 | return ;
31 | default:
32 | return ;
33 | }
34 | }
35 |
36 | renderStaticComponent(renderComponent, configuration, pageModel) {
37 | return (
38 |
39 | { (preview) => React.createElement(renderComponent, { configuration, pageModel, preview }) }
40 |
41 | );
42 | }
43 |
44 | render() {
45 | const { path, pageModel, renderComponent } = this.props;
46 |
47 | let configuration;
48 | // render entire page if no path has been specified
49 | if (!path) {
50 | if (!pageModel) {
51 | console.log(' has no supplied page model');
52 | return null;
53 | }
54 |
55 | configuration = pageModel.page;
56 | } else {
57 | // or lookup component configuration using supplied path
58 | configuration = getConfigurationForPath(path, pageModel);
59 | if (configuration && renderComponent) {
60 | return this.renderStaticComponent(renderComponent, configuration, pageModel);
61 | }
62 | }
63 |
64 | if (!configuration) {
65 | return null;
66 | }
67 |
68 | return this.renderPageComponent(configuration, renderComponent);
69 | }
70 | }
71 |
72 | export default withPageModel(RenderCmsComponent);
73 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/add-html-comment.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import getNestedObject from './get-nested-object';
18 |
19 | export function addBeginComment(htmlElm, position, configuration, preview) {
20 | const beginNodeSpan = getNestedObject(configuration, ['_meta', 'beginNodeSpan', 0, 'data']);
21 | if (!preview || !htmlElm || !beginNodeSpan || htmlElm.classList.contains('cms-begin-comment-added')) {
22 | return;
23 | }
24 |
25 | htmlElm.insertAdjacentHTML(position, configuration._meta.beginNodeSpan[0].data);
26 | // adding an HTML class to ensure comments are not added more than once
27 | // this is because the comments are added through the DOM and not by React
28 | // so this function is fired on every re-render of the parent component
29 | htmlElm.classList.add('cms-begin-comment-added');
30 | }
31 |
32 | export function addEndComment(htmlElm, position, configuration, preview) {
33 | const endNodeSpan = getNestedObject(configuration, ['_meta', 'endNodeSpan', 0, 'data']);
34 | if (!preview || !htmlElm || !endNodeSpan || htmlElm.classList.contains('cms-end-comment-added')) {
35 | return;
36 | }
37 |
38 | htmlElm.insertAdjacentHTML(position, configuration._meta.endNodeSpan[0].data);
39 | // @see comment in addBeginComment()
40 | htmlElm.classList.add('cms-end-comment-added');
41 | }
42 |
43 | export function addBodyComments(configuration, preview) {
44 | const endNodeSpan = getNestedObject(configuration, ['_meta', 'endNodeSpan', 0, 'data']);
45 | if (!preview || !endNodeSpan) {
46 | return;
47 | }
48 |
49 | // remove comments from page meta-data element, if existing
50 | let pageMetaDataElm = document.getElementById('hst-page-meta-data');
51 | if (pageMetaDataElm) {
52 | pageMetaDataElm.innerHTML = '';
53 | } else {
54 | // otherwise create page-meta-data element containing page HTML comments
55 | pageMetaDataElm = document.createElement('div');
56 | pageMetaDataElm.id = 'hst-page-meta-data';
57 | pageMetaDataElm.style = 'display: none';
58 | document.body.appendChild(pageMetaDataElm);
59 | }
60 |
61 | configuration._meta.endNodeSpan.forEach(({ data }) => pageMetaDataElm.insertAdjacentHTML('beforeend', data));
62 | }
63 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/fetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import axios from 'axios';
18 | import { buildApiUrl } from './cms-urls';
19 |
20 | const requestConfigGet = {
21 | method: 'GET',
22 | withCredentials: true,
23 | };
24 |
25 | const requestConfigPost = {
26 | method: 'POST',
27 | withCredentials: true,
28 | headers: {
29 | 'Content-Type': 'application/x-www-form-urlencoded',
30 | },
31 | };
32 |
33 | async function fetchUrl(url, requestConfig) {
34 | try {
35 | const { data } = await axios(url, requestConfig);
36 |
37 | return data;
38 | } catch (error) {
39 | if (error.response) {
40 | // The request was made and the server responded with a status code
41 | // that falls out of the range of 2xx
42 | console.log(`Error! Status code ${error.response.status} while fetching CMS page data for URL: ${url}`);
43 | console.log(error.response.data);
44 | console.log(error.response.headers);
45 | } else if (error.request) {
46 | // The request was made but no response was received
47 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
48 | // http.ClientRequest in node.js
49 | console.log(error.request);
50 | } else {
51 | // Something happened in setting up the request that triggered an Error
52 | console.log(`Error while fetching CMS page data for URL:${url}`, error.message);
53 | }
54 |
55 | console.log(error.config);
56 | }
57 |
58 | return null;
59 | }
60 |
61 | export function fetchCmsPage(pathInfo, query, preview, cmsUrls) {
62 | const url = buildApiUrl(pathInfo, query, preview, null, cmsUrls);
63 | return fetchUrl(url, requestConfigGet);
64 | }
65 |
66 | // from rendering.service.js
67 | function toUrlEncodedFormData(json) {
68 | return Object.keys(json)
69 | .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(json[key])}`)
70 | .join('&');
71 | }
72 |
73 | export function fetchComponentUpdate(pathInfo, query, preview, componentId, body) {
74 | const requestConfig = { data: toUrlEncodedFormData(body), ...requestConfigPost };
75 | const url = buildApiUrl(pathInfo, query, preview, componentId);
76 |
77 | return fetchUrl(url, requestConfig);
78 | }
79 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/create-link.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import jsonpointer from 'jsonpointer';
19 | import getNestedObject from './get-nested-object';
20 | import { CreateLinkContext, PageModelContext } from '../context';
21 |
22 | function isString(value) {
23 | return typeof value === 'string' || value instanceof String;
24 | }
25 |
26 | function _createLink(linkType, link, linkText, className, externalCreateLinkFunction, pageModel) {
27 | let href = null;
28 | let internalLink = null;
29 |
30 | // eslint-disable-next-line default-case
31 | switch (linkType) {
32 | case 'self':
33 | href = getNestedObject(link, ['_links', 'site', 'href']);
34 | internalLink = getNestedObject(link, ['_links', 'site', 'type']);
35 | break;
36 |
37 | case 'ref':
38 | if (!link || !isString(link)) {
39 | break;
40 | }
41 | // eslint-disable-next-line no-case-declarations
42 | const linkedContent = jsonpointer.get(pageModel, link);
43 | if (linkedContent) {
44 | href = getNestedObject(linkedContent, ['_links', 'site', 'href']);
45 | internalLink = getNestedObject(linkedContent, ['_links', 'site', 'type']);
46 | }
47 | break;
48 |
49 | case 'href':
50 | href = link;
51 | internalLink = 'internal';
52 | break;
53 | }
54 |
55 | // linkText is a function insteaf of a string, so that additional HTML can be included inside the anchor tag
56 | if (href && internalLink && typeof linkText === 'function') {
57 | return internalLink === 'internal' && typeof externalCreateLinkFunction === 'function'
58 | ? externalCreateLinkFunction(href, linkText, className)
59 | : {linkText()};
60 | }
61 |
62 | return null;
63 | }
64 |
65 | export default function createLink(linkType, link, linkText, className) {
66 | return (
67 |
68 | { (pageModel) =>
69 | { (externalCreateLinkFunction) => _createLink(
70 | linkType,
71 | link,
72 | linkText,
73 | className,
74 | externalCreateLinkFunction,
75 | pageModel,
76 | ) }
77 | }
78 |
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/content-component-wrapper.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import jsonpointer from 'jsonpointer';
19 | import PlaceholderComponent from './placeholder';
20 | import UndefinedComponent from './undefined';
21 | import CmsEditButton from './cms-edit-button';
22 | import { ComponentDefinitionsContext } from '../../context';
23 | import getNestedObject from '../../utils/get-nested-object';
24 |
25 | export default class ContentComponentWrapper extends React.Component {
26 | renderContentComponentWrapper(component, pageModel, content, preview, componentDefinitions, manageContentButton) {
27 | // based on the type of the component, render a different React component
28 | if (component.label in componentDefinitions && componentDefinitions[component.label].component) {
29 | return React.createElement(componentDefinitions[component.label].component, {
30 | content, pageModel, preview, manageContentButton,
31 | }, null);
32 | }
33 |
34 | return ;
35 | }
36 |
37 | render() {
38 | const { configuration, pageModel, preview } = this.props;
39 |
40 | // get content from model
41 | let contentRef = getNestedObject(configuration, ['models', 'document', '$ref']);
42 | let content;
43 | if (!contentRef) {
44 | // NewsList component passed document ID through property instead of via reference in attributes map
45 | ({ contentRef } = this.props);
46 | }
47 |
48 | if (contentRef && (typeof contentRef === 'string' || contentRef instanceof String)) {
49 | content = jsonpointer.get(pageModel, contentRef);
50 | }
51 |
52 | if (!content && preview) {
53 | // return placeholder if no document is set on component
54 | return ;
55 | }
56 |
57 | if (!content) {
58 | // don't render placeholder outside of preview mode
59 | return null;
60 | }
61 |
62 | // create edit content button and pass as a prop
63 | const manageContentButton = preview ? : null;
64 |
65 | return (
66 |
67 | { (componentDefinitions) => this.renderContentComponentWrapper(
68 | configuration,
69 | pageModel,
70 | content,
71 | preview,
72 | componentDefinitions,
73 | manageContentButton,
74 | ) }
75 |
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/src/components/news-list.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { ContentComponentWrapper, getNestedObject, Placeholder } from 'bloomreach-experience-react-sdk';
19 |
20 | export function NewsListPagination(props) {
21 | if (!props.showPagination) {
22 | return null;
23 | }
24 |
25 | return (
26 |
47 | );
48 | }
49 |
50 | export default class NewsList extends React.Component {
51 | render() {
52 | const { preview, pageModel, configuration } = this.props;
53 |
54 | // return placeholder if no list is set on component
55 | let list = getNestedObject(configuration, ['models', 'pageable', 'items', 0]);
56 | if (!list) {
57 | return preview
58 | ?
59 | : null;
60 | }
61 |
62 | list = configuration.models.pageable.items;
63 |
64 | // build list of news articles
65 | const listItems = list.map((listItem, index) => {
66 | if (!configuration
67 | || typeof configuration !== 'object'
68 | || configuration.constructor !== Object
69 | || !('$ref' in listItem)) {
70 | console.log('NewsList component configuration is not a map, unexpected format of configuration');
71 | return null;
72 | }
73 |
74 | const newsItemConfig = { label: 'News Item' };
75 |
76 | return ;
83 | });
84 |
85 | return (
86 |
87 |
88 | {listItems}
89 |
90 |
91 |
92 |
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/components/news-list.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { ContentComponentWrapper, getNestedObject, Placeholder } from 'bloomreach-experience-react-sdk';
19 |
20 | export function NewsListPagination(props) {
21 | if (!props.showPagination) {
22 | return null;
23 | }
24 |
25 | return (
26 |
47 | );
48 | }
49 |
50 | export default class NewsList extends React.Component {
51 | render() {
52 | const { preview, pageModel, configuration } = this.props;
53 |
54 | // return placeholder if no list is set on component
55 | let list = getNestedObject(configuration, ['models', 'pageable', 'items', 0]);
56 | if (!list) {
57 | return preview
58 | ?
59 | : null;
60 | }
61 |
62 | list = configuration.models.pageable.items;
63 |
64 | // build list of news articles
65 | const listItems = list.map((listItem, index) => {
66 | if (!configuration
67 | || typeof configuration !== 'object'
68 | || configuration.constructor !== Object
69 | || !('$ref' in listItem)) {
70 | console.log('NewsList component configuration is not a map, unexpected format of configuration');
71 | return null;
72 | }
73 |
74 | const newsItemConfig = { label: 'News Item' };
75 |
76 | return ;
83 | });
84 |
85 | return (
86 |
87 |
88 | {listItems}
89 |
90 |
91 |
92 |
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/container-item.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import ContentComponentWrapper from './content-component-wrapper';
19 | import UndefinedComponent from './undefined';
20 | import { addBeginComment, addEndComment } from '../../utils/add-html-comment';
21 | import { ComponentDefinitionsContext, PageModelContext, PreviewContext } from '../../context';
22 |
23 | export default class CmsContainerItem extends React.Component {
24 | renderContainerItem(configuration, pageModel, preview, componentDefinitions) {
25 | if (preview && configuration) {
26 | return (
27 |
31 | );
32 | }
33 |
34 | if (configuration) {
35 | return this.renderContainerItemComponent(configuration, pageModel, preview, componentDefinitions);
36 | }
37 |
38 | return null;
39 | }
40 |
41 | renderContainerItemComponent(component, pageModel, preview, componentDefinitions) {
42 | // component not defined in component-definitions
43 | if (!(component.label in componentDefinitions)) {
44 | return ;
45 | }
46 |
47 | const componentDefinition = componentDefinitions[component.label];
48 | // based on the type of the component, render a different React component
49 | if ('wrapInContentComponent' in componentDefinition
50 | && componentDefinition.wrapInContentComponent) {
51 | // wrap component in ContentComponentWrapper class
52 | return ;
58 | }
59 |
60 | if (componentDefinition.component) {
61 | // component is defined and does not have to be wrapped in ContentComponent, so render the actual component
62 | return React.createElement(componentDefinition.component, {
63 | configuration: component,
64 | pageModel,
65 | preview,
66 | componentDefinitions,
67 | }, null);
68 | }
69 |
70 | return null;
71 | }
72 |
73 | addMetaData(htmlElm, configuration, preview) {
74 | addBeginComment(htmlElm, 'afterbegin', configuration, preview);
75 | addEndComment(htmlElm, 'beforeend', configuration, preview);
76 | }
77 |
78 | render() {
79 | const { configuration } = this.props;
80 |
81 | return (
82 |
83 | { (pageModel) =>
84 | { (preview) =>
85 | { (componentDefinitions) => this.renderContainerItem(
86 | configuration,
87 | pageModel,
88 | preview,
89 | componentDefinitions,
90 | ) }
91 | }
92 | }
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/examples/client-side-rendered/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import ReactDOM from 'react-dom';
19 | import {
20 | BrowserRouter,
21 | Link,
22 | Redirect,
23 | Route,
24 | Switch,
25 | } from 'react-router-dom';
26 | import { CmsPage, RenderCmsComponent } from 'bloomreach-experience-react-sdk';
27 |
28 | import Banner from './components/banner';
29 | import Content from './components/content';
30 | import CmsMenu from './components/menu';
31 | import NewsItem from './components/news-item';
32 | import NewsList from './components/news-list';
33 |
34 | const BR_ORIGIN = new URL(process.env.REACT_APP_BR_ORIGIN);
35 | const BR_CONTEXT_PATH = process.env.REACT_APP_BR_CONTEXT_PATH;
36 | const BR_CHANNEL_PATH = process.env.REACT_APP_BR_CHANNEL_PATH;
37 |
38 | const urlConfig = {
39 | scheme: BR_ORIGIN.protocol.slice(0, -1),
40 | hostname: BR_ORIGIN.hostname,
41 | port: BR_ORIGIN.port,
42 | contextPath: BR_CONTEXT_PATH,
43 | channelPath: BR_CHANNEL_PATH
44 | };
45 |
46 | const cmsUrls = {
47 | preview: urlConfig,
48 | live: urlConfig
49 | };
50 |
51 | const componentDefinitions = {
52 | Banner: { component: Banner, wrapInContentComponent: true },
53 | Content: { component: Content, wrapInContentComponent: true },
54 | 'News List': { component: NewsList },
55 | 'News Item': { component: NewsItem, wrapInContentComponent: true },
56 | };
57 |
58 | const createLink = (href, linkText, className) => {linkText()};
59 |
60 | class App extends React.Component {
61 | render() {
62 | // hostname and URL-path are used for detecting if site is viewed in CMS preview
63 | // and for fetching Page Model for the viewed page
64 | const request = { hostname: window.location.hostname, path: window.location.pathname + window.location.search };
65 |
66 | return (
67 |
68 |
69 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 | }
91 | }
92 |
93 | ReactDOM.render(
94 |
95 |
96 |
97 |
98 |
99 | ,
100 | document.getElementById('root'),
101 | );
102 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/container.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import CmsContainerItem from './container-item';
19 | import { addBeginComment, addEndComment } from '../../utils/add-html-comment';
20 | import { ComponentDefinitionsContext, PageModelContext, PreviewContext } from '../../context';
21 |
22 | export default class CmsContainer extends React.Component {
23 | renderContainerWrapper(configuration, pageModel, preview, componentDefinitions) {
24 | if (preview) {
25 | return (
26 | // need to wrap container inside a div instead of React.Fragment because otherwise HTML comments are not removed
27 |
33 | );
34 | }
35 |
36 | return this.renderContainer(configuration, pageModel, preview, componentDefinitions);
37 | }
38 |
39 | renderContainer(configuration = { components: [] }, pageModel, preview, componentDefinitions) {
40 | const { components, label } = configuration;
41 |
42 | // don't render anything when there're no components found
43 | if (!components || !components.length) {
44 | return null;
45 | }
46 |
47 | // get component item components
48 | const containerItemComponents = components.map((component) => (
49 |
50 | ));
51 |
52 | // check if component container is found in component definitions
53 | const ContainerComponent = componentDefinitions[label] && componentDefinitions[label].component;
54 |
55 | // if found then wrap container items with this component
56 | if (!ContainerComponent) {
57 | return (
58 |
59 | { containerItemComponents }
60 |
61 | );
62 | }
63 |
64 | return React.createElement(
65 | ContainerComponent,
66 | {
67 | configuration,
68 | pageModel,
69 | preview,
70 | componentDefinitions,
71 | },
72 | containerItemComponents,
73 | );
74 | }
75 |
76 | addMetaData(htmlElm, configuration, preview) {
77 | addBeginComment(htmlElm, 'beforebegin', configuration, preview);
78 | addEndComment(htmlElm, 'afterend', configuration, preview);
79 | }
80 |
81 | render() {
82 | if (!this.props.configuration) {
83 | return null;
84 | }
85 |
86 | return (
87 |
88 | {(pageModel) => (
89 |
90 | {(preview) => (
91 |
92 | {(componentDefinitions) => this.renderContainerWrapper(
93 | this.props.configuration,
94 | pageModel,
95 | preview,
96 | componentDefinitions,
97 | )}
98 |
99 | )}
100 |
101 | )}
102 |
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/examples/server-side-rendered/src/pages/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import getConfig from 'next/config';
19 | import Error from 'next/error';
20 | import Link from 'next/link';
21 | import { withRouter } from 'next/router';
22 |
23 | import fetch from 'isomorphic-unfetch';
24 | import { getApiUrl, CmsPage, RenderCmsComponent } from 'bloomreach-experience-react-sdk';
25 |
26 | import Banner from '../components/banner';
27 | import Content from '../components/content';
28 | import CmsMenu from '../components/menu';
29 | import NewsItem from '../components/news-item';
30 | import NewsList from '../components/news-list';
31 |
32 | const { publicRuntimeConfig } = getConfig();
33 | const brOrigin = new URL(publicRuntimeConfig.brOrigin);
34 |
35 | const urlConfig = {
36 | scheme: brOrigin.protocol.slice(0, -1),
37 | hostname: brOrigin.hostname,
38 | port: brOrigin.port,
39 | contextPath: publicRuntimeConfig.brContextPath,
40 | channelPath: publicRuntimeConfig.brChannelPath,
41 | };
42 |
43 | const cmsUrls = {
44 | preview: urlConfig,
45 | live: urlConfig,
46 | };
47 |
48 | const componentDefinitions = {
49 | Banner: { component: Banner, wrapInContentComponent: true },
50 | Content: { component: Content, wrapInContentComponent: true },
51 | 'News List': { component: NewsList },
52 | 'News Item': { component: NewsItem, wrapInContentComponent: true },
53 | };
54 |
55 | const createLink = (href, linkText, className) => {linkText()};
56 |
57 | export class Index extends React.Component {
58 | static async getInitialProps({ req, asPath }) {
59 | // setting pageModel to empty list instead of null value,
60 | // as otherwise the API will be fetched client-side again after server-side fetch errors
61 | let pageModel = {};
62 |
63 | // hostname and URL-path are used for detecting if site is viewed in CMS preview
64 | // and for fetching Page Model for the viewed page
65 | const request = {
66 | hostname: req.headers.host,
67 | path: asPath,
68 | };
69 | const url = getApiUrl(request, cmsUrls);
70 | const response = await fetch(url, { headers: { Cookie: req.headers.cookie } });
71 |
72 | if (response.ok) {
73 | try {
74 | pageModel = await response.json();
75 | } catch (err) {
76 | console.log(`Error! Could not convert response to JSON for URL: ${url}`);
77 | console.log(err);
78 | }
79 | } else {
80 | console.log(`Error! Status code ${response.status} while fetching CMS page data for URL: ${url}`);
81 | }
82 |
83 | return {
84 | pageModel,
85 | request,
86 | errorCode: !response.ok ? response.status : null,
87 | };
88 | }
89 |
90 | render() {
91 | const { errorCode, request } = this.props;
92 |
93 | if (errorCode) {
94 | return ();
95 | }
96 |
97 | return (
98 |
100 |
101 |
111 |
112 |
113 |
114 |
115 |
116 | );
117 | }
118 | }
119 |
120 | export default withRouter(Index);
121 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/cms-urls.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import pathToRegexp from 'path-to-regexp';
18 |
19 | export const FULLY_QUALIFIED_LINK = /\w+:\/\//;
20 |
21 | const defaultCmsUrls = {
22 | scheme: 'http',
23 | hostname: 'localhost',
24 | port: '8080',
25 | contextPath: 'site',
26 | channelPath: '',
27 | previewPrefix: '_cmsinternal',
28 | apiPath: 'resourceapi',
29 | apiComponentRenderingUrlSuffix: '?_hn:type=component-rendering&_hn:ref=',
30 | };
31 |
32 | function setUrlsWithDefault(urls = {}, defaultUrls = {}) {
33 | const newUrls = {
34 | scheme: urls.scheme || defaultUrls.scheme,
35 | hostname: urls.hostname || defaultUrls.hostname,
36 | port: urls.port !== undefined ? urls.port : defaultUrls.port,
37 | contextPath: urls.contextPath !== undefined ? urls.contextPath : defaultUrls.contextPath,
38 | channelPath: urls.channelPath || defaultUrls.channelPath,
39 | previewPrefix: urls.previewPrefix !== undefined ? urls.previewPrefix : defaultUrls.previewPrefix,
40 | apiPath: urls.apiPath || defaultUrls.apiPath,
41 | apiComponentRenderingUrlSuffix: urls.apiComponentRenderingUrlSuffix || defaultUrls.apiComponentRenderingUrlSuffix,
42 | };
43 |
44 | newUrls.baseUrl = `${newUrls.scheme}://${newUrls.hostname}`;
45 | if (newUrls.port) {
46 | newUrls.baseUrl = `${newUrls.baseUrl}:${newUrls.port}`;
47 | }
48 |
49 | return newUrls;
50 | }
51 |
52 | const cmsUrls = {};
53 |
54 | export function updateCmsUrls(urls = {}) {
55 | if (typeof urls !== 'object') {
56 | console.log('Warning! Supplied CMS URLs not of type object. Using default URLs.');
57 | urls = {};
58 | }
59 |
60 | cmsUrls.live = setUrlsWithDefault(urls.live, defaultCmsUrls);
61 | cmsUrls.preview = setUrlsWithDefault(urls.preview, cmsUrls.live);
62 |
63 | const pathregexp = `${cmsUrls.live.contextPath !== '' ? `/:contextPath(${cmsUrls.live.contextPath})?` : ''}`
64 | + `/:previewPrefix(${cmsUrls.live.previewPrefix})?`
65 | + `${cmsUrls.live.channelPath !== '' ? `/:channelPath(${cmsUrls.live.channelPath})?` : ''}`
66 | + '/:pathInfo*';
67 |
68 | cmsUrls.regexpKeys = [];
69 | cmsUrls.regexp = pathToRegexp(pathregexp, cmsUrls.regexpKeys);
70 |
71 | return cmsUrls;
72 | }
73 |
74 | export function buildApiUrl(pathInfo, query, preview, componentId, urls) {
75 | // when using fetch outside of CmsPage for SSR, cmsUrls need to be supplied
76 | if (!urls) {
77 | urls = cmsUrls;
78 | }
79 | urls = urls[preview ? 'preview' : 'live'];
80 |
81 | let url = urls.baseUrl;
82 | // add api path to URL, and prefix with contextPath and preview-prefix if used
83 | if (urls.contextPath !== '') {
84 | url += `/${urls.contextPath}`;
85 | }
86 | if (preview && urls.previewPrefix !== '') {
87 | url += `/${urls.previewPrefix}`;
88 | }
89 | if (urls.channelPath !== '') {
90 | url += `/${urls.channelPath}`;
91 | }
92 | url += `/${urls.apiPath}`;
93 | if (pathInfo) {
94 | url += `/${pathInfo}`;
95 | }
96 | // if component ID is supplied, URL should be a component rendering URL
97 | if (componentId) {
98 | url += urls.apiComponentRenderingUrlSuffix + componentId;
99 | }
100 | if (query) {
101 | url += (url.includes('?') ? '&' : '?') + query;
102 | }
103 |
104 | return url;
105 | }
106 |
107 | function hasPreviewQueryParameter(query) {
108 | return query.startsWith('bloomreach-preview=true')
109 | || query.indexOf('&bloomreach-preview=true') !== -1;
110 | }
111 |
112 | // if hostname is different for preview and live,
113 | // then hostname can be used to detect if we're in preview mode
114 | function isMatchingPreviewHostname(hostname, urls) {
115 | return urls.live.hostname !== urls.preview.hostname
116 | && hostname === urls.preview.hostname;
117 | }
118 |
119 | export function parseRequest(request = {}, urls) {
120 | if (!urls) {
121 | urls = cmsUrls;
122 | }
123 |
124 | const [urlPath, query = ''] = request.path.split('?', 2);
125 | const [hostname] = request.hostname.split(':', 2);
126 | const results = urls.regexp.exec(urlPath);
127 | let preview = hasPreviewQueryParameter(query) || isMatchingPreviewHostname(hostname, urls);
128 | if (!preview && results) {
129 | const previewIdx = urls.regexpKeys.findIndex((obj) => obj.name === 'previewPrefix');
130 | preview = results[previewIdx + 1] !== undefined;
131 | }
132 |
133 | let path = '';
134 | if (results) {
135 | const pathIdx = urls.regexpKeys.findIndex((obj) => obj.name === 'pathInfo');
136 | // query parameter is not needed for fetching API URL and can actually conflict with component rendering URLs
137 | path = results[pathIdx + 1] || '';
138 | }
139 |
140 | return { path, preview, query };
141 | }
142 |
143 | export function getApiUrl(request, newCmsUrls = {}) {
144 | // eslint-disable-next-line no-shadow
145 | const cmsUrls = updateCmsUrls(newCmsUrls);
146 | const parsedRequest = parseRequest(request, cmsUrls);
147 |
148 | return buildApiUrl(parsedRequest.path, parsedRequest.query, parsedRequest.preview, null, cmsUrls);
149 | }
150 |
151 | updateCmsUrls();
152 |
153 | export default cmsUrls;
154 |
--------------------------------------------------------------------------------
/packages/sdk/src/cms-components/core/page.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Hippo B.V. (http://www.onehippo.com)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import {
19 | ComponentDefinitionsContext,
20 | CreateLinkContext,
21 | PageModelContext,
22 | PreviewContext,
23 | } from '../../context';
24 | import { addBodyComments } from '../../utils/add-html-comment';
25 | import { updateCmsUrls, parseRequest } from '../../utils/cms-urls';
26 | import { fetchCmsPage, fetchComponentUpdate } from '../../utils/fetch';
27 | import findChildById from '../../utils/find-child-by-id';
28 |
29 | export default class CmsPage extends React.Component {
30 | constructor(props) {
31 | super(props);
32 | updateCmsUrls(this.props.cmsUrls);
33 |
34 | this.state = parseRequest(this.props.request);
35 | this.state.createLink = this.props.createLink;
36 | if (typeof this.props.componentDefinitions === 'object') {
37 | this.state.componentDefinitions = this.props.componentDefinitions;
38 | }
39 |
40 | if (this.props.pageModel) {
41 | this.state.pageModel = this.props.pageModel;
42 | }
43 | }
44 |
45 | async fetchPageModel(path, query, preview) {
46 | if (this.props.debug) {
47 | console.log('### React SDK debugging ### fetching page model for URL-path \'%s\'', path);
48 | }
49 | const data = await fetchCmsPage(path, query, preview);
50 | this.updatePageModel(data);
51 | }
52 |
53 | updatePageModel(pageModel) {
54 | if (pageModel) {
55 | addBodyComments(pageModel.page, this.state.preview);
56 | }
57 |
58 | this.setState({
59 | pageModel,
60 | });
61 | if (this.state.preview && this.cms && typeof this.cms.createOverlay === 'function') {
62 | if (this.props.debug) {
63 | console.log('### React SDK debugging ### creating CMS overlay');
64 | }
65 | this.cms.createOverlay();
66 | }
67 | }
68 |
69 | initializeCmsIntegration() {
70 | if (!this.state.preview || typeof window === 'undefined') {
71 | return;
72 | }
73 |
74 | window.SPA = {
75 | renderComponent: this.updateComponent.bind(this),
76 | init: (cms) => {
77 | this.cms = cms;
78 | if (this.state.pageModel) {
79 | if (this.props.debug) {
80 | console.log('### React SDK debugging ### creating CMS overlay');
81 | }
82 | cms.createOverlay();
83 | }
84 | },
85 | };
86 | }
87 |
88 | async updateComponent(componentId, propertiesMap) {
89 | if (this.props.debug) {
90 | console.log('### React SDK debugging ### component update triggered for \'%s\' with properties:', componentId);
91 | console.dir(propertiesMap);
92 | }
93 | // find the component that needs to be updated in the page structure object using its ID
94 | const componentToUpdate = findChildById(this.state.pageModel, componentId);
95 | if (componentToUpdate == null) {
96 | return;
97 | }
98 |
99 | const response = await fetchComponentUpdate(
100 | this.state.path,
101 | this.state.query,
102 | this.state.preview,
103 | componentId,
104 | propertiesMap,
105 | );
106 | // API can return empty response when component is deleted
107 | if (!response) {
108 | return;
109 | }
110 |
111 | if (response.page) {
112 | componentToUpdate.parent[componentToUpdate.idx] = response.page;
113 | }
114 |
115 | const pageModel = { ...this.state.pageModel };
116 | if (response.content) {
117 | pageModel.content = { ...pageModel.content, ...response.content };
118 | }
119 |
120 | this.setState({ pageModel });
121 | }
122 |
123 | componentDidUpdate(prevProps, prevState) {
124 | if (this.props.pageModel !== prevProps.pageModel) {
125 | this.updatePageModel(this.props.pageModel);
126 | } else if (this.props.request.path !== prevProps.request.path) {
127 | const parsedUrl = parseRequest(this.props.request);
128 | this.fetchPageModel(parsedUrl.path, parsedUrl.query, parsedUrl.preview);
129 | }
130 |
131 | if (this.state.pageModel !== prevState.pageModel && this.cms) {
132 | this.cms.createOverlay();
133 | }
134 | }
135 |
136 | componentDidMount() {
137 | this.initializeCmsIntegration();
138 | // fetch page model if not supplied
139 | if (!this.state.pageModel) {
140 | this.fetchPageModel(this.state.path, this.state.query, this.state.preview);
141 | } else {
142 | // add body comments client-side as document variable is undefined server-side
143 | addBodyComments(this.state.pageModel.page, this.state.preview);
144 | }
145 | }
146 |
147 | render() {
148 | const { pageModel } = this.state;
149 |
150 | if (!pageModel || !pageModel.page) {
151 | return null;
152 | }
153 |
154 | return (
155 |
156 |
157 |
158 |
159 | { typeof this.props.children === 'function' ? this.props.children() : this.props.children }
160 |
161 |
162 |
163 |
164 | );
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/packages/sdk/README.md:
--------------------------------------------------------------------------------
1 | # BloomReach Experience SDK for React
2 |
3 | > The BloomReach Experience SDK for React works only with [Bloomreach Experience](https://www.bloomreach.com/en/products/experience-manager) version 13 and below. Please use [@bloomreach/react-sdk](https://www.npmjs.com/package/@bloomreach/react-sdk) with all the latest versions of Bloomreach Experience.
4 |
5 | SDK for powering content and components in React applications by [BloomReach Experience](https://www.bloomreach.com/en/products/experience).
6 | This library makes integrating a React app with BloomReach Experience a breeze. It supports both
7 | client-side- and server-side rendered/isomorphic React apps.
8 |
9 | BloomReach Experience allows you to use an external front-end such as React for rendering while still
10 | providing a native-like authoring experience, such as integrated preview, in-context editing, drag &
11 | drop, server-side personalization. For more information on this approach, see [A new approach to
12 | integrating SPA's with WCM: Fixing what's wrong with headless integrations](https://www.bloomreach.com/en/blog/2018/03/a-new-approach-to-integrating-spas-with-wcm-fixing-what%E2%80%99s-wrong-with-headless-integrations.html).
13 |
14 | ## Install
15 |
16 | ```bash
17 | npm install --save bloomreach-experience-react-sdk
18 | ```
19 |
20 | ## Usage
21 |
22 | ```jsx
23 | import React from 'react'
24 | import { CmsPage, RenderCmsComponent } from 'bloomreach-experience-react-sdk'
25 |
26 | const componentDefinitions = {
27 | "MyCustomComponent": { component: MyCustomComponent }
28 | }
29 |
30 | class MyApp extends React.Component {
31 | render() {
32 | const request = { hostname: window.location.hostname, path: window.location.pathname + window.location.search };
33 | return (
34 |
35 |
36 |
37 | )
38 | }
39 | }
40 | ```
41 |
42 | At a minimum, `` and `` should be imported in the React app.
43 |
44 | `` fetches the Page Model when it is not supplied as a prop (see section on server-side
45 | rendering); it fetches updates to the page model on component changes in the CMS; and it initializes and
46 | manages state, and provides this as context to ``. The `` component can be
47 | put anywhere within the React App.
48 |
49 | `` renders components based on the Page Model API response. It consumes the state
50 | from `` and renders the container components and any content referenced from the components as
51 | contained in the Page Model API response. The `` should be nested within ``
52 | and placed in the React app at the exact location where you want BloomReach Experience to control what
53 | components and content is rendered.
54 |
55 | `` takes two props: `componentDefinitions` and `request`.
56 |
57 | The `request` prop is used to fetch the Page Model for the current page; and to detect whether
58 | preview mode is active so that meta-data for Channel Manager functionality (e.g. in-context editing) is
59 | included in the HTML, and consequently Channel Manager functionality is enabled.
60 |
61 | Finally, component definitions are supplied through the `componentDefinitions` prop. The component
62 | definitions tell `` what React component to render by mapping these to the
63 | *hst:label* of a CMS catalog component.
64 |
65 | ## Server-side rendering
66 |
67 | For server-side rendering (e.g. using [Next.js](https://github.com/zeit/next.js)) you need to fetch the
68 | Page Model API server-side and supply it as a prop to ``. Apart from this the usage is the same
69 | as with client-side rendering.
70 |
71 | The helper function `getApiUrl()` can be used to generate the Page Model API URL using the current
72 | request.
73 |
74 | It is important to pass cookies from the initial request and supply these with the request header of the
75 | Page Model API request. Otherwise you will get a 403 error in preview mode.
76 |
77 | Finally, to get preview to work for server-side rendered apps you can use a reverse proxy to route
78 | requests from BloomReach's site preview to the React App-server. For more details, see section *3.2
79 | Server-side apps* of the Hippo Lab: [Integrate a React application with BloomReach Experience](https://www.onehippo.org/labs/integrate-a-react-application-with-bloomreach-experience.html).
80 |
81 | ```jsx
82 | import fetch from 'isomorphic-unfetch'
83 | import { getApiUrl, CmsPage, RenderCmsComponent } from 'bloomreach-experience-react-sdk'
84 |
85 | // setting pageModel to empty list instead of null value,
86 | // as otherwise the API will be fetched client-side again after server-side fetch errors
87 | let pageModel = {}
88 |
89 | const request = { hostname: req.headers.host, path: asPath }
90 | const url = getApiUrl(request, {})
91 | // pass cookies of initial request for CMS preview
92 | const response = await fetch(url, {headers: {'Cookie': req.headers.cookie}})
93 |
94 | if (response.ok) {
95 | try {
96 | pageModel = await response.json()
97 | } catch (err) {
98 | console.log(`Error! Could not convert response to JSON for URL: ${url}`)
99 | }
100 | } else {
101 | console.log(`Error! Status code ${response.status} while fetching CMS page data for URL: ${url}`)
102 | }
103 | ```
104 |
105 | ## Example React apps
106 |
107 | There are two example React apps available that use the SDK. One client-side rendered, and one
108 | server-side rendered using [Next.js](https://github.com/zeit/next.js). You can find the [example apps on
109 | Github](https://github.com/bloomreach/experience-react-sdk/tree/master/examples).
110 |
111 | ## Creating custom components
112 |
113 | Any components that are supplied through the `compenentDefinitions` prop can be any type of valid React
114 | component, including functional components. Props are passed to these components as context so that you
115 | can access the components' configuration, content, etc. See more information below.
116 |
117 | ### Props
118 |
119 | The following props are passed to a component that is rendered by ``:
120 | - `configuration` - `Object` component configuration. Contains the contributed models, raw parameters
121 | and resolved parameters. Content included in the component's model is not serialized as part of the
122 | component's configuration but in a separate content object, and a JSON Pointer reference is used to
123 | link to the actual content object. This is done to prevent content from being included multiple times
124 | in the API response when referenced multiple times on a page.
125 | - `pageModel` - `Object` full Page Model API response.
126 | - `preview` - `Boolean` is *true* if preview mode is active based on current URL supplied through
127 | `request` prop of ``.
128 |
129 | For more information on the Page Model API response, see the [Swagger documentation](https://www.onehippo.org/library/concepts/spa-plus/page-model-api/swagger-api-documentation.html).
130 |
131 | ### Example
132 |
133 | ```jsx
134 | import React from 'react'
135 | import NewsItem from './news-item'
136 | import { getNestedObject } from 'bloomreach-experience-react-sdk'
137 |
138 | export default class NewsList extends React.Component {
139 | render() {
140 | const preview = this.props.preview
141 | const pageModel = this.props.pageModel
142 | const configuration = this.props.configuration
143 |
144 | // return placeholder if no list is set on component
145 | const list = getNestedObject(configuration, ['models', 'pageable', 'items'])
146 | if (!list && preview) {
147 | return (
166 | )
167 | }
168 | }
169 | ```
170 |
171 | ### Content components
172 |
173 | Components that reference a single content-item (e.g. the Banner component) can use a convenient
174 | wrapper-class that looks up the content and passes it as a prop. See below.
175 |
176 | To enable this, the property `wrapInContentComponent: true` has to be set on the component in the
177 | `componentDefinitions` prop. See `componentDefinitions` in the API section for more details.
178 |
179 | #### Props
180 |
181 | - `content` - `Object` raw content object that contains the content-item's fields and field-values. Any
182 | references to other content-items (e.g. images) are serialized as JSON Pointers.
183 | - `manageContentButton` - `React.Component` for placement and rendering of the [Manage Content Button](https://www.onehippo.org/library/concepts/component-development/render-manage-content-button.html)
184 | in preview mode in CMS.
185 | - `pageModel` - `Object` full Page Model API response.
186 | - `preview` - `Boolean` is *true* if preview is active based on current URL supplied through `request`
187 | prop of ``.
188 |
189 | #### Example
190 |
191 | ```jsx
192 | import React from 'react'
193 |
194 | const content = this.props.content;
195 | const manageContentButton = this.props.manageContentButton;
196 |
197 | class Banner extends React.Component {
198 | render() {
199 | return (
200 |
)
204 | }
205 | }
206 | ```
207 |
208 | ### Static CMS components
209 |
210 | Static CMS components are components that are defined by developers / administrators and cannot be
211 | modified by users in the CMS. However, any content or site menus these components reference can be
212 | changed by users in the CMS.
213 |
214 | Since `` only renders container components (drag-and-drop components) by default,
215 | you have to specify two additional properties in order to render a static CMS component: the `path`
216 | property to point to the relative path of the component, and `renderComponent` to specify which React
217 | component to use for rendering the component. See the example below.
218 |
219 | ```jsx
220 |
221 | ```
222 |
223 | ### Containers
224 |
225 | Containers are being used to hold container items, which will be rendered by the SDK. Whenever it needs to customize a layout of those container items, it is possible also to pass a custom container component in `componentDefinitions`.
226 |
227 | #### Example
228 | ```jsx
229 | import React from 'react'
230 | import { CmsPage, RenderCmsComponent } from 'bloomreach-experience-react-sdk'
231 |
232 | function MyCustomFooter(props) {
233 | return (
234 |
239 | );
240 | }
241 |
242 | const componentDefinitions = {
243 | "Footer Container": { component: MyCustomFooter }
244 | }
245 |
246 | // ...
247 |
248 | class MyApp extends React.Component {
249 | render() {
250 | return (
251 |
252 |
253 |
254 | )
255 | }
256 | }
257 | ```
258 |
259 | ### More component examples
260 |
261 | For more detailed examples, see the components included in the [example applications](https://github.com/bloomreach/experience-react-sdk/tree/master/examples/client-side-rendered/src/components).
262 |
263 | ### Helper functions
264 |
265 | Additionally, there are a variety of helper functions available. See the examples below. For full
266 | details on the APIs, see the API section.
267 |
268 | ```jsx
269 | import { createLink, getImageUrl, getNestedObject, parseAndRewriteLinks, parseDate } from 'bloomreach-experience-react-sdk'
270 |
271 | const link = createLink('ref', link, linkText, className)
272 | const image = getImageUrl(content.image, this.props.pageModel, this.props.preview)
273 | const list = getNestedObject(configuration, ['models', 'pageable', 'items', 0])
274 | const contentHtml = parseAndRewriteLinks(content.content.value, this.props.preview)
275 | const date = parseDate(content.date)
276 | ```
277 |
278 | ## API
279 |
280 | ### ``
281 |
282 | The CmsPage component is a higher-order component that takes care of:
283 | - Fetching the Page Model for client-side rendering, when not supplied as a prop.
284 | - Fetching Page Model updates on component changes in the CMS.
285 | - Initializing and managing state, and providing this as context to ``.
286 |
287 | #### Properties
288 |
289 | - `cmsUrls` - `Object` Override default CMS URL's. (Optional)
290 | - `componentDefinitions` - `object` Mapping of CMS catalog components to React components. Determines
291 | what component to render based from the Page Model. (Optional)
292 | - `createLink` - `Function` Called when creating internal links so that links can be constructed using
293 | the router library of the React app. (Optional)
294 | - `pageModel` - `Object` Supply Page Model as prop. Used for server-side-rendering where Page Model API
295 | is fetched server-side. When supplied, `CmsPage` will not fetch Page Model API on mount, only on
296 | component updates in CMS. (Optional)
297 | - `request` - `String` Current URL-path for determining if preview mode is active, and for fetching the
298 | Page Model for the page that is active. (Required)
299 |
300 | ###### `cmsUrls` property
301 |
302 | Property that allows you to override the default URL's for fetching the Page Model API. Typically you
303 | will only have to define `scheme`, `hostname`, `port`, and `contextPath`. Input object takes the
304 | following properties (all are optional):
305 | - `scheme` - `String` scheme (default: *http*)
306 | - `hostname` - `String` hostname (default: *localhost*)
307 | - `port` - `number` port number (default: *8080*)
308 | - `contextPath` - `String` site context-path (default: *site*)
309 | - `channelPath` - `String` path to the used channel, if channel is accessed through a subpath
310 | - `previewPrefix` - `String` preview-prefix used by CMS (default: *_cmsinternal*)
311 | - `apiPath` - `String` path to Page Model API as subpath (default: *resourceapi*)
312 | - `apiComponentRenderingUrlSuffix` - `String` (default: *?_hn:type=component-rendering&_hn:ref=*)
313 |
314 | ###### `componentDefinitions` property
315 |
316 | Maps CMS catalog components to React components. Expects as input an object with `hst:label` of the CMS
317 | components as keys and as value another object. The nested object has the mandatory property `component`
318 | who's value maps the CMS component to a React component. See the example below:
319 |
320 | ```js
321 | const componentDefinitions = {
322 | "MyCustomCmsComponent": { component: MyCustomReactComponent },
323 | "AnotherCmsComponent": { component: AnotherReactComponent, wrapInContentComponent: true }
324 | };
325 | ```
326 |
327 | Additionally, the property `wrapInContentComponent: true` can be used for components that reference a
328 | single content-item. When this property is set on a component, it will be wrapped in a convenient
329 | wrapper class. See section *Content components*.
330 |
331 | ##### `createLink` property
332 |
333 | Called when creating internal links so that links can be constructed using the router library of the
334 | React app.
335 |
336 | Takes `Function` as input. The function should return valid JSX and have three parameters:
337 | - `href` - `String` href of link
338 | - `linkText` - `Function` contains the HTML that is wrapped inside the link. Is a function so that it
339 | can include HTML.
340 | - `className` - `String` classnames to add to the link element
341 |
342 | For example:
343 | ```jsx
344 | const createLink = (href, linkText, className) => {
345 | return ({linkText()})
346 | }
347 | ```
348 |
349 | ##### `pageModel` property
350 |
351 | Page Model can be supplied as a prop when using a server-side rendered / isomorphic React framework.
352 | When supplied, `` will not fetch the Page Model API client-side.
353 |
354 | ##### `request` property
355 |
356 | Takes `Object` as input with properties `hostname` and `path`, both of type `String`. The property
357 | `hostname` should contain the hostname for the current request (client-side this is
358 | window.location.hostname). The property `path` should contain the URL-path for the current request
359 | (client-side this is window.location.pathname);
360 |
361 | ### ``
362 |
363 | Renders a CMS component and all of its children using the Page Model supplied by ``. Will
364 | render the entire Page Model by default.
365 |
366 | #### Properties
367 |
368 | - `path` - `String` path to a component (static CMS component), container or container-item in the Page
369 | Model to render only that component and its children. If no path is supplied, entire Page Model will be
370 | rendered.
371 | - `renderComponent` - `React.Component` render a static CMS component using specified React component.
372 | Only works in combination with `path` property, which should specify path to the static CMS component.
373 | Site menus that are rendered this way can leverage the `` component for rendering edit
374 | buttons in the CMS.
375 |
376 | #### Example
377 |
378 | ```jsx
379 |
380 | ```
381 |
382 | ### ``
383 |
384 | Inserts meta-data for either a content-item or site menu for placing an edit button in preview mode in
385 | the CMS. Content-items that are rendered by a content component using `wrapInContentComponent: true` do
386 | not need to use this component, but should use the `manageContentButton` prop that passes the meta-data.
387 |
388 | #### Properties
389 |
390 | - `configuration` - `Object` configuration of the site menu (not the component configuration containing
391 | the menu) or the content-item object which has the `_meta` object in its root.
392 | - `preview` - `Boolean` toggle to prevent edit buttons from being rendered outside of preview.
393 |
394 | ### `getApiUrl(request, [newCmsUrls])`
395 |
396 | Helper function for generating URL to the Page Model API for server-side fetching of the Page Model.
397 |
398 | #### Arguments
399 |
400 | - `request` - `Object` containing hostname and URL-path as properties `hostname` and `path`
401 | respectively. See the `request` property section above.
402 | - `newCmsUrls` - `Object` takes `cmsUrls` property as input to override default CMS URL's
403 |
404 | #### Return types
405 |
406 | `String` returns URL for fetching Page Model API
407 |
408 | ### `createLink(linkType, link, linkText, className)`
409 |
410 | Creates a link to either a component or content-item itself or a referenced content-item (can be a
411 | document, image or asset) and returns the link as JSX.
412 |
413 | #### Arguments
414 |
415 | - `linkType` - `String` type of link to create. Valid values are `self`, `ref` or `href`.
416 | - `self` - Create link to the component or content-item itself. E.g. for a news-item in a news
417 | overview.
418 | - `ref` - Create link to a referenced content-item. E.g. for a banner.
419 | - `href` - Used by `parseAndRewriteLinks()` to create links using a href only.
420 | - `link` - `Object` the component configuration or content-object that is linking to itself or
421 | referenced another content-item.
422 | - `linkText` - `JSX | Function` HTML to wrap inside the link. Is a function so that it can include HTML.
423 | - `className` - `String` classnames to add to the link element
424 |
425 | #### Return types
426 |
427 | `JSX` returns link as JSX object that can be included as a variable anywhere within the return section
428 | of a React component's render method.
429 |
430 | ### `getImageUrl(imageRef, pageModel, preview)`
431 |
432 | Creates link to URL of image in case BloomReach Experience is used for serving images.
433 |
434 | #### Arguments
435 |
436 | - `imageRef` - `String` JSON Pointer that references the image.
437 | - `pageModel` - `Object` since this function is a pure JavaScript function it can't get Page Model from
438 | context, so it has to be provided as function parameter. The Page Model is used to retrieve the image.
439 | - `preview` - `Boolean` toggle for whether preview mode is active. Components rendered by
440 | `` can pass the prop `this.props.preview`.
441 |
442 | #### Return types
443 |
444 | `String` returns URL to image.
445 |
446 | ### `getNestedObject(nestedObject, pathArray)`
447 |
448 | Returns a nested object or value using a path array. Useful when you need to access deeply nested
449 | objects/values without having to string null checks together.
450 |
451 | #### Arguments
452 |
453 | - `nestedObject` - `Object` the object containing the nested object or value.
454 | - `pathArray` - `Array` contains the path to the nested object as an array.
455 |
456 | #### Return types
457 |
458 | `Object|null` returns the nested object if found, otherwise returns null.
459 |
460 | ### `parseAndRewriteLinks(html, preview)`
461 |
462 | Parses HTML of a rich-text field of a content-item for rewriting links in HTML to internal links. Uses
463 | the `createLink` function passed to `` for constructing internal links.
464 |
465 | #### Arguments
466 |
467 | - `html` - `String` value of rich-text field of a content-item. Should contain HTML only.
468 | - `preview` - `Boolean` toggle for whether preview mode is active. Components rendered by
469 | `` can pass the prop `this.props.preview`.
470 |
471 | #### Return types
472 |
473 | `JSX` returns parsed and rewrited HTML as JSX.
474 |
475 | ### `parseDate(date)`
476 |
477 | Parses date-field of a content item and returns date as a string.
478 |
479 | #### Arguments
480 |
481 | - `date` - `String` takes raw value of a date-field as input, following the ISO 8601:2000 format.
482 |
483 | #### Return types
484 |
485 | `String` returns date in full date format.
486 |
487 | ## Release notes
488 |
489 | ### Version 0.6.4
490 | - Fix bug in the object type check in the `findChildById` function.
491 |
492 | ### Version 0.6.3
493 | - Add support of fully-qualified resource links.
494 |
495 | ### Version 0.6.2
496 | - Fixed page model fetch on the component update.
497 |
498 | ### Version 0.6.1
499 | - Fixed a bug with query string passing to the Page Model API.
500 | - Fixed a bug with missing key prop in the link rewriter.
501 |
502 | ### Version 0.6.0
503 | - Added eslint.
504 | - Migrated to rollup.
505 | - Migrated to yarn.
506 | - Fixed CmsPage children to not be wrapped around a function.
507 | - Added support of custom React components for container components.
508 |
509 | ### Version 0.5.2
510 | - Fixed bug with query string affecting on the path parsing.
511 |
512 | ### Version 0.5.1
513 | - Fixed bug with preview update on the component properties dialog changes;
514 | - Fixed bug with preview update on save/discard changes from the component properties dialog;
515 | - Fixed bug with the manage content button keeps referring to the old content after saving changes in the dialog.
516 |
517 | ### Version 0.5.0
518 | - Fixed bug with SSO handshake in client-side rendered applications.
519 |
520 | Upgrade steps:
521 | - Pass query string parameters along with other request details to ``:
522 | ```jsx
523 | const request = { hostname: window.location.hostname, path: window.location.path + window.location.search };
524 |
525 | ```
526 |
527 | ### Version 0.4.0
528 |
529 | Added support for rendering static CMS components.
530 | - Added new property `renderComponent` to `` which allows you to render a static
531 | component. This only works in combination with the `path` property, which should contain the relative
532 | path to the component.
533 | - Renamed `` to `` and exported it so it can be used by apps. This
534 | component is now more generic so it can also be used to generate edit buttons in the CMS for site menus.
535 | - Moved `` component out of SDK and into the example apps.
536 |
537 |
538 | ### Version 0.3.0
539 |
540 | Added extra checks so that `
` elements needed for CMS preview are only inserted in preview mode.
541 |
542 | ### Version 0.2.0
543 |
544 | This version includes significant changes. Please make sure to update your components using the upgrade
545 | steps further down.
546 | - Changed `cmsUrls` property to support different live and preview URLs.
547 | - Changed `urlPath` property to `request` property which no longer takes URL-path as string, but an
548 | object with hostname and URL-path. This property should be passed to ``.
549 | - Changed helper method `getApiUrl()` to take `request` property as parameter instead of `urlPath`.
550 | - Changed helper method `parseAndRewriteLinks()` to take an extra parameter: `preview`.
551 | - Changed helper method `getImageUrl()` to take an extra parameter: `preview`.
552 |
553 | Upgrade steps:
554 | - Modify `cmsUrls` property to include URLs for live and preview using the properties `live` and
555 | `preview` respectively. Please note that the `cms` prefix has been removed from all URL properties. So `cmsHostname` has become `hostname`. Preview URLs are optional. For example:
556 | ```js
557 | const cmsUrls = {
558 | live: {
559 | hostname: bloomreach.com
560 | },
561 | preview: {
562 | hostname: cms.bloomreach.com
563 | }
564 | }
565 | ```
566 | - Pass current request details through `request` property to ``. This was previously done
567 | through the `urlPath` property. The `request` property should be an object that contains the hostname
568 | and path as properties `hostname` and `path` respectively. For example:
569 | ```jsx
570 | const request = { hostname: window.location.hostname, path: window.location.path };
571 |
572 | ```
573 | - Update usage of `getApiUrl` to pass `request` property (see previous bullet) instead of URL-path.
574 | - Update usage of helper methods `getImageUrl()` and `parseAndRewriteLinks()` to include the preview
575 | parameter. Components rendered by `` have the preview value passed as prop
576 | `this.props.preview`.
577 |
578 | ## FAQ / Troubleshooting
579 |
580 | - Information about common problems and possible solutions can be found on [the troubleshooting page](https://documentation.bloomreach.com/library/concepts/spa-integration/troubleshooting.html).
581 | - Information about the recommended setup can be found on [the best practices page](https://documentation.bloomreach.com/library/concepts/spa-integration/best-practices.html).
582 |
583 | ## License
584 |
585 | Apache 2.0
586 |
--------------------------------------------------------------------------------