Skip to content

Latest commit

 

History

History
402 lines (298 loc) · 13 KB

README.md

File metadata and controls

402 lines (298 loc) · 13 KB

Tecnojest Widget Base

Get started

To be able to use the widget API you need to integrate this package in your project. Follow the steps in New project if you are creating a new project from scratch, or in Existing project if you are trying to run an existing project locally. In either case you will need to configure your local npm engine to point to Tecnojest's registry, by doing the following:

# authenticate in the private npm registry
npm login --registry https://npm.invidea.it
# set the registry to point to @tecnojest's scope
npm config set @tecnojest:registry https://npm.invidea.it

if during the first npm login step you receive a UNABLE_TO_VERIFY_LEAF_SIGNATURE error, then run:

npm config set strict-ssl false

and try again.

Existing project

Simply follow the instructions above to login and change configurations, then run the usual routine:

npm install npm start

New project

  1. Init new project

    npm init
    
  2. Install widget base

    npm install @tecnojest/widget-base
    
  3. Add peer dependencies into devDependcies of package.json file:

    "devDependencies": {
            ...
            "@babel/core": "7.7.4",
            "@babel/plugin-proposal-class-properties": "7.7.4",
            "@babel/plugin-proposal-decorators": "7.7.4",
            "@babel/plugin-proposal-export-namespace-from": "7.7.4",
            "@babel/plugin-proposal-function-sent": "7.7.4",
            "@babel/plugin-proposal-json-strings": "7.7.4",
            "@babel/plugin-proposal-numeric-separator": "7.7.4",
            "@babel/plugin-proposal-throw-expressions": "7.7.4",
            "@babel/plugin-syntax-dynamic-import": "7.7.4",
            "@babel/plugin-syntax-import-meta": "7.7.4",
            "babel-eslint": "10.1.0",
            "babel-loader": "8.1.0",
            "babel-preset-airbnb": "4.4.0",
            "clean-webpack-plugin": "^3.0.0",
            "compression-webpack-plugin": "^3.1.0",
            "copy-webpack-plugin": "5.0.5",
            "css-loader": "3.2.0",
            "cssimportant-loader": "0.4.0",
            "dotenv": "^8.2.0",
            "dotenv-webpack": "^1.7.0",
            "duplicate-package-checker-webpack-plugin": "^3.0.0",
            "eslint": "6.7.1",
            "eslint-config-airbnb": "18.0.1",
            "eslint-loader": "3.0.2",
            "eslint-plugin-import": "2.18.2",
            "eslint-plugin-jsx-a11y": "6.2.3",
            "eslint-plugin-react": "7.17.0",
            "eslint-plugin-react-hooks": "1.7.0",
            "file-loader": "^6.0.0",
            "html-webpack-plugin": "^3.2.0",
            "mini-css-extract-plugin": "0.8.0",
            "node-sass": "4.13.0",
            "postcss-increase-specificity": "0.6.0",
            "postcss-loader": "3.0.0",
            "raw-loader": "^4.0.1",
            "sass-loader": "8.0.0",
            "source-map-loader": "^1.0.1",
            "style-loader": "1.0.1",
            "tape": "^4.13.0",
            "uglify-loader": "^3.0.0",
            "uglifyjs-webpack-plugin": "^2.2.0",
            "url-loader": "^4.0.0",
            "webpack": "4.42.0",
            "webpack-cli": "3.3.10",
            "webpack-dev-server": "3.10.3",
            "webpack-obfuscator": "0.18.5",
            "webpack-serve": "3.2.0",
            "webpack-shell-plugin": "^0.5.0"
        }
  4. (optional) Create a file called .gitignore and paste the following lines:

    gitignore
    node_modules
    coverage
    **/dist/
    .DS_Store
    .env
    package-lock.json
    
  5. Install remaining packages (from official registry to speed up download)

    npm install --registry=https://registry.npmjs.org/
    
  6. Add start and build script into script part of package.json:

    "scripts": {
        ...
        "build": "webpack-cli  --config ./node_modules/@tecnojest/widget-base/webpack.config.js --mode production",
        "start": "webpack-dev-server --config ./node_modules/@tecnojest/widget-base/webpack.config.js --host 0.0.0.0"
    }
  7. Create a main file named src/index.js in the project root dir, implementing the basic methods of the JavaScript API:

  • createElement(props): ...
  • render(component,el): ...
  • unmountComponentAtNode(node): ...

when developing a widget based on React the configuration would be:

Index file

import EmbeddableWidget from '@tecnojest/widget-base';
import Widget from './components/widget';
import React from 'react';
import ReactDOM from 'react-dom';

EmbeddableWidget.Widget = Widget;
EmbeddableWidget.React = React;
EmbeddableWidget.Engine = {
  createElement: (props) => {
    return <Widget className={process.env.WIDGET_MAIN_CSS_CLASS} {...props} />;
  },
  render: (component, el) => ReactDOM.render(component, el),
  unmountComponentAtNode: (node) => ReactDOM.unmountComponentAtNode(node),
};

export default EmbeddableWidget;

This file must include at least the EmbeddableWidget.Engine part, implementing the three methods createElement(...), render(...), and unmountComponent(...) - (the example above uses React and ReactDOM).

React

If you also use ReactJS then install the basic modules: npm install react react-dom

TypeScript

If you want to use TypeScript then install the necessary modules:

npm install typescript ts-loader @babel/preset-env @babel/preset-typescript

Then create a tsconfig.json file at the root of your project and paste the following configuration:

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "strictNullChecks": true,
    "module": "commonjs",
    "jsx": "react",
    "target": "es5",
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "skipLibCheck": true,
    "esModuleInterop": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}
  1. Start the project

    npm start
    

Refactoring the code base

ReactJS

If you have bootstrapped your application using the create-react-app scripts, follow the steps for getting started in an New project and make sure that you substitute the content of the default index.js file with the new index.js from step 5. Simply change this line in that code snippet:

    import Widget from "./App";

Configuration

Object attributes

You can define the following static in the main Widget class:

  • externalScripts (static) e.g.:

    static externalScripts = [
      {
        'name': 'jquery-validate',
        'src': 'https://ajax.aspnetcdn.com/ajax/jquery.validate/1.7/jquery.validate.min.js'
        'depends_on': 'jquery'
      },
      {
        'name': 'jquery',
        'src': 'https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.js'
      }
    ];
  • options (static) - options that apply to all widgets in the same namespace, e.g.:

    They will override options from defaultProps

    static options: Options = {
      appendFooter: true;
      appendTooltip: true;
      singleton: true;
    }

    The above are a subset of the Options object below:

    interface Options {
      // should append a footer at the bottom
      appendFooter?: boolean;
      // should append a tooltip with dependencies at the bottom
      appendTooltip?: boolean;
      // can only one widget of the type exist
      singleton?: boolean;
      // should a cookie be set with the token
      setCookie?: boolean;
      // cookie domain used for setting the cookie
      setCookieDomain?: string | null;
      // should use AJAX to load the script instead of appending a <script> tag
      loadScriptAJAX?: boolean;
      // the token used for authentication
      token?: string;
      // name of the token (the key in localstorage)
      tokenName?: string;
      // main css class of the widget
      className?: string;
      // credentials can be saved here from the hosting environment
      credentials?: string | Object;
      // can be used to emit scoped events
      scope?: string;
    }

Environmental variables

The following environmental variables are evaluated at runtime, overwrite them to change the desired behaviour:

  • WIDGET_TITLE: The widget title displayed in the main page (default to project's title in package.json file)
  • WIDGET_MAIN_CSS_CLASS: Add a class name to every element of the widget to increase specificity.
  • WIDGET_HIDE_EXPLANATION: Should the widget title and a link to the latest widget's version script be shown on the page?
  • WIDGET_ASSET_SERVER: Path to the server used for static asset distribution (public path). Defaults to "/".
  • WIDGET_USE_CLEANSLATE: Should a CSS reset be applied?
  • WIDGET_UNCOMPRESSED: Should a compressed version of assets be provided in the production mode?
  • PORT: The port to be used for the development server. Defaults to 80.

ReactJS

If you use any default props in the component, define them as a static attribute in your component class definition, e.g.:

static defaultProps = { ... };

options in defaultProps will be overridden by the options static variable

Usage

The static SDK methods can be accessed in the browser console through the window global object. The name of the property will be the title of your widget (WIDGET_TITLE env variable) or, if not defined, the name of your package specified in package.json.

Widget API

Example taken from ./templates/TypeScript React Here you can see some of the methods available through the API. Of course all of those can be used in code as well.

Examples

Applying dark/light theme based on the hosting platform's preferences

To accomplish our goal, we need to have the ability to add some classes to our elements at runtime. Let us first outline the usage flow:

  1. User adds a widget to a dashboard and chooses a dark theme.
  2. The hosting platform knows that it should apply the dark theme to widget(s), but it does not know which elements can be styled in a particular widget.
  3. The hosting platform invokes
EmbeddableWidget.applyExternalCSSClasses({
  uid: WIDGET_UID,
  classes: [
    { role: 'background', className: 'background--dark' },
    { role: 'foreground', className: 'foreground--dark' },
  ],
});
  1. The SDK applies styles to all elements under our widget (resolved by uid) which are found by
classes.forEach((externalClass: ExternalCSSClass) => {
  const querySelector = `[data-role="${externalClass.role}"]`;
  this.injectCSSClass({ uid, className: externalClass.className, querySelector });
});

Note that, for the SDK to be able to style your widget, you have to define the appropriate data-role attributes on elements to be styled. These have to be consistent with the hosting platform's code too, otherwise no styles will be applied. Example:

const MyElement = () => <h2 data-role="heading">My widget</div>;
  1. ✅ Success! User sees the dark-themed widget.

Best practices

Variables

The widget base already includes Webpack DotEnv plugin to parse environmental variables. We therefore suggest to create a file called .env in the project root and define all the variables as key=value lines. We also advise to include this file in your .gitignore.

Prop types

If you are using ReactJS for programming the Widget: use PropTypes!!

Contribute

Hot reload

Link packages:

npm link # in package folder of @tecnojest/widget-base
npm link @tecnojest/widget-base

Publish new version

Apply all needed modifications, then when ready to publish run:

npm run deploy