This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

Custom Widgets

Custom widgets are independent applications, tocco-apps, that can be embedded on external websites.

Widget package

Create a new package for your widget in the widgets folder:

yarn plop Package

Add the widget to a bundle:

yarn plop Bundle app

The src/main.js is the entry point of the widget.

Env

Define the env inside the initApp of the main.js. There is a helper function env.setInputEnvs(input) which sets the env vars from the input automatically. Without the correct envs the core component will not behave properly for widgets (e.g. entity-detail should not render the meta-information inside the footer).

import {env} from 'tocco-util'
...

const initApp = (id, input, events, publicPath) => {
    const content = <MyWidget />

    // set env from input
    env.setInputEnvs(input)

    const store = appFactory.createStore(reducers, sagas, input, packageName)
    ...
    return appFactory.createApp(packageName, content, store, {...})
}

Input / PropTypes

The widgets receives the config from the Widget Config entity as input object with some additional meta information:

  • appContext object containing the widgetConfigKey

  • backendUrl

  • locale

Note

The selection prop type, which has been added automatically, can be removed for widgets.

Example:

src/main.js
import PropTypes from 'prop-types'
import {appContext} from 'tocco-util'

const initApp = (...) => {
  ...
}

...

const ExampleWidgetApp = props => {
  ...
}

ExampleWidgetApp.propTypes = {
    appContext: appContext.propTypes.isRequired,
    backendUrl: PropTypes.string,
    locale: PropTypes.string,
    ...
}

Name

Description

Example

appContext

Meta information about the embedded widget. Available embedTypes are: admin, legacy-admin, widget, legacy-widget

{
  "embedType": "widget",
  "widgetConfigKey": "123",
}

backendUrl

The base url of the nice2 backend.

https://master.tocco.ch

locale

The locale of the widget. If the locale is set the widget should render this locale instead of the logged in user.

de-CH

Visibility status integration

Your widget app will get a function onVisibilityStatusChange as an input property. This can be used to communicate to the system running the widget that some state, in which it might want to display some content, was reached. An example are the status list and detail in the entity-browser app that get triggered when the user navigates to one of the corresponding sites, or the success status in the event-registration app that gets triggered when the registration is successfully created.

See Visibility status configuration on how to configure visibility status to be available on a widget config.

When implementing your own visibility status, we recommend creating a visibilityStatus.js status file in the root of your src folder, containing an object of all your implemented visibility status and then only using the values defined in there. This ensures no typo is made when changing visibilty status which would create hard to debug errors, as well as serving as a common way to document all available status in code.

Instead of passing onVisibilityStatusChange through your app and calling it by hand, use the action creator externalEvents.fireVisibilityStatusChangeEvent.

Example visibilityStatus.js
export const VisibilityStatus = {
    list: 'list',
    detail: 'detail',
    success: 'success'
}
Example app that uses fireVisibilityStatusChangeEvent to change to success state
// ExampleAppContainer.js
import {connect} from 'react-redux'
import {externalEvents} from 'tocco-app-extensions'

const mapActionCreators = {
    fireVisibilityStatusChangeEvent: externalEvents.fireVisibilityStatusChangeEvent
}

const mapStateToProps = state => ({})

export default connect(mapStateToProps, mapActionCreators)(ExampleApp)

// ExampleApp.js
import {VisibilityStatus} from './visibilityStatus'

const ExampleApp = ({fireVisibilityStatusChangeEvent}) => {
    fireVisibilityStatusChangeEvent([VisibilityStatus.success])
}

For externalEvents.fireVisibilityStatusChangeEvent to work you’ll have to make sure that externalEvents has been added to your apps store with the correct events.

Part of a main.js that adds externalEvents to the store
const EXTERNAL_EVENTS = ['onVisibilityStatusChange']

const initApp = (id, input, events, publicPath) => {
    const store = appFactory.createStore(reducers, sagas, input, packageName)
    externalEvents.addToStore(store, state => appFactory.getEvents(EXTERNAL_EVENTS, state.input))
}

Reuse the entity browser

Often the entity browser can be reused if a custom widgets is implemented. For example if a widget configuration parameter influences the form definition (e.g. box removed or creation allowed), the modifyFormDefinition of the EntityBrowserApp can be used. All other input properties are passed to the entity-browser. As an example:

const ExampleApp = ({
  allowCreate,
  reportIds,
  searchFilters,
  limit,
  backendUrl,
  businessUnit,
  appContext,
  intl,
  onVisibilityStatusChange
}) => {
  const modifyFormDefinition = formDefinition => {
    if (allowCreate) {
      return form.addCreate(formDefinition, intl)
    } else {
      return form.removeActions(formDefinition, ['new', 'copy'])
    }
  }

  return (
    <EntityBrowserApp
      entityName="ENTITY"
      formBase="FORM_BASE"
      searchFilters={searchFilters}
      limit={limit}
      modifyFormDefinition={modifyFormDefinition}
      backendUrl={backendUrl}
      businessUnit={businessUnit}
      appContext={appContext}
      reportIds={reportIds}
      onVisibilityStatusChange={onVisibilityStatusChange}
    />
  )
}

The app-extensions provides serveral helper methods for modifing the form definition (See JSDoc on how to use them). It’s possible to dynamically modify the form definition. As an example only remove a box on the detail if an entity is related to a certain lookup entity value.

Sometimes it is necessary to add a field which is not part of the form definition (e.g. a mandatory checkbox for the privacy protection). Add a pseudo form field to the form definition. Additionally use the modifyEntityPaths to add the entity paths of the pseudo fields. Once the form is initialized modifying the entity paths has no effect.

const ExampleApp = ({
  // ...
}) => {
  const fields = [
    form.createCheckbox('privacy_protection', false, 'privacy protection label', true)
  ]

  const modifyFormDefinition = formDefinition => form.appendChildrenToBox(
      formDefinition,
      'master_data',
      fields.map(f => f.formField))

  const modifyEntityPaths = entityPaths => ({
    ...form.getMergedEntityPaths(fields),
    ...entityPaths
  })

  // also pass other properties to entity-browser
  return (
    <EntityBrowserApp
      modifyFormDefinition={modifyFormDefinition}
      modifyEntityPaths={modifyEntityPaths}
    />
  )
}