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.
Simply follow the instructions above to login and change configurations, then run the usual routine:
npm install npm start
-
Init new project
npm init
-
Install widget base
npm install @tecnojest/widget-base
-
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" }
-
(optional) Create a file called
.gitignore
and paste the following lines:gitignore node_modules coverage **/dist/ .DS_Store .env package-lock.json
-
Install remaining packages (from official registry to speed up download)
npm install --registry=https://registry.npmjs.org/
-
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" }
-
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:
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).
If you also use ReactJS then install the basic modules: npm install react react-dom
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"]
}
-
Start the project
npm start
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";
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
fromdefaultProps
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; }
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.
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
indefaultProps
will be overridden by theoptions
static variable
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
.
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.
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:
- User adds a widget to a dashboard and chooses a dark theme.
- 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.
- The hosting platform invokes
EmbeddableWidget.applyExternalCSSClasses({
uid: WIDGET_UID,
classes: [
{ role: 'background', className: 'background--dark' },
{ role: 'foreground', className: 'foreground--dark' },
],
});
- 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>;
- ✅ Success! User sees the dark-themed widget.
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
.
If you are using ReactJS for programming the Widget: use PropTypes!!
Link packages:
npm link # in package folder of @tecnojest/widget-base
npm link @tecnojest/widget-base
Apply all needed modifications, then when ready to publish run:
npm run deploy