1.0.0 • Published 3 years ago

@siil78/react-widgets v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
3 years ago

Example Widget semantic-release

This example contains documentation and example code for creating widgets using React.

View the demo

Requirements

Getting started

  1. On the react-widgets project page click the "Use this template" button to setup a new widget.
  2. Give the new repository a name and a description and then click the "Create repository from template" button.
  3. Clone the new repository and change the project name, version, and homepage inside package.json.
  4. Change the name of the widget render function(renderReactWidgets) in src/index.js. Note: It is recommended to use something unique to the widget in order to avoid potential conflicts with other widgets, like the widget repository name.
  5. Change the renderFunctionName in the header of public/index.html to match the designated widget render function from the previous step (i.e. renderReactWidgets)
  6. Install the project dependencies using npm install
  7. Run the project locally using npm start

IMPORTANT: Make sure you check the widget development documentation when developing your own.

widget.json

There is a special file called widget.json in the root of the widget. Widget registries, like this example, need this file to learn about this widget. It contains the following keys:

NameRequiredExampleDescription
shortcodeyesproduct-catalogThe machine name identifier for the widget.
descriptionnoA catalog of products for our company.A longer description for the widget. This is shown in the widget catalog.
availableTranslationsyes['en', 'es']Language codes this widget is available in for internationalization purposes.
settingsSchemanoA JSON Schema object decribing the input parameters for the widgets at embed time.
externalPeerDependenciesnoList of runtime dependencies for this widget that embedders need to add along with the widget.
statusyesstableOne of stable, beta, wip, or deprecated.

Configurable widgets

Configuration parameters are specified during the embed process. Drupal will create a form element automatically to gather those parameters in the editorial screens. For the CMS to know what form element to use, the widget definition needs to include a JSON Schema definition for every parameter.

This repository contains an example of a configurable parameter. In this case it's the text of the button. This is described in widget.json as:

  "settingsSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "fields": {
        "type": "object",
        "properties": {
          "button-text": {
            "type": "string",
            "title": "Button text",
            "description": "Some random string to be displayed when the widget is rendered.",
            "examples": ["I am a button", "Please, click me", "CLICK"]
          }
        }
      }
    }
  },

And then accessed in the widget code Widget.jsx:

<p className="is-size-6 pb-4">
  <button className="button is-primary">{element.getAttribute('data-button-text')}</button>
</p>

Note that since the field name is button-text, the value is accessed as 'data-button-text'.

External dependencies

externalPeerDependencies is a tool designed to share JS dependencies across different widgets. This module provides an example how to avoid bundling react, react-dom, and react-intl with the widget's JS, while making Drupal (or any other available integrations) load the dependencies automatically.

For each dependency you will need to:

  1. Tell Webpack to not include the library in the resulting JS file(s) for this widget.
    // webpack.config.js or craco.config.js
    externals: {
        react: 'React',
        'react-dom': 'ReactDOM',
        'react-intl': 'ReactIntl',
    },
    // ...
  1. Tell the widget registry (in widget.json), and ultimately the CMS integrations where to find these libraries that were excluded.
"externalPeerDependencies": {
    "react": {"src": "https://unpkg.com/react@^17/umd/react.production.min.js"},
    "react-dom": {"src": "https://unpkg.com/react-dom@^17/umd/react-dom.production.min.js"},
    "react-intl": {"src": "https://unpkg.com/react-intl-bundle@^1/dist/react-intl.production.min.js"}
},

Continuous Integration

Testing and deployment scripts available inside this example repository using GitHub Actions.

Usage

Note: Changes made to the src/public/index.html file are for development and preview purposes only and will not be compiled into the production version of the widget.

Widgets are referenced in the header of the page and rendered using an orchestrator script. The document.loadWidget and document.loadWidgets functions allow rendering of multiple widgets, and multiple instances of a single widget.

loader.js

Within the <head> tag reference the widget load callback(renderFunctionName) and tell it which div(instanceId) to render the widget in.

Projects embedding widgets should include the loader script maintained in the Widget Registry. Include the script in the HTML as:

<script
  src="//js-widgets.github.io/widget-registry-boilerplate/widget-registry/production/loader.js"
  type="application/javascript"
></script>

Remember to use your version of the widget registry instead of widget-registry-boilerplate.

document.loadWidget()

document.loadWidget({
  renderFunctionName: 'renderReactWidgets',
  instanceId: 'react-widgets-1',
  language: 'de',
  onRenderFinish: (renderedElement) => {
    alert('Render process finished.');
  },
});

Within the <body> tag add the instanceId div wherever you want this widget to render.

<div id="react-widgets-1" data-button-text="foobar"></div>

document.loadWidgets()

document.loadWidgets({
  'widget-1': {
    renderFunctionName: 'renderReactWidgets',
    instanceId: 'react-widgets-1',
  },
  'widget-2': {
    renderFunctionName: 'renderReactWidgets',
    instanceId: 'react-widgets-2',
  },
});
<div id="react-widgets-1" data-button-text="foo"></div>
<div id="react-widgets-2" data-button-text="bar"></div>

Parameters

NameRequiredDefaultExampleDescription
renderFunctionNameyesrenderReactWidgetsThe render function callback.
instanceIdyesreact-widgets-1The already present HTML element ID where the react app will be rendered.
languagenoendeThe language code for internationalization purposes.
originnowindow.location.originhttps://www.example.orgProtocol and hostname where a JSONAPI endpoint is available.
onRenderFinishnoA callback that executes after the widget has been rendered.

Attributes

Data attributes of the instanceId div are accessible from the <App /> React component using the getAttribute() method.

import React, { Component } from 'react';

class Widget extends Component {
  render() {
    return <div className="App">{this.props.obj.getAttribute('data-button-text')}</div>;
  }
}

export default Widget;

DIV attributes

<div id="react-widgets-1" data-button-text="Hello world!"></div>

Query string values

http://localhost:3000/?data-button-text=Hello%20world!

Note: DIV attributes are the preferred method and will always take precedence over the use of query string values. Query string values are a good way to test a widget quickly without having to alter the HTML source code.

Translations

The boilerplate widget has built-in translation support using the react-intl module. Use the FormattedMessage to create translatable strings within the widget.

<FormattedMessage id="App.welcomeMsg" defaultMessage="Welcome!" />

Create all translation messages in src/messages.js, following the provided example. Generate locales using the npm run build:locales command. This will scan src/messages.js for translatable strings and compile them into JSON files for translation under src/locales/*.json.

Supported Languages

LanguageCode
Arabicar
Germande
Englishen
Spanishes
Latin American Spanishesla
Frenchfr
Italianit
Japaneseja
Koreanko
Polishpl
Portuguesept
Russianru
Turkishtr
Simplified Chinesezh-cn
Traditional Chinesezh-tw

Typescript

In an effort to improve overall code quality and reduce run-time exceptions, we strongly encourage widget developers to use Typescript. We understand, though, that using Typescript can be challenging if you don't have prior experience with it and we don't want to impose that restriction upon you.

If you feel comfortable with Typescript or are willing to learn about it (here and here are good resources), you can run npm run use-typescript, which will install all the libraries you'll need, update file extensions, and make appropriate configuration changes.

Styling

In order to scope widget styles to the widget without adversely affecting the styles of the page within which it's embedded, we're using a library called craco. This library allows us to override configuration in apps bootstrapped with Create React App, which is otherwise a closed system. By using this method, we can make alterations to confuration without needing to eject the app. This along with a PostCSS plugin called postcss-wrap allows us to define a CSS class that will be prepended to all CSS selectors, effectively namespacing all styles.

Uncomment the style rule specified in index.css and start the app to see the prepended class from postcss.config.js added. Remember to define your custom namespace class in postcss.config.js and add it to the top-most element in the app, as seen in Widget.jsx. If you need to use this method, please remove the index.css file and where it's imported in index.js.

It is recommended to rely on the above method strictly for usage of installable component libraries. For custom styling, we recommend installing Emotion, which is a library for writing CSS-in-JS.

Testing

This boilerplate is setup to use Jest and @testing-library/react for testing. See Widget.test.js for an example of how to use these libraries.

Current Maintainer(s)