Skip to content
Branch: master
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
public
src I18n approach #33 Oct 31, 2019
.editorconfig React client Oct 24, 2018
.env.development.local React client Oct 24, 2018
.env.production.local React client Oct 24, 2018
README.md [React] More docs Oct 31, 2019
_editorconfig Migrate to cuba-react Aug 7, 2019
_gitignore
images.d.ts
package.json
tsconfig.json
tsconfig.prod.json React client Oct 24, 2018
tsconfig.test.json
tslint.json React client Oct 24, 2018

README.md

CUBA React Front-end Client

Overview

React client is an alternative to Generic UI which provides front-end oriented development experience. It's more flexible in terms of layout customization and allows easily integrate UI libraries and components from vast JavaScript ecosystem. However it requires better knowledge of modern front-end stack.

Technologies

The client is based on the following frameworks and libraries:

You will need IDE with TypeScript support: VSCode, WebStorm or IntelliJ IDEA Ultimate Edition.

Supported Browsers

The client supports all modern (evergreen) browsers. In order to support IE 9,10,11 additional configuration required.

Getting Started

Creating an App

There are two ways to create React client:

  1. As a module of CUBA application (using CUBA Studio). You will be able to create CRUD screens using STUDIO's UI.
  2. As a standalone front-end app (using command line interface)
gen-cuba-front react-typescript

See the above link for step-by-step instruction.

Project Layout

Here is the structure of the newly generated project:

app-name/
  package.json
  package-lock.json
  node_modules/
  public/
    index.html
    favicon.ico
  src/
    index.css
    index.tsx          <- App entry point. Do not move/rename this file
    routing.ts         <- Routing configuration
    app/
      App.css
      App.tsx          <- App shell. Switches between Login form and internal application
    cuba/              <- CUBA Model. See [Backend model]
      entitites/       <- Project entities
        base/          <- Entities from addons and framework
      enums/           <- Project enums

If client was generated using Studio it's placed in modules/front directory of main project.

Development

Creating React Components

It is highly recommended to read full React documentation. In React, like in many modern frameworks everything is a component. We use components to create reusable blocks of our application as well as particular pages and screens.

Let's create our first component: place file Button.tsx in src directory:

import React, { Component } from 'react';

export class Button extends Component {
  render() {
    <button>Click me</button>;
  }
}

Alternatively, you can create the component using a function:

export function Button(props) {
  return <button>{props.name}</button>;
}

Observable State with MobX

MobX is a library for reactive state management which enables to work with state in a convenient and concise way.

Consider the following example:

@observer 
class Counter extends React.Component {

  @observable
  count = 0;
  
  render() {
    return (
      <div>
        Counter: {this.count} <br />
        <button onClick={this.handleInc}> + </button>
        <button onClick={this.handleDec}> - </button>
      </div>
    )
  }

  handleInc = () => {
    this.count++;
  }

  handleDec = () => {
    this.count--;
  }
}

As soon as we decorate a class or a function component as observer, it automatically subscribes to changes on any observable value or object i.e. in the example above changing count property will result in automatic re-render of the component.

CUBA React Components

MainStore

MainStore contains common application data. It's being initialized using <CubaAppProvider>:

<CubaAppProvider cubaREST={cubaREST}>
   // App component tree
</CubaAppProvider>

You can inject it in any component using @injectMainStore decorator:

@injectMainStore
@observer
export class AppInfo extends React.Component<MainStoreInjected> {
  render() {
    if (!this.props.mainStore) {
      return null;
    }
    const {
      initialized,
      authenticated,
      userName,
      metadata,
      messages,
      enums
    } = this.props.mainStore;
    return (
      <ul>
        <li>App initialized: {initialized ? 'yes' : 'no'}</li>
        <li>User authenticated: {authenticated ? 'yes' : 'no'}</li>
        <li>User name: {userName}</li>
        <li>Metadata: {JSON.stringify(metadata)}</li>
        <li>Messages: {JSON.stringify(messages)}</li>
        <li>Enums: {JSON.stringify(enums)}</li>
      </ul>
    )
  }
}
DataCollectionStore

DataCollectionStore is a MobX based store for loading entity collections. It can be created via collection() initializer function:

dataCollection = collection<Pet>(Pet.NAME, {
    view: 'pet-with-owner-and-type',
    sort: 'identificationNumber',
    filter: {conditions: [{property: 'name', operator: "contains", value: 'Ro'}]},
    limit: 10,
    offset: 0,
    loadImmediately: true, // true by default
  }
);

Typically it's being used to display list of entities. Since it's reactive, any changes in items and status will trigger re-render of @observer components:

@observer
class CarList extends React.Component {
  carsData = collection<Car>(Car.NAME, {view: 'car-view', sort: '-updateTs'});
  render() {
    if (this.carsData.status === "LOADING") return 'Loading...';
    return (
      <ul>
        {this.carsData.items.map(car =>
           <li>{car._instanceName}</li>
        )}
      </ul>
    )
  }
}
DataInstanceStore

DataInstanceStore is used to work with a single instance of some Entity. It can be created via instance() initializer function:

dataInstance = instance<Pet>(Pet.NAME, {view: 'pet-with-owner-and-type', loadImmediately: false});

Use dataInstance.commit() method to perform entity update:

dataInstance.item.name = 'New Name';
dataInstance.commit()
EntityProperty

<EntityProperty> component is aimed to display a value of some Entity's property. It automatically applies formatting according to the type of property and adds corresponding label from global message pack (defined on the backend)

<EntityProperty entityName={Pet.NAME}
                propertyName='birthDate'
                value={pet.birthDate}/>
FormField

<FormField> component automatically creates correct Form UI component based on entity and property names:

<FormField entityName={Pet.NAME} propertyName='birthDate'/>

For the attributes with relationship it's possible to provide instance of DataCollectionStore via optionsContainer prop to render options list

petTypesDc = collection<PetType>(PetType.NAME, {view: '_minimal', sort: 'name'});
...
<FormField entityName={Pet.NAME}
           propertyName='type'
           optionsContainer={this.petTypesDc}/>
DataTable

<DataTable> is used to present data in tabular form.

Data table showcase

It uses Ant Design's Table under the hood and provides the following additional benefits:

  • out-of-the-box integration with DataCollectionStore
  • powerful filters
  • support for action buttons (e.g. for CRUD operations)

At the same time <DataTable> provides developer with a full access to underlying Table via its tableProps and columnProps properties (see below).

Example of using <DataTable>'s API:

<DataTable dataCollection={this.dataCollection}
           fields={this.fields}
           onSelectedRowChange={this.onSelectedRowChange}
           buttons={buttons}
           defaultSort={'-updateTs'}
           tableProps={{
             bordered: true
           }}
           columnProps={{
             align: 'right'
           }}
/>
  • dataCollection - instance of DataCollectionStore
  • fields - array of entity property names
  • onSelectedRowChange - callback that takes the id of selected row, can be used together with buttons e.g. to facilitate CRUD operations
  • buttons - array of React elements representing controls that will be rendered above the table
  • defaultSort - name of the field to be sorted by. If the name is preceeding by the '+' character, then the sort order is ascending, if by the '-' character then descending. If there is no special character before the property name, then ascending sort will be used.
  • tableProps - can be used to override any of the underlying Table properties
  • columnProps - can be used to override any of the underlying Column properties. It shall be used instead of redefining columns in tableProps if the goal is to extend rather that fully replace the existing custom column-related functionality.

Routing

Routing is based on well-known React Router library. The generated app has a single point (src/routing.ts) to define screens which will be automatically placed in the main menu:

mainRoutes.push({
  pathPattern: '/pets', // pattern may be used to consume some parameters, e.g.: /pets/:petId?
  menuLink: '/pest',
  component: PetBrowser, // component to be rendered, should be imported in `routes.ts`
  caption: 'Pets' // Menu item caption
});

The src/App.tsx contains Switch component which renders screen depending on the URL path:

  <Switch>
    <Route exact={true} path="/" component={HomePage}/>
    {mainRoutes.map((route) =>
      <Route key={route.pathPattern} path={route.pathPattern} component={route.component}/>
    )}
  </Switch>

You can manually add Route to Switch component or customize the structure used in routes.ts for example in order to create hierarchical menu.

Forms

In order to facilitate data binding, Ant Design's Form component and utilities are used in the app.

getFieldDecorator is a useful higher order function which allows you to setup validation and binding. See the following example:

  <Form.Item label='name'>
     getFieldDecorator('model', {
       normalize: (value) => {
         return value === '' ? null : value; // Normalize value so that empty string is converted to null
       },
       rules: [ // Allows you to setup front-end validation rules
         {required: true} 
       ]
     })(
        <FormField entityName={Entity.NAME}
                   propertyName='model'/>
     )}
  </Form.Item>

i18n

i18n is powered by react-intl library.

Out of the box React client supports en and ru locales.

To add new localized content
  • Add new messages to src/i18n/{locale}.json files
  • Refer to them from your code using standard react-intl components or API (see documentation)
To override existing messages

Simply replace existing messages in src/i18n/{locale}.json files. This way you can override messages in client app, cuba-react components and some of the messages in antd components.

Adding support for new locales
  • Add a corresponding {locale}.json message pack. Note that it shall contain messages for cuba-react components (keys starting with cuba-react) and antd Form validation messages (keys starting with antd.form.validation)
  • Create a mapping between locale and message pack by modifying messagesMapping in src/i18n/i18nMappings.ts
  • Create a mapping between locale and antd/es/locale-provider/Locale object by modifying antdLocaleMapping in src/i18n/i18nMappings.ts.

This is required because most of the messages in antd components are translated by telling antd to use one of the predefined locales. An extensive list of locales supported by antd can be found here.

  • Add import of corresponding moment locale to index.tsx, e.g. import 'moment/locale/ru';

This is required because some of antd components use localized messages from moment.

  • Add means of switching to the new locale. E.g. if you are using the default LanguageSwitcher - add an additional locale option into it.

Hot Deploy and Dev Server

In order to run development server, use the following command:

$ npm run start

If the client was generated via CUBA Studio (as a module of CUBA application) you can use Gradle in order to run npm tooling:

$ ./gradlew npm_run_start

There is a known bug in Gradle node plugin which does not kill JS development server on task interruption.

Hot deploy from Studio to the Tomcat is not supported.

Build Scripts

$ npm run build command builds your app for production use. See build folder.

See available scripts in Create React App documentation.

Configuration

By default client deployed to Tomcat is built with production preset and aimed to be served under app-front context. Use PUBLIC_URL env variable to change this behavior (see .env.production.local).

The client served from development server has absolute URL of REST API specified in REACT_APP_CUBA_URL (see .env.development.local).

See the list of all environment variables available for configuration.

See src/config.ts for full list of common application settings used in runtime.

Backend Model

src/cuba directory contains TypeScript representation of project's entities, views and facades to access REST services. Here is the layout of the directory:

  • entities - project entities and views;
  • entities/base - framework and addons entities;
  • enums - project enums;
  • services.ts - middleware services exposed to REST;
  • queries.ts - REST queries.

Consider the Role entity class of CUBA Framework generated in typescript:

src/cuba/entities/base/sec$Role.ts

export class Role extends StandardEntity {
    static NAME = "sec$Role";
    name?: string | null;
    locName?: string | null;
    description?: string | null;
    type?: any | null;
    defaultRole?: boolean | null;
    permissions?: Permission[] | null;
}
  • You can easily access entity name by static NAME property: Role.NAME,
  • The class contains all properties of the domain model entity including ones from class hierarchy. Reference fields have corresponding types as well so that you can work with them in a type-safe manner:
function changeRole(role: Role) {
  role.defaultRole = true;   // ok
  role.defaultRole = 'foo';  // compilation fails  
}

Synchronizing Project Model

In order to regenerate project model to conform changes in the backend you can use the following command:

$ npm run `update-model`

Theming

Ant Design provides abilities to customize theme using less and overriding built-in variables. See the detailed documentation on Ant Design website.

Security

Since React client works via Generic REST API endpoints, the backend (CUBA) application should have properly configured Security Roles and Access groups. See the corresponding chapter in REST API documentation.

You can’t perform that action at this time.