From 8cfab86944b027efbda6d0399e4030c8a949635b Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Tue, 31 May 2022 16:08:23 -0400 Subject: [PATCH 1/7] ENDOC-502 Update the MFE config tutorial --- .../create/mfe/widget-configuration.md | 542 +++++++----------- 1 file changed, 210 insertions(+), 332 deletions(-) diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md index 7df984da0e..4671092550 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md @@ -1,295 +1,68 @@ # Add a Configuration Screen in App Builder -Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. It can be developed and tested in isolation without a running Entando instance. - -## Create React App - -Let’s start with the boilerplate provided by [Create React -App](https://create-react-app.dev/), probably the most popular one. - -`npx create-react-app my-widget-config --use-npm` - - my-widget-config - ├── README.md - ├── node_modules - ├── package.json - ├── .gitignore - ├── public - │ ├── favicon.ico - │ ├── index.html - │ ├── logo192.png - │ ├── logo512.png - │ ├── manifest.json - │ └── robots.txt - └── src - ├── App.css - ├── App.js - ├── App.test.js - ├── index.css - ├── index.js - ├── logo.svg - ├── serviceWorker.js - └── setupTests.js - -Then, type `cd my-widget-config` and `npm start` to start the app. - -## Add Input Field - -Let’s start with a simple form: only an input with a label. So, let’s -edit `App.js` +Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. - import React from 'react'; - - class App extends React.Component { - constructor(props) { - super(props); - this.state = { name: ''}; - } - - handleNameChange(value) { - this.setState(prevState => ({ - ...prevState, - name: value, - })); - } - - render() { - const { name } = this.state; - return ( -
-

Sample Entando Widget Configuration

- - this.handleNameChange(e.target.value)} value={name} /> -
- ); - } - } - - export default App; +There are 3 steps to this tutorial +1. Modify an existing MFE (the target MFE) to take a configuration option +2. Create a new MFE (the config MFE) to provide a user interface for the configuration option +3. Set up the target MFE to use the configuration provided by the config MFE -You are free to use your favorite form handling library e.g., -[Formik](https://jaredpalmer.com/formik), -[redux-form](https://redux-form.com/) (that requirese redux) or others. +## Prerequisites +- [A working instance of Entando](../../../docs/getting-started/) +- [An existing React MFE](./react.md) -In regards to styling, since this is going to be an App Builder screen, -we strongly suggest using [PatternFly -v3](https://www.patternfly.org/v3/) (`patternfly` and `patternfly-react` -packages) to keep UX coherence. - -## Custom Element +## Add a configuration option to your target MFE +Start by adding a configuration option to an existing MFE. If you don't already have one, you can create it via the [React MFE tutorial](./react.md). -Now, let’s add the web component that will wrap the entire React app. -Let’s name it `WidgetElement` +### Add an Attribute to the Custom Element - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; +1. Edit `src/WidgetElement.js` to add attribute handling to the custom element and to re-render the App when an attribute changes. This enables the *name* attribute on the custom element to be passed as a prop to +the React root component (*App*). + +``` javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; - class WidgetElement extends HTMLElement { - constructor() { - super(); - this.reactRootRef = React.createRef(); - this.mountPoint = null; - } +const ATTRIBUTES = { + name: 'name', +}; - get config() { - return this.reactRootRef.current ? this.reactRootRef.current.state : {}; - } +class WidgetElement extends HTMLElement { - set config(value) { - return this.reactRootRef.current.setState(value); - } + static get observedAttributes() { + return Object.values(ATTRIBUTES); + } - connectedCallback() { - this.mountPoint = document.createElement('div'); - this.appendChild(this.mountPoint); - ReactDOM.render(, this.mountPoint); - } + attributeChangedCallback(name, oldValue, newValue) { + if (!Object.values(ATTRIBUTES).includes(name)) { + throw new Error(`Untracked changed attribute: ${name}`); } - - customElements.define('my-widget-config', WidgetElement); - - export default WidgetElement; - -Its responsibility is rendering the React app and syncing the React app -state in a `config` property. That *must* be named that way. The key to -App Builder communication is that it works in three steps: - -- App Builder reads `config` property when the widget config screen is - rendered - -- `config` property is mutated when a user configures the widget - -- When a user saves the config, App Builder retrieves it (again, from - the `config` property) and persists it through Entando APIs - -This means the widget developer can focus on the configuration screens -without having to call Entando APIs to read or write configuration. - -One more JS file to update: `index.js`. Starting from this - - import React from 'react'; - import ReactDOM from 'react-dom'; - import './index.css'; - import App from './App'; - import * as serviceWorker from './serviceWorker'; - - ReactDOM.render(, document.getElementById('root')); - - // If you want your app to work offline and load faster, you can change - // unregister() to register() below. Note this comes with some pitfalls. - // Learn more about service workers: https://bit.ly/CRA-PWA - serviceWorker.unregister(); - -You only have to import `WidgetElement` plus the css, if needed. -Something like - - import './index.css'; - import './WidgetElement'; - -We assume we don’t need a service worker for the widget, so we can -delete serviceWorker.js. - -To ensure our web component is working we have to edit -`public/index.html`. Remove `
` from the `body` (we -programmatically generated the react root in the `connectedCallback` -method of `WidgetElement`) and add our new web component tag -``. - - - - - - - - React App - - - - - - -> **Note** -> -> the web component tag name (`my-widget-config` in this tutorial) -> *must* match the first parameter of the `customElements.define` -> method. - -The page should auto reload and... congrats, you’re running an Entando -widget in isolation. - -## Configuration Screen - -Next, we’ll build our widget before embedding it into the Entando -instance. From the react project root, type - -`npm run build` - -and a `build/static` directory will be generated. For convenience in this tutorial, rename the generated files: - -- a file like `js/runtime~main.c7dcdf0b.js` to `js/runtime.js` - (bootstrapping logic) - -- a file like `js/2.230b21ef.chunk.js` to `js/vendor.js` (third-party - libraries) - -- a file like `js/main.1fd3965a.chunk.js` to `js/main.js` (app) - -Next load these files into Entando under `public/my-widget-config/static` using `Administration` → `File Browser`. - -Now go to `Components` → `Micro frontends & Widgets` and find the original widget we're creating the configuration screen for. Edit the widget and update the -**`configUI`** field. - - { - "customElement": "my-widget-config", - "resources": [ - "my-widget-config/static/js/runtime.js", - "my-widget-config/static/js/vendor.js", - "my-widget-config/static/js/main.js" - ] + if (this.mountPoint && newValue !== oldValue) { + this.render(); } - -> **Note** -> -> - It is possible to keep the original names in order to avoid -> potential caching issues, but then you will have to update the -> *Config UI* field in the App Builder widget screen each time a new -> version of the widget is deployed. -> -> - `configUI` is a JSON object, so pay attention to save a -> well-formed one (the integrated JSON editor will help you) -> -> - value for `customElement` must match the name of custom tag in -> `index.html` and the one passed as parameter to -> `customElements.define` in `WidgetElement` -> - -You can now add a page in App Builder, drag the widget into the page template slot and you’ll see the configuration screen we just built. - -# Display Widget Configuration - -So, we already created a React micro frontend widget and configuration -screen to customize a *name* field. - -In this tutorial we will display that field in our micro frontend -widget. - -## Add Attribute - -Edit `WidgetElement` to add attribute handling to the custom element, -and re-render our app when an attribute changes. Now, the *name* -attribute is being read from the custom element and passed as a prop to -the react root component (*App*). - - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; - - const ATTRIBUTES = { - name: 'name', - }; - - class WidgetElement extends HTMLElement { - - static get observedAttributes() { - return Object.values(ATTRIBUTES); - } - - attributeChangedCallback(name, oldValue, newValue) { - if (!Object.values(ATTRIBUTES).includes(name)) { - throw new Error(`Untracked changed attribute: ${name}`); - } - if (this.mountPoint && newValue !== oldValue) { - this.render(); - } - } - - connectedCallback() { - this.mountPoint = document.createElement('div'); - this.appendChild(this.mountPoint); - this.render(); - } - - render() { - const name = this.getAttribute(ATTRIBUTES.name); - ReactDOM.render(, this.mountPoint); - } - } - - customElements.define('my-widget', WidgetElement); - - export default WidgetElement; - -> **Note** -> -> `attributeChangedCallback` is also a custom elements lifecycle hook -> method. - -## Display Input - -Edit the `App` component now, to make it display the `name` prop. - + } + + connectedCallback() { + this.mountPoint = document.createElement('div'); + this.appendChild(this.mountPoint); + this.render(); + } + + render() { + const name = this.getAttribute(ATTRIBUTES.name); + ReactDOM.render(, this.mountPoint); + } +} + +customElements.define('my-widget', WidgetElement); + +export default WidgetElement; +``` + +2. Edit the `src/App.js` component to make it display the `name` prop. This turns the static component from the previous tutorial into a more dynamic component. +``` javascript import React from 'react'; import './App.css'; @@ -306,82 +79,187 @@ Edit the `App` component now, to make it display the `name` prop. } export default App; +``` -Now, to ensure our custom element is working, we can edit -`public/index.html` and set a value for the *name* attribute of the +3. For test purposes, edit `public/index.html` and set a value for the *name* attribute of the custom element. +``` html + +``` +4. Start the app and confirm that "Hello, Jane!" is displayed. +``` bash +cd my-widget +npm start +``` +5. Build the app and load the updated files into Entando. If you followed the previous tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. +```bash +npm run build +``` + +## Create a config MFE +Next create a new MFE for managing the configuration option. These steps are very similar to the [previous tutorial](./react.md). + +::: tip +This tutorial sets up a separate, standalone config MFE since that allows reuse across multiple target MFEs. You could also choose to add the config custom element into the target MFE in which case the configUI will also reference the target MFE files. +::: + +1. Generate a new React app +```shell +npx create-react-app my-widget-config --use-npm +``` +2. Start the app +```shell +cd my-widget-config +npm start +``` +3. Modify `src/App.js` to add a simple form for managing a single `name` field. +```javascript +import React from 'react'; + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { name: ''}; + } + + handleNameChange(value) { + this.setState(prevState => ({ + ...prevState, + name: value, + })); + } + + render() { + const { name } = this.state; + return ( +
+

Sample Entando Widget Configuration

+ + this.handleNameChange(e.target.value)} value={name} /> +
+ ); + } +} + +export default App; +``` + +::: tip +* Use your favorite form handling library, e.g.[Formik](https://jaredpalmer.com/formik) +* This MFE will be displayed within the App Builder which currently uses [PatternFly +v3](https://www.patternfly.org/v3/) (`patternfly` and `patternfly-react` +packages) for styling. +::: + +4. Add `src/WidgetElement.js` to setup the custom element for the config MFE. +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +class WidgetElement extends HTMLElement { + constructor() { + super(); + this.reactRootRef = React.createRef(); + this.mountPoint = null; + } - - - - - - - React App - - - - - + get config() { + return this.reactRootRef.current ? this.reactRootRef.current.state : {}; + } -After page reload, you should be able to display a simple "Hello, -Marco!" message. + set config(value) { + return this.reactRootRef.current.setState(value); + } -## Build It + connectedCallback() { + this.mountPoint = document.createElement('div'); + this.appendChild(this.mountPoint); + ReactDOM.render(, this.mountPoint); + } +} -From the react project root, type: +customElements.define('my-widget-config', WidgetElement); -`npm run build` +export default WidgetElement; +``` -and the `build/static` directory will be regenerated. Again, for convenience, rename the files and then update them in Entando under `public/my-widget/static` using `Administration` → `File Browser`: +::: tip App Builder integration +* A config MFE must retain its state in a `config` property +* The App Builder supplies the `config` property when the MFE is rendered +* When a user saves the form, the App Builder automatically persists the configuration through Entando APIs +::: + +5. Replace `src/index.js` with this +```javascript + import './index.css'; + import './WidgetElement'; +``` -- a file like `js/runtime~main.c7dcdf0b.js` to `js/runtime.js` - (bootstrapping logic) +6. For test purposes, modify `public/index.html` and confirm the form renders correctly. Replace the body tag with this: +```html + + + +``` -- a file like `js/2.230b21ef.chunk.js` to `js/vendor.js` (third-party - libraries) +## Build and configure the config MFE -- a file like `js/main.1fd3965a.chunk.js` to `js/main.js` (app) +1. Build the app from the 'my-widget-config' directory +```bash +npm run build +``` -- a file like `css/main.d1b05096.chunk.js` to `css/main.css` - (stylesheet) +2. In the App Builder, go to `Administration` → `File browser` → `public` -> **Note** -> -> you could keep the original names in order to avoid potential caching -> issues, but then you will have to update the *Custom UI* field in the -> App Builder widget screen every time a new version of the widget is -> deployed. +3. Click `Create folder` and name it "my-widget-config". -If the application server you’re running does not have hot deploy -enabled, restart it. +4. Click `Save` -## Update Widget in App Builder +5. Click `my-widget-config` -Open the `Entando App Builder`, go to `Components` → `Micro frontends & Widgets`, find the widget *My Widget* and click to edit it. +6. Create a folder structure similar to your generated build directory: -Update the *Custom UI* field from: +- `my-widget-config/static/css` +- `my-widget-config/static/js` - <#assign wp=JspTaglibs[ "/aps-core"]> - - - - - +6. Upload the css and js files from the corresponding directories under 'my-widget-config/build/static'. The generated id in each file name (e.g. '073c9b0a') may be different after each build. There may also be LICENSE.txt or .map files but they are not necessary for this tutorial. -to +- `my-widget-config/build/static/css/main.073c9b0a.css` +- `my-widget-config/build/static/js/main.b9eb8fa4.js` + +7. Go to `Components` → `MFE & Widgets` and edit your target widget. +8. Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". +```javascript +{ + "customElement": "my-widget-config", + "resources": [ + "my-widget-config/static/css/main.073c9b0a.css", + "my-widget-config/static/js/main.b9eb8fa4.js" + ] +} +``` + +9. Set the **`Custom UI`** so it accepts the "name" config parameter. +```javascript +{ <#assign wp=JspTaglibs[ "/aps-core"]> - - - - + + <@wp.currentWidget param="config" configParam="name" var="configName" /> +} +``` +::: tip +Multiple `<@wp.currentWidget param` tags can be used when a config MFE supports more than one parameter. +::: + +10. Test the full setup by adding the widget into an existing page. + +11. Fill out the "name" field and click `Save`. You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. + +12. Publish the page and confirm the target MFE is configured and displays correctly. + -We basically added a JSTL tag to extract a field (under `configParam`) -from the config field of the current widget and put it in a `configName` -variable, that we pass to the custom element. -Save the widget and reload the page that contains the widget. You should see -`Hello, Marco!` as expected. From 5acac18ab445ba0af8a10ad8f3729d5ae8fc4f67 Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Wed, 1 Jun 2022 14:34:22 -0400 Subject: [PATCH 2/7] ENDOC-502 Apply MFE config feedback --- vuepress/docs/v7.0/tutorials/create/mfe/README.md | 2 -- .../docs/v7.0/tutorials/create/mfe/widget-configuration.md | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/README.md b/vuepress/docs/v7.0/tutorials/create/mfe/README.md index 6c0f4ce368..ce3f2e43e1 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/README.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/README.md @@ -27,8 +27,6 @@ Entando's microservice and micro frontend architecture allows developers to work - [Add an App Builder configuration screen to a widget](./widget-configuration.md) -- [Display widget configuration](./widget-configuration.md#display-widget-configuration) - - [Communicate Between Micro Frontends](./communication.md) - [Widget authentication with Keycloak](./authentication.md) diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md index 4671092550..1225c189b1 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md @@ -235,7 +235,6 @@ npm run build { "customElement": "my-widget-config", "resources": [ - "my-widget-config/static/css/main.073c9b0a.css", "my-widget-config/static/js/main.b9eb8fa4.js" ] } @@ -245,8 +244,8 @@ npm run build ```javascript { <#assign wp=JspTaglibs[ "/aps-core"]> - - + + <@wp.currentWidget param="config" configParam="name" var="configName" /> } From d3118879799c6bd8b1dc1bc03a2bef3dbf04169a Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Fri, 3 Jun 2022 08:49:02 -0700 Subject: [PATCH 3/7] ENDOC-502-mfe-config Edits --- .../tutorials/create/mfe/communication.md | 11 +-- .../create/mfe/widget-configuration.md | 83 ++++++++++--------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/communication.md b/vuepress/docs/v7.0/tutorials/create/mfe/communication.md index e224039538..996d44ef33 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/communication.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/communication.md @@ -15,25 +15,22 @@ Entando supports communication between micro frontends using [Custom Events](htt - A React micro frontend that listens to an event - An Angular micro frontend that publishes an event to a React micro frontend -## Publisher +## Create a Publisher -Create a simple app to publish an event. +1. Create a simple app to publish an event: ``` bash npx create-react-app publisher-widget --use-npm ``` -Start the app. +2. Start the app from its root directory: ``` bash cd publisher-widget -``` - -``` bash npm start ``` -### Create Custom Event +### Create a Custom Event Next, add event firing logic. diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md index 1225c189b1..e74d3e4aa1 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md @@ -1,24 +1,24 @@ # Add a Configuration Screen in App Builder -Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. +Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. This tutorial splits the process into 3 steps: -There are 3 steps to this tutorial -1. Modify an existing MFE (the target MFE) to take a configuration option -2. Create a new MFE (the config MFE) to provide a user interface for the configuration option -3. Set up the target MFE to use the configuration provided by the config MFE +**1. Modify an existing MFE (the target MFE) to take a configuration option** +**2. Create a new MFE (the config MFE) to provide a user interface for the configuration option** +**3. Set up the target MFE to use the configuration provided by the config MFE** ## Prerequisites - [A working instance of Entando](../../../docs/getting-started/) - [An existing React MFE](./react.md) -## Add a configuration option to your target MFE +## Step 1: Add a configuration option to your target MFE Start by adding a configuration option to an existing MFE. If you don't already have one, you can create it via the [React MFE tutorial](./react.md). ### Add an Attribute to the Custom Element -1. Edit `src/WidgetElement.js` to add attribute handling to the custom element and to re-render the App when an attribute changes. This enables the *name* attribute on the custom element to be passed as a prop to -the React root component (*App*). +1. Replace the contents of `src/WidgetElement.js` with the following to add attribute handling to the custom element and re-render the app when an attribute changes. + +> Note: This enables the `name` attribute of the custom element to be passed as a property to the React root component (`App`). ``` javascript import React from 'react'; @@ -61,7 +61,8 @@ customElements.define('my-widget', WidgetElement); export default WidgetElement; ``` -2. Edit the `src/App.js` component to make it display the `name` prop. This turns the static component from the previous tutorial into a more dynamic component. +2. Replace the contents of `src/App.js` with the following. This component now displays the `name` property, turning the static component from the [previous tutorial](./react.md) into a more dynamic component. + ``` javascript import React from 'react'; import './App.css'; @@ -81,26 +82,32 @@ export default WidgetElement; export default App; ``` -3. For test purposes, edit `public/index.html` and set a value for the *name* attribute of the -custom element. +3. For test purposes, replace the contents of `public/index.html` with the following. This allows you to set a value for the `name` attribute of the custom element. + ``` html ``` -4. Start the app and confirm that "Hello, Jane!" is displayed. + +4. Start the app and confirm that "Hello, Jane!" is displayed + ``` bash cd my-widget npm start ``` -5. Build the app and load the updated files into Entando. If you followed the previous tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. +5. Build the app + ```bash npm run build ``` -## Create a config MFE -Next create a new MFE for managing the configuration option. These steps are very similar to the [previous tutorial](./react.md). +6. Load the updated `my-widget` files into Entando as was done for the [previous tutorial](./react.md#upload-the-react-files) + +> Note: If you followed the previous tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. +## Step 2: Create a config MFE +Next, create a new MFE for managing the configuration option. These steps are very similar to the [previous tutorial](./react.md). ::: tip -This tutorial sets up a separate, standalone config MFE since that allows reuse across multiple target MFEs. You could also choose to add the config custom element into the target MFE in which case the configUI will also reference the target MFE files. +This tutorial sets up a separate, standalone config MFE since that allows reuse across multiple target MFEs. You could also choose to include the config custom element in the target MFE, in which case the **configUI** will also reference the target MFE files. ::: 1. Generate a new React app @@ -112,7 +119,8 @@ npx create-react-app my-widget-config --use-npm cd my-widget-config npm start ``` -3. Modify `src/App.js` to add a simple form for managing a single `name` field. +3. Replace the contents of `src/App.js` with the following to add a simple form for managing a single `name` field + ```javascript import React from 'react'; @@ -145,13 +153,13 @@ export default App; ``` ::: tip -* Use your favorite form handling library, e.g.[Formik](https://jaredpalmer.com/formik) -* This MFE will be displayed within the App Builder which currently uses [PatternFly +* Use your preferred form handling library, e.g.[Formik](https://jaredpalmer.com/formik) +* This MFE will be displayed within the App Builder, which currently uses [PatternFly v3](https://www.patternfly.org/v3/) (`patternfly` and `patternfly-react` -packages) for styling. +packages) for styling ::: -4. Add `src/WidgetElement.js` to setup the custom element for the config MFE. +4. Add a `src/WidgetElement.js` component with the following content to set up the custom element for the config MFE ```javascript import React from 'react'; import ReactDOM from 'react-dom'; @@ -190,29 +198,28 @@ export default WidgetElement; * When a user saves the form, the App Builder automatically persists the configuration through Entando APIs ::: -5. Replace `src/index.js` with this +5. Replace the contents of `src/index.js` with the following: ```javascript import './index.css'; import './WidgetElement'; ``` -6. For test purposes, modify `public/index.html` and confirm the form renders correctly. Replace the body tag with this: +6. For test purposes, replace the body tag of `public/index.html` with the following and confirm the form renders correctly ```html ``` +## Step 3: Build and configure the config MFE -## Build and configure the config MFE - -1. Build the app from the 'my-widget-config' directory +1. Build the app from the `my-widget-config` directory ```bash npm run build ``` 2. In the App Builder, go to `Administration` → `File browser` → `public` -3. Click `Create folder` and name it "my-widget-config". +3. Click `Create folder` and name it "my-widget-config" 4. Click `Save` @@ -223,14 +230,16 @@ npm run build - `my-widget-config/static/css` - `my-widget-config/static/js` -6. Upload the css and js files from the corresponding directories under 'my-widget-config/build/static'. The generated id in each file name (e.g. '073c9b0a') may be different after each build. There may also be LICENSE.txt or .map files but they are not necessary for this tutorial. +6. Upload the css and js files from the corresponding directories under `my-widget-config/build/static` + +> Note: The generated ID of each file name (e.g. '073c9b0a') may change after every build. These folders may also contain LICENSE.txt or .map files, but they are not applicable to this tutorial. - `my-widget-config/build/static/css/main.073c9b0a.css` - `my-widget-config/build/static/js/main.b9eb8fa4.js` -7. Go to `Components` → `MFE & Widgets` and edit your target widget. +7. Go to `Components` → `MFE & Widgets` and edit your target widget -8. Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". +- Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". ```javascript { "customElement": "my-widget-config", @@ -240,25 +249,25 @@ npm run build } ``` -9. Set the **`Custom UI`** so it accepts the "name" config parameter. -```javascript -{ +- Set **`Custom UI`** to accept the "name" config parameter +```ftl <#assign wp=JspTaglibs[ "/aps-core"]> <@wp.currentWidget param="config" configParam="name" var="configName" /> -} ``` ::: tip Multiple `<@wp.currentWidget param` tags can be used when a config MFE supports more than one parameter. ::: -10. Test the full setup by adding the widget into an existing page. +8. Test the full setup by adding the widget into an existing page -11. Fill out the "name" field and click `Save`. You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. +9. Fill out the "name" field and click `Save` + +> Note: You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. -12. Publish the page and confirm the target MFE is configured and displays correctly. +10. Publish the page and confirm the target MFE is configured and displays correctly From 8d860aafbc0525489b392555dbf423083478d9d7 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Fri, 3 Jun 2022 10:01:47 -0700 Subject: [PATCH 4/7] ENDOC-502-mfe-config Last fixes --- .../tutorials/create/mfe/widget-configuration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md index e74d3e4aa1..93a7b7de89 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md @@ -3,9 +3,9 @@ Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. This tutorial splits the process into 3 steps: -**1. Modify an existing MFE (the target MFE) to take a configuration option** -**2. Create a new MFE (the config MFE) to provide a user interface for the configuration option** -**3. Set up the target MFE to use the configuration provided by the config MFE** +1. Modify an existing MFE (the target MFE) to take a configuration option +2. Create a new MFE (the config MFE) to provide a user interface for the configuration option +3. Set up the target MFE to use the configuration provided by the config MFE ## Prerequisites - [A working instance of Entando](../../../docs/getting-started/) @@ -244,12 +244,12 @@ npm run build { "customElement": "my-widget-config", "resources": [ - "my-widget-config/static/js/main.b9eb8fa4.js" + "my-widget-config/static/js/main.e6c13ad2.js" ] } ``` -- Set **`Custom UI`** to accept the "name" config parameter +- Set **`Custom UI`** to accept the `name` config parameter ```ftl <#assign wp=JspTaglibs[ "/aps-core"]> @@ -263,7 +263,7 @@ Multiple `<@wp.currentWidget param` tags can be used when a config MFE supports 8. Test the full setup by adding the widget into an existing page -9. Fill out the "name" field and click `Save` +9. Fill out the `name` field and click `Save` > Note: You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. From 857249492a65b262245bea18b34a50059c45ea5c Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Tue, 7 Jun 2022 10:11:00 -0700 Subject: [PATCH 5/7] ENDOC-502-mfe-config Apply feedback and to Next --- .../create/mfe/widget-configuration.md | 571 +++++++----------- .../create/mfe/widget-configuration.md | 72 ++- 2 files changed, 259 insertions(+), 384 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/next/tutorials/create/mfe/widget-configuration.md index 7df984da0e..fb0209a0c9 100644 --- a/vuepress/docs/next/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/next/tutorials/create/mfe/widget-configuration.md @@ -1,295 +1,67 @@ # Add a Configuration Screen in App Builder -Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. It can be developed and tested in isolation without a running Entando instance. - -## Create React App - -Let’s start with the boilerplate provided by [Create React -App](https://create-react-app.dev/), probably the most popular one. - -`npx create-react-app my-widget-config --use-npm` - - my-widget-config - ├── README.md - ├── node_modules - ├── package.json - ├── .gitignore - ├── public - │ ├── favicon.ico - │ ├── index.html - │ ├── logo192.png - │ ├── logo512.png - │ ├── manifest.json - │ └── robots.txt - └── src - ├── App.css - ├── App.js - ├── App.test.js - ├── index.css - ├── index.js - ├── logo.svg - ├── serviceWorker.js - └── setupTests.js - -Then, type `cd my-widget-config` and `npm start` to start the app. - -## Add Input Field - -Let’s start with a simple form: only an input with a label. So, let’s -edit `App.js` +Entando widgets can be customized through an App Builder configuration screen that is itself a micro frontend. This tutorial splits the process into 3 steps: - import React from 'react'; - - class App extends React.Component { - constructor(props) { - super(props); - this.state = { name: ''}; - } - - handleNameChange(value) { - this.setState(prevState => ({ - ...prevState, - name: value, - })); - } - - render() { - const { name } = this.state; - return ( -
-

Sample Entando Widget Configuration

- - this.handleNameChange(e.target.value)} value={name} /> -
- ); - } - } +1. Modify an existing MFE (the target MFE) to take a configuration option +2. Create a new MFE (the config MFE) to provide a user interface for the configuration option +3. Set up the target MFE to use the configuration provided by the config MFE - export default App; +## Prerequisites +- [A working instance of Entando](../../../docs/getting-started/) +- [An existing React MFE](./react.md) -You are free to use your favorite form handling library e.g., -[Formik](https://jaredpalmer.com/formik), -[redux-form](https://redux-form.com/) (that requirese redux) or others. +## Step 1: Add a configuration option to your target MFE +Start by adding a configuration option to an existing MFE. If you don't already have one, you can create it via the [React MFE tutorial](./react.md). -In regards to styling, since this is going to be an App Builder screen, -we strongly suggest using [PatternFly -v3](https://www.patternfly.org/v3/) (`patternfly` and `patternfly-react` -packages) to keep UX coherence. +### Add an Attribute to the Custom Element -## Custom Element +1. Replace the contents of `src/WidgetElement.js` with the following to add attribute handling to the custom element and re-render the app when an attribute changes. This enables the `name` attribute of the custom element to be passed as a property to the React root component (`App`). + +``` javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; -Now, let’s add the web component that will wrap the entire React app. -Let’s name it `WidgetElement` +const ATTRIBUTES = { + name: 'name', +}; - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; +class WidgetElement extends HTMLElement { - class WidgetElement extends HTMLElement { - constructor() { - super(); - this.reactRootRef = React.createRef(); - this.mountPoint = null; - } - - get config() { - return this.reactRootRef.current ? this.reactRootRef.current.state : {}; - } + static get observedAttributes() { + return Object.values(ATTRIBUTES); + } - set config(value) { - return this.reactRootRef.current.setState(value); - } - - connectedCallback() { - this.mountPoint = document.createElement('div'); - this.appendChild(this.mountPoint); - ReactDOM.render(, this.mountPoint); - } + attributeChangedCallback(name, oldValue, newValue) { + if (!Object.values(ATTRIBUTES).includes(name)) { + throw new Error(`Untracked changed attribute: ${name}`); } - - customElements.define('my-widget-config', WidgetElement); - - export default WidgetElement; - -Its responsibility is rendering the React app and syncing the React app -state in a `config` property. That *must* be named that way. The key to -App Builder communication is that it works in three steps: - -- App Builder reads `config` property when the widget config screen is - rendered - -- `config` property is mutated when a user configures the widget - -- When a user saves the config, App Builder retrieves it (again, from - the `config` property) and persists it through Entando APIs - -This means the widget developer can focus on the configuration screens -without having to call Entando APIs to read or write configuration. - -One more JS file to update: `index.js`. Starting from this - - import React from 'react'; - import ReactDOM from 'react-dom'; - import './index.css'; - import App from './App'; - import * as serviceWorker from './serviceWorker'; - - ReactDOM.render(, document.getElementById('root')); - - // If you want your app to work offline and load faster, you can change - // unregister() to register() below. Note this comes with some pitfalls. - // Learn more about service workers: https://bit.ly/CRA-PWA - serviceWorker.unregister(); - -You only have to import `WidgetElement` plus the css, if needed. -Something like - - import './index.css'; - import './WidgetElement'; - -We assume we don’t need a service worker for the widget, so we can -delete serviceWorker.js. - -To ensure our web component is working we have to edit -`public/index.html`. Remove `
` from the `body` (we -programmatically generated the react root in the `connectedCallback` -method of `WidgetElement`) and add our new web component tag -``. - - - - - - - - React App - - - - - - -> **Note** -> -> the web component tag name (`my-widget-config` in this tutorial) -> *must* match the first parameter of the `customElements.define` -> method. - -The page should auto reload and... congrats, you’re running an Entando -widget in isolation. - -## Configuration Screen - -Next, we’ll build our widget before embedding it into the Entando -instance. From the react project root, type - -`npm run build` - -and a `build/static` directory will be generated. For convenience in this tutorial, rename the generated files: - -- a file like `js/runtime~main.c7dcdf0b.js` to `js/runtime.js` - (bootstrapping logic) - -- a file like `js/2.230b21ef.chunk.js` to `js/vendor.js` (third-party - libraries) - -- a file like `js/main.1fd3965a.chunk.js` to `js/main.js` (app) - -Next load these files into Entando under `public/my-widget-config/static` using `Administration` → `File Browser`. - -Now go to `Components` → `Micro frontends & Widgets` and find the original widget we're creating the configuration screen for. Edit the widget and update the -**`configUI`** field. - - { - "customElement": "my-widget-config", - "resources": [ - "my-widget-config/static/js/runtime.js", - "my-widget-config/static/js/vendor.js", - "my-widget-config/static/js/main.js" - ] + if (this.mountPoint && newValue !== oldValue) { + this.render(); } + } -> **Note** -> -> - It is possible to keep the original names in order to avoid -> potential caching issues, but then you will have to update the -> *Config UI* field in the App Builder widget screen each time a new -> version of the widget is deployed. -> -> - `configUI` is a JSON object, so pay attention to save a -> well-formed one (the integrated JSON editor will help you) -> -> - value for `customElement` must match the name of custom tag in -> `index.html` and the one passed as parameter to -> `customElements.define` in `WidgetElement` -> - -You can now add a page in App Builder, drag the widget into the page template slot and you’ll see the configuration screen we just built. - -# Display Widget Configuration - -So, we already created a React micro frontend widget and configuration -screen to customize a *name* field. + connectedCallback() { + this.mountPoint = document.createElement('div'); + this.appendChild(this.mountPoint); + this.render(); + } + + render() { + const name = this.getAttribute(ATTRIBUTES.name); + ReactDOM.render(, this.mountPoint); + } +} -In this tutorial we will display that field in our micro frontend -widget. +customElements.define('my-widget', WidgetElement); -## Add Attribute +export default WidgetElement; +``` -Edit `WidgetElement` to add attribute handling to the custom element, -and re-render our app when an attribute changes. Now, the *name* -attribute is being read from the custom element and passed as a prop to -the react root component (*App*). - - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; - - const ATTRIBUTES = { - name: 'name', - }; - - class WidgetElement extends HTMLElement { - - static get observedAttributes() { - return Object.values(ATTRIBUTES); - } - - attributeChangedCallback(name, oldValue, newValue) { - if (!Object.values(ATTRIBUTES).includes(name)) { - throw new Error(`Untracked changed attribute: ${name}`); - } - if (this.mountPoint && newValue !== oldValue) { - this.render(); - } - } - - connectedCallback() { - this.mountPoint = document.createElement('div'); - this.appendChild(this.mountPoint); - this.render(); - } - - render() { - const name = this.getAttribute(ATTRIBUTES.name); - ReactDOM.render(, this.mountPoint); - } - } - - customElements.define('my-widget', WidgetElement); - - export default WidgetElement; - -> **Note** -> -> `attributeChangedCallback` is also a custom elements lifecycle hook -> method. - -## Display Input - -Edit the `App` component now, to make it display the `name` prop. +2. Replace the contents of `src/App.js` with the following. This component now displays the `name` property, turning the static component from the [React MFE tutorial](./react.md) into a more dynamic component. +``` javascript import React from 'react'; import './App.css'; @@ -306,82 +78,189 @@ Edit the `App` component now, to make it display the `name` prop. } export default App; +``` + +3. For test purposes, replace the contents of `public/index.html` with the following. This allows you to set a value for the `name` attribute of the custom element. + +``` html + +``` + +4. Start the app and confirm that "Hello, Jane!" is displayed + +``` bash +cd my-widget +npm start +``` +5. Build the app + +```bash +npm run build +``` + +6. Load the updated `my-widget` files into Entando as was done for the [React MFE tutorial](./react.md#upload-the-react-files) + +> Note: If you followed the React MFE tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. +## Step 2: Create a config MFE +Next, create a new MFE for managing the configuration option. These steps are very similar to the [React MFE tutorial](./react.md). + +::: tip +This tutorial sets up a separate, standalone config MFE since that allows reuse across multiple target MFEs. You could also choose to include the config custom element in the target MFE, in which case the **configUI** will also reference the target MFE files. +::: + +1. Generate a new React app +```shell +npx create-react-app my-widget-config --use-npm +``` +2. Start the app +```shell +cd my-widget-config +npm start +``` +3. Replace the contents of `src/App.js` with the following to add a simple form for managing a single `name` field + +```javascript +import React from 'react'; + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { name: ''}; + } + + handleNameChange(value) { + this.setState(prevState => ({ + ...prevState, + name: value, + })); + } + + render() { + const { name } = this.state; + return ( +
+

Sample Entando Widget Configuration

+ + this.handleNameChange(e.target.value)} value={name} /> +
+ ); + } +} + +export default App; +``` + +::: tip +* Use your preferred form handling library, e.g.[Formik](https://jaredpalmer.com/formik) +* This MFE will be displayed within the App Builder, which currently uses [PatternFly +v3](https://www.patternfly.org/v3/) (`patternfly` and `patternfly-react` +packages) for styling +::: + +4. Add a `src/WidgetElement.js` component with the following content to set up the custom element for the config MFE +```javascript +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +class WidgetElement extends HTMLElement { + constructor() { + super(); + this.reactRootRef = React.createRef(); + this.mountPoint = null; + } -Now, to ensure our custom element is working, we can edit -`public/index.html` and set a value for the *name* attribute of the -custom element. - - - - - - - - React App - - - - - - -After page reload, you should be able to display a simple "Hello, -Marco!" message. - -## Build It - -From the react project root, type: - -`npm run build` - -and the `build/static` directory will be regenerated. Again, for convenience, rename the files and then update them in Entando under `public/my-widget/static` using `Administration` → `File Browser`: - -- a file like `js/runtime~main.c7dcdf0b.js` to `js/runtime.js` - (bootstrapping logic) - -- a file like `js/2.230b21ef.chunk.js` to `js/vendor.js` (third-party - libraries) - -- a file like `js/main.1fd3965a.chunk.js` to `js/main.js` (app) - -- a file like `css/main.d1b05096.chunk.js` to `css/main.css` - (stylesheet) - -> **Note** -> -> you could keep the original names in order to avoid potential caching -> issues, but then you will have to update the *Custom UI* field in the -> App Builder widget screen every time a new version of the widget is -> deployed. - -If the application server you’re running does not have hot deploy -enabled, restart it. - -## Update Widget in App Builder - -Open the `Entando App Builder`, go to `Components` → `Micro frontends & Widgets`, find the widget *My Widget* and click to edit it. - -Update the *Custom UI* field from: + get config() { + return this.reactRootRef.current ? this.reactRootRef.current.state : {}; + } - <#assign wp=JspTaglibs[ "/aps-core"]> - - - - - + set config(value) { + return this.reactRootRef.current.setState(value); + } -to + connectedCallback() { + this.mountPoint = document.createElement('div'); + this.appendChild(this.mountPoint); + ReactDOM.render(, this.mountPoint); + } +} - <#assign wp=JspTaglibs[ "/aps-core"]> - - - - - <@wp.currentWidget param="config" configParam="name" var="configName" /> - +customElements.define('my-widget-config', WidgetElement); -We basically added a JSTL tag to extract a field (under `configParam`) -from the config field of the current widget and put it in a `configName` -variable, that we pass to the custom element. +export default WidgetElement; +``` -Save the widget and reload the page that contains the widget. You should see -`Hello, Marco!` as expected. +::: tip App Builder integration +* A config MFE must retain its state in a `config` property +* The App Builder supplies the `config` property when the MFE is rendered +* When a user saves the form, the App Builder automatically persists the configuration through Entando APIs +::: + +5. Replace the contents of `src/index.js` with the following: +```javascript + import './index.css'; + import './WidgetElement'; +``` + +6. For test purposes, replace the body tag of `public/index.html` with the following and confirm the form renders correctly +```html + + + +``` +## Step 3: Build and configure the config MFE + +1. Build the app from the `my-widget-config` directory +```bash +npm run build +``` + +2. In the App Builder, go to `Administration` → `File browser` → `public` + +3. Click `Create folder` and name it "my-widget-config" + +4. Click `Save` + +5. Click `my-widget-config` + +6. Create a folder structure similar to your generated build directory: + + - `my-widget-config/static/css` + - `my-widget-config/static/js` + +7. Upload the css and js files from the corresponding directories under `my-widget-config/build/static` + + - `my-widget-config/build/static/css/main.073c9b0a.css` + - `my-widget-config/build/static/js/main.b9eb8fa4.js` + +> Note: The generated ID of each file name (e.g. '073c9b0a') may change after every build. These folders may also contain LICENSE.txt or .map files, but they are not applicable to this tutorial. + +8. Go to `Components` → `MFE & Widgets` and edit your target widget + + - Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". + ```javascript + { + "customElement": "my-widget-config", + "resources": [ + "my-widget-config/static/js/main.e6c13ad2.js" + ] + } + ``` + + - Set **`Custom UI`** to accept the `name` config parameter + ```ftl + <#assign wp=JspTaglibs[ "/aps-core"]> + + + <@wp.currentWidget param="config" configParam="name" var="configName" /> + + ``` +::: tip +Multiple `<@wp.currentWidget param` tags can be used when a config MFE supports more than one parameter. +::: + +9. Test the full setup by adding the widget into an existing page + +10. Fill out the `name` field and click `Save`. You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. + +11. Publish the page and confirm the target MFE is configured and displays correctly diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md index 93a7b7de89..7ff5172a8b 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/widget-configuration.md @@ -16,9 +16,7 @@ Start by adding a configuration option to an existing MFE. If you don't already ### Add an Attribute to the Custom Element -1. Replace the contents of `src/WidgetElement.js` with the following to add attribute handling to the custom element and re-render the app when an attribute changes. - -> Note: This enables the `name` attribute of the custom element to be passed as a property to the React root component (`App`). +1. Replace the contents of `src/WidgetElement.js` with the following to add attribute handling to the custom element and re-render the app when an attribute changes. This enables the `name` attribute of the custom element to be passed as a property to the React root component (`App`). ``` javascript import React from 'react'; @@ -61,7 +59,7 @@ customElements.define('my-widget', WidgetElement); export default WidgetElement; ``` -2. Replace the contents of `src/App.js` with the following. This component now displays the `name` property, turning the static component from the [previous tutorial](./react.md) into a more dynamic component. +2. Replace the contents of `src/App.js` with the following. This component now displays the `name` property, turning the static component from the [React MFE tutorial](./react.md) into a more dynamic component. ``` javascript import React from 'react'; @@ -82,7 +80,7 @@ export default WidgetElement; export default App; ``` -3. For test purposes, replace the contents of `public/index.html` with the following. This allows you to set a value for the `name` attribute of the custom element. +3. For test purposes, replace the contents of `public/index.html` with the following. This allows you to set a value for the `name` attribute of the custom element. ``` html @@ -100,11 +98,11 @@ npm start npm run build ``` -6. Load the updated `my-widget` files into Entando as was done for the [previous tutorial](./react.md#upload-the-react-files) +6. Load the updated `my-widget` files into Entando as was done for the [React MFE tutorial](./react.md#upload-the-react-files) -> Note: If you followed the previous tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. +> Note: If you followed the React MFE tutorial, only `js/main.GENERATED-ID.js` needs to be added or updated. ## Step 2: Create a config MFE -Next, create a new MFE for managing the configuration option. These steps are very similar to the [previous tutorial](./react.md). +Next, create a new MFE for managing the configuration option. These steps are very similar to the [React MFE tutorial](./react.md). ::: tip This tutorial sets up a separate, standalone config MFE since that allows reuse across multiple target MFEs. You could also choose to include the config custom element in the target MFE, in which case the **configUI** will also reference the target MFE files. @@ -227,47 +225,45 @@ npm run build 6. Create a folder structure similar to your generated build directory: -- `my-widget-config/static/css` -- `my-widget-config/static/js` + - `my-widget-config/static/css` + - `my-widget-config/static/js` -6. Upload the css and js files from the corresponding directories under `my-widget-config/build/static` +7. Upload the css and js files from the corresponding directories under `my-widget-config/build/static` -> Note: The generated ID of each file name (e.g. '073c9b0a') may change after every build. These folders may also contain LICENSE.txt or .map files, but they are not applicable to this tutorial. + - `my-widget-config/build/static/css/main.073c9b0a.css` + - `my-widget-config/build/static/js/main.b9eb8fa4.js` -- `my-widget-config/build/static/css/main.073c9b0a.css` -- `my-widget-config/build/static/js/main.b9eb8fa4.js` +> Note: The generated ID of each file name (e.g. '073c9b0a') may change after every build. These folders may also contain LICENSE.txt or .map files, but they are not applicable to this tutorial. -7. Go to `Components` → `MFE & Widgets` and edit your target widget - -- Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". -```javascript -{ - "customElement": "my-widget-config", - "resources": [ - "my-widget-config/static/js/main.e6c13ad2.js" - ] -} -``` +8. Go to `Components` → `MFE & Widgets` and edit your target widget + + - Set **`Config UI`** to select the config custom element and its corresponding files. Note that the paths here reference "my-widget-config". + ```javascript + { + "customElement": "my-widget-config", + "resources": [ + "my-widget-config/static/js/main.e6c13ad2.js" + ] + } + ``` -- Set **`Custom UI`** to accept the `name` config parameter -```ftl - <#assign wp=JspTaglibs[ "/aps-core"]> - - - <@wp.currentWidget param="config" configParam="name" var="configName" /> - -``` + - Set **`Custom UI`** to accept the `name` config parameter + ```ftl + <#assign wp=JspTaglibs[ "/aps-core"]> + + + <@wp.currentWidget param="config" configParam="name" var="configName" /> + + ``` ::: tip Multiple `<@wp.currentWidget param` tags can be used when a config MFE supports more than one parameter. ::: -8. Test the full setup by adding the widget into an existing page +9. Test the full setup by adding the widget into an existing page -9. Fill out the `name` field and click `Save` - -> Note: You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. +10. Fill out the `name` field and click `Save`. You can update the widget configuration at any point by clicking `Settings` from the widget actions in the Page Designer. -10. Publish the page and confirm the target MFE is configured and displays correctly +11. Publish the page and confirm the target MFE is configured and displays correctly From b3def987947fa12eeefc56460bd38c6910c87595 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Tue, 7 Jun 2022 10:27:30 -0700 Subject: [PATCH 6/7] ENDOC-502-mfe-config Fix links update Next files --- .../docs/next/tutorials/create/mfe/README.md | 5 +- .../docs/next/tutorials/create/mfe/react.md | 293 +++++------------- .../docs/v7.0/tutorials/create/mfe/README.md | 3 +- 3 files changed, 83 insertions(+), 218 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/mfe/README.md b/vuepress/docs/next/tutorials/create/mfe/README.md index 6c0f4ce368..74219a4819 100644 --- a/vuepress/docs/next/tutorials/create/mfe/README.md +++ b/vuepress/docs/next/tutorials/create/mfe/README.md @@ -24,10 +24,7 @@ Entando's microservice and micro frontend architecture allows developers to work - [Create an Angular Micro Frontend](./angular.md) -- [Add an App Builder configuration screen to a - widget](./widget-configuration.md) - -- [Display widget configuration](./widget-configuration.md#display-widget-configuration) +- [Add an App Builder configuration screen to a widget](./widget-configuration.md) - [Communicate Between Micro Frontends](./communication.md) diff --git a/vuepress/docs/next/tutorials/create/mfe/react.md b/vuepress/docs/next/tutorials/create/mfe/react.md index c3d8a9dac3..99bb3467a6 100644 --- a/vuepress/docs/next/tutorials/create/mfe/react.md +++ b/vuepress/docs/next/tutorials/create/mfe/react.md @@ -4,65 +4,58 @@ sidebarDepth: 2 # Create a React Micro Frontend - - ## Prerequisites -- [A working instance of Entando.](../../../docs/getting-started/) -- Use the Entando CLI to verify all dependencies are installed with the command `ent check-env develop`. +- [A working instance of Entando](../../../docs/getting-started/) +- Use the [Entando CLI](../../../docs/reference/entando-cli.md) to verify all dependencies are installed: +``` +ent check-env develop +``` -## Create React App -We'll use [Create React App](https://create-react-app.dev/) to generate a simple app in seconds. -1. Create 'my-widget' directory structure with the following: +## Create a React App +[Create React App](https://create-react-app.dev/) allows you to generate a simple app in seconds. +1. Create a React app: ``` bash npx create-react-app my-widget --use-npm ``` - -This is the expected output: +This tutorial updates the following files: my-widget ├── README.md - ├── node_modules - ├── package.json - ├── .gitignore ├── public - │ ├── favicon.ico - │ ├── index.html - │ ├── logo192.png - │ ├── logo512.png - │ ├── manifest.json - │ └── robots.txt + │ └── index.html └── src - ├── App.css ├── App.js - ├── App.test.js - ├── index.css - ├── index.js - ├── logo.svg - ├── serviceWorker.js - └── setupTests.js + └── index.js -2. Start the app +2. Start the app: ``` bash cd my-widget npm start ``` -### Wrap with Custom Element +The React app should open in your browser at `http://localhost:3000`. + +### Configure the Custom Element -1. Add a new file `src/WidgetElement.js` with the following custom element to wrap the entire React app +The steps below wrap the app component with an HTML custom element. The `connectedCallback` method renders the React app when the custom element is added to the DOM. + +1. In `my-widget/src`, create a file named `WidgetElement.js` + +2. Add the following code to `WidgetElement.js`: ``` js import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import App from './App'; class WidgetElement extends HTMLElement { connectedCallback() { this.mountPoint = document.createElement('div'); this.appendChild(this.mountPoint); - ReactDOM.render(, this.mountPoint); + const root = ReactDOM.createRoot(this.mountPoint); + root.render(); } } @@ -70,234 +63,106 @@ customElements.define('my-widget', WidgetElement); export default WidgetElement; ``` -The React `root` node is programatically generated in the `connectedCallback` method when the custom element is added to the DOM. -::: tip -`connectedCallback` is a lifecycle hook that [runs each time the element is added to the DOM.](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Using_the_lifecycle_callbacks) +::: tip Custom Element Names +- [Must contain a hyphen `-` in the name](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name) +- Cannot be a single word +- Should follow `kebab-case` naming convention ::: -::: tip Custom Elements -- [Must contain a hyphen `-` in the name.](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name) -- Cannot be a single word. -- Should follow `kebab-case` for naming convention. -::: +### Display the Custom Element -### Import Custom Element - -1. Open `src/index.js`. The initial file looks like: - -``` js -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import * as serviceWorker from './serviceWorker'; - -ReactDOM.render(, document.getElementById('root')); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); -``` - -2. Replace the entire file with these two lines +1. Replace the entire contents of `src/index.js` with these two lines: ``` js import './index.css'; import './WidgetElement'; ``` -### Test Micro Frontend - -1. Open `public/index.html` - -2. Replace `
` with the custom element `` +2. In `public/index.html`, replace `
` with this: ``` html - - - ... - ``` -::: tip Congratulations! -You’re now running `React` in a containerized micro frontend. -::: - -## Build the Resource URL - -Add your micro frontend to Entando by uploading the JavaScript and CSS files to the `public` folder. This is the way Entando makes files available to the public. - -### Add Widget - -First, add a widget to get the resource URL for the `public` folder. Then use the same widget to add the Micro Frontend to Entando. - -1. Go to `Components > Micro frontends & Widgets` in the App Builder - -2. Click `Add` in the lower right corner - -![New widget screen](./img/new-widget-screen.png) +3. Observe your browser automatically redisplay the React app -3. Enter the following: -- `Title: My Widget` → enter both English and Italian languages -- `Code: my_widget` → dashes are not allowed -- `Group: Free Access` -- `Icon`: → upload an icon of your choice -- In the center panel under `Custom UI`, enter the following: - -``` ftl -<#assign wp=JspTaglibs[ "/aps-core"]> -<@wp.resourceURL /> -``` -4. Click `Save` - -::: tip -`<#assign wp=JspTaglibs[ "/aps-core"]>` gives you access to the `@wp` object where you can use environment variables like `resourceURL`. +::: tip Congratulations! +You’re now using a custom element to display a React app. ::: +## Display the Micro Frontend in Entando +### Build the React App -### Add Page - -Next, add the widget to a page to view the `Resource URL`. -If you're getting started with a new install of Entando, add the widget to the `Home` page. - ---- - -> For Experienced Entando users: Add a new page → Add your widget to the page - ---- - -1. Go to `Pages` → `Management` - -2. Next to the `Home` folder, under `Actions`, → `Edit` - -3. In the `Title` field, choose `My Widget` - -4. In the Code field, choose `my_widget` - -5. Under Page groups, in the Owner group field, choose `Free Access` - -4. Scroll down to `Page Template` and select `Single Frame Page`. Leave all other fields blank or in the default setting. - -5. Click `Save and Design`. You are now in the page Designer. - -6. In the Search field of the right sidebar, type `My Widget`. It will show as an option. - -7. Drag and drop `My Widget` into the `Sample Frame` in the body of the page - -8. Click `Publish` - -9. In the top right corner, click `View Published Page`. This will take you to a blank home page with your widget. - -10. Copy the `Resource URL` at the top. For example, this is the URL in a quickstart environment set up via the Getting Started guide: - -``` -/entando-de-app/cmsresources/ -``` - -### Build It - -With the Resource URL where the new React App will be hosted, you are ready to build. - -1. Create an `.env.production` file in the root of `my-widget` project +To deploy the custom element into Entando as a micro frontend, you must perform a production build of the React app. -2. Add the `PUBLIC_URL` into the file. - -``` +1. In the `my-widget` directory, create an `.env.production` file that consists of one line: +``` text PUBLIC_URL=/entando-de-app/cmsresources/my-widget ``` + ::: warning Notes -- `/entando-de-app/cmsresources/` is the Resource URL for your Entando application -- `/my-widget` is the public folder that's created to host the files. +- `/entando-de-app/cmsresources/` is the Resource URL for your Entando Application, which matches nearly all development environments. Consult your Entando admin or use the following fragment to discover the Resource URL. +``` ftl + <#assign wp=JspTaglibs[ "/aps-core"]> + <@wp.resourceURL /> +``` +- `/my-widget` is the public folder that hosts the JavaScript and CSS files for the React app ::: -### npm build - -1. Open a command line and navigate to the project root of your `my-widget` - -2. Run the command: +2. Build the app: ``` bash npm run build ``` -3. Rename the following files generated in the `build` directory - -| Example of Generated Build File | Rename to | Function -| :--- | :--- | :--- -| build/static/js/2.f14073bd.chunk.js | `static/js/vendor.js` | Third-party libraries -| build/static/js/runtime-main.8a835b7b.js | `static/js/runtime.js` | Bootstrapping logic -| build/static/js/main.4a514a6d.chunk.js | `static/js/main.js` | App -| build/static/css/main.5f361e03.chunk.css | `static/css/main.css` | Stylesheet - -::: warning Generated Build Files -The JavaScript and CSS files are renamed so App Builder can deploy the new versions of the micro frontend without having to update the `Custom UI` field of the widget. -::: - -If you want to use the original [file names with the content hashes to avoid potential caching issues in your browser](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system), update the `Custom UI` field of your widget when deploying new versions of your micro frontend. The `Custom UI` settings will be covered in the next section. - -::: warning Additional Deployment Options -1. Install the micro frontend from a bundle in the `Entando Component Repository`. -2. Add the micro frontend to `Entando App Builder`. -3. Load the micro frontend from an API. -::: -## Host Micro Frontend -Now you are ready to host the micro frontend in Entando. +### Upload the React files -### Create Public Folder +To set up the micro frontend to use in Entando: -1. Navigate to `Entando App Builder` in your browser +1. In your App Builder, go to `Administration` → `File browser` → `public` -2. Click `Administration` at the lower left hand side of the screen +2. Click `Create folder` and name it "my-widget" to match the `.env.production` path above -3. Click the `File browser` tab +3. Click `Save` -4. Choose the `public` folder +4. Click `my-widget` -5. Click `Create folder` - -6. Enter `my-widget` - -7. Click `Save` - -8. Click `my-widget` - -9. Create the same folder structure as your generated build directory +5. Create a folder structure similar to your generated build directory: - `my-widget/static/css` - `my-widget/static/js` - `my-widget/static/media` -10. Upload the renamed files in the corresponding `js` and `css` folders - -- `my-widget/static/css/main.css` -- `my-widget/static/js/main.js` -- `my-widget/static/js/runtime.js` -- `my-widget/static/js/vendor.js` +6. Upload the css, js, and logo files from the corresponding directories under `my-widget/build/static`, for example: -Note: You can drag and drop the files in your browser +- `my-widget/build/static/css/main.073c9b0a.css` +- `my-widget/build/static/js/main.b9eb8fa4.js` +- `my-widget/build/static/media/logo.6ce2458023.svg` -11. Upload the `React` logo +> Note: The generated ID of each file name (e.g. '073c9b0a') may change after every build. These folders may also contain LICENSE.txt or .map files, but they are not applicable to this tutorial. +### Create the Widget -- `my-widget/static/media/logo.5d5d9eef.svg` → You don't need to rename this file +Add a widget for your MFE. -### Update Custom UI Field +1. In your App Builder, go to `Components` → `MFE & Widgets` -1. Go to `Components` → `Micro frontends & Widgets` +2. Click `Add` in the lower right corner -2. Under the `My Widgets` category → next to `My Widget` → under `Action` → select `Edit` +![New widget screen](./img/new-widget-screen.png) -3. Update `Custom UI` field: +3. Edit the fields below: +- `Title`: "My Widget" → enter this in both the English and Italian language fields +- `Code`: "my_widget" → dashes are not allowed +- `Group`: "Free Access" +- `Icon`: upload or select an icon of your choice +- In the center panel under `Custom UI`, enter the following. Make sure to use the actual file names from your build. ``` ftl <#assign wp=JspTaglibs[ "/aps-core"]> - - - - + + ``` @@ -305,16 +170,20 @@ Note: You can drag and drop the files in your browser ### View the Widget -View the React micro frontend in action on your page. +Place the React micro frontend onto a page to see it in action. + +1. In the `Entando App Builder`, go to `Pages` → `Management` -1. In the `Entando App Builder` go back to `Pages` → `Management` +2. Choose an existing page (or [create a new one](../../compose/page-management.md#create-a-page)) and select `Design` from its Actions -2. Next to the page you created, under `Actions`→ `Design`. This takes you back to the page Designer. +3. Find your widget in the `Widgets` sidebar and drag it onto the page -3. Click on `View Published Page` on the top right side +4. Click `Publish` -![React Micro Frontend](./img/react-micro-frontend.png) +3. Click on `View Published Page` + + ::: tip Congratulations! You now have a React micro frontend running in Entando. -::: +::: \ No newline at end of file diff --git a/vuepress/docs/v7.0/tutorials/create/mfe/README.md b/vuepress/docs/v7.0/tutorials/create/mfe/README.md index ce3f2e43e1..74219a4819 100644 --- a/vuepress/docs/v7.0/tutorials/create/mfe/README.md +++ b/vuepress/docs/v7.0/tutorials/create/mfe/README.md @@ -24,8 +24,7 @@ Entando's microservice and micro frontend architecture allows developers to work - [Create an Angular Micro Frontend](./angular.md) -- [Add an App Builder configuration screen to a - widget](./widget-configuration.md) +- [Add an App Builder configuration screen to a widget](./widget-configuration.md) - [Communicate Between Micro Frontends](./communication.md) From 812d2e53c98893627753aef373f8611cfb9199b7 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Tue, 7 Jun 2022 10:31:33 -0700 Subject: [PATCH 7/7] ENDOC-502-mfe-config Fix dead link --- vuepress/docs/next/tutorials/create/mfe/communication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuepress/docs/next/tutorials/create/mfe/communication.md b/vuepress/docs/next/tutorials/create/mfe/communication.md index 79a157c381..e224039538 100644 --- a/vuepress/docs/next/tutorials/create/mfe/communication.md +++ b/vuepress/docs/next/tutorials/create/mfe/communication.md @@ -275,7 +275,7 @@ You’ve now created a micro frontend that listens to custom events. Now let's add the publisher and subscriber micro frontends in Entando. -> Note: These follow the same steps as in the [Create a React Micro Frontend](./react.md#build-it) tutorial. +> Note: These follow the same steps as in the [Create a React Micro Frontend](./react.md#build-the-react-app) tutorial. ### Create Environment File