Client architecture

Client

Client side of the application consists of two React-based SPAs and login page for unauthorized users - login.jsx.

  • Admin - admin panel - admin.jsx.
  • Frontend - application that builds forms - app.jsx. All three jsx files are located in the /wwwroot/js/app/ folder of the ASP.NET Core MVC DWKit project. In all our examples and starter packs this project is called OptimaJet.DWKit.StarterApplication.

Admin

React-based SPA. The main page is admin.jsx. Check out this file example on GitHub. This page contains a single React component, <DWKitAdmin/>, and it is this component that represents Admin application. Below is an example of its default settings:

<DWKitAdmin
    apiUrl="/configapi"
    workflowApi="/workflow/designerapi"
    imageFolder="/images/"
    localizationFolder="/localization/"
    themesFolder="/themes/"
    deltaWidth={0}
    deltaHeight={0}
    controlActions={globalActions}
    returnToAppUrl="/"
/>
  • apiUrl - URL of the API method of the ConfigAPIController controller. This method is used to edit all metadata in Data Model, Forms, Server and client user-defined functions and other system entities in DWKit, except for the workflow schemes.
  • workflowApi - URL of the DesignerAPI method of the WorkflowController controller. Find more details on how Workflow Designer connects to the server in Workflow Engine documentation.
  • imageFolder - server URL of a folder with images.
  • localizationFolder - server URL of a folder with localization files.
  • themesFolder - server URL of a folder with colour themes.
  • deltaWidth and deltaHeight - specify these only if you're embedding admin panel into another app. They define values admin panel width and height will be reduced to.
  • controlActions - a list of system Client actions that are delivered with DWKit.
  • returnToAppUrl - url by which user is transferred to the main app from admin panel.

Below are some other important aspects of Admin application work that should be taken into account.

  • Admin application is fully isolated from the Frontend application on the client side. They interact only via the metadata stored on server.
  • Interaction between Admin application and server occurs only via the ConfigAPIController.API() and WorkflowController.DesignerAPI() methods.
  • Usually, there is no need to make changes to the admin panel.

Frontend

React-based SPA. The main page is app.jsx. Check out this file example on GitHub. Normally, you won't have to make changes to this page. However, let's take a look at what this page contains. We are interested only in the layout that is returned by the component. It may seem too big and complicated, but what really matters to us are the settings of <DWKitForm/>, <StateBindedForm/>, <FormContent/> and <FlowContent/> components.

...
return <div className="dwkit-wrapper" key={this.state.pagekey}>
    <Provider store={Store}>
        <StateBindedForm {...sectorprops} formName="header" stateDataPath="app.extra" data={{ currentUser: user.name, currentEmployee: currentEmployee }} modelurl="/ui/form/header" />
    </Provider>
    <div className="dwkit-container">
        <Provider store={Store}>
            <StateBindedForm className="dwkit-sidebar-container" {...sectorprops} formName="sidebar" stateDataPath="app.extra" data={{currentEmployee: currentEmployee}} modelurl="/ui/form/sidebar" />
        </Provider>
        <div className="dwkit-content">
            <Provider store={Store}>
                <BrowserRouter>
                    <div className="dwkit-content-form">
                        <ApplicationRouter onReload={this.onRefresh.bind(this, true)} onRefresh={this.onRefresh.bind(this, false)} />
                        <NotificationComponent
                            onFetchStarted={this.onFetchStarted.bind(this)}
                            onFetchFinished={this.onFetchFinished.bind(this)}/>
                        <Switch>
                            <Route path='/form' component={FormContent}  />
                            <Route path='/flow' component={FlowContent}  />
                            <Route exact path='/'>
                                <FormContent formName={this.props.defaultForm ? this.props.defaultForm : "dashboard"} />
                            </Route>
                            <Route path='/account/logoff' render={() => {
                                Store.logoff();
                                return null;
                            }} />
                            <Route nomatch render={() => {
                                //Hack for back button
                                let url = window.location.href;
                                window.location.href = url;
                                return null;
                            }} />
                        </Switch>
                    </div>
                </BrowserRouter>
            </Provider>
        </div>
    </div>
    <DWKitForm {...sectorprops} className="dwkit-footer" formName="footer" modelurl="/ui/form/footer" />
</div>;
...

Let's look at these components more closely.

  • <DWKitForm/> - this components just draws forms created in Form Builder. Such a form is not bound to the global state of the client application. In the code below this component draws a simplest form. However all other components, used here to draw forms, use this component as basis. That is why it is one of the most important React components used by DWKit. Here's an example of how we use this component. Let's draw a 'footer' form in it.

    <DWKitForm {...sectorprops} className="dwkit-footer" formName="footer" modelurl="/ui/form/footer" />
  • <StateBindedForm/> - wrapper component for <DWKitForm/>, bound to the global state of the client application. Binding type is defined by this component properties values. Read more about this here. Here's our example:

    <StateBindedForm {...sectorprops} formName="header" stateDataPath="app.extra" data={{ currentUser: user.name, currentEmployee: currentEmployee }} modelurl="/ui/form/header" />

    Component with such settings will draw a 'header' form and transmits a combination of the following two objects into it as data:

    • data - data object.
    • part of the global state of the client application path to which is defined by the stateDataPath="app.extra" property.
  • <FormContent/> - This is a main component which displays DWKit main form. It also controls modal windows. This is a <DWKitForm/> wrapper, but the component is bound to the global state of the client application. The component fully integrates all events transmitted from <DWKitForm/> into DWKit app. In other words, it is an automated component, and that is why it does not have any settings. This component will display the form which name was transferred as url. If the page url is /form/{formName}/, a form with the name 'formName' will be loaded.
  • <FlowContent/> - This is a main component which displays DWKit main form, but displayed form name is defined by Business flow mechanism. Business flow name is set in a url. If the page url is /flow/{businessFlowName}/, business flow with 'businessFlowName' name defines loaded form.

Only one <FormContent/> or <FlowContent/> component can be simultaneously rendered on one page in DWKit. But it does not mean that DWKit can work with only one form at a time, as it has modal windows mechanism. Let's see how <FormContent/> or <FlowContent/> components are integrated into the app.

<Switch>
    <Route path='/form' component={FormContent}  />
    <Route path='/flow' component={FlowContent}  />
    <Route exact path='/'>
        <FormContent formName={this.props.defaultForm ? this.props.defaultForm : "dashboard"} />
    </Route>
   ...
</Switch>

Even if you are not familiar with React, you'll still understand example above. If there's a form in the url, we draw a form, if there's a flow in url, we use Business flow mechanism. If url is empty, we upload a default form (in our case it will be the form called 'dashboard' or its name will be obtained from application settings).

There are following forms in the code above: 'header', 'sidebar' and 'footer'. All these three forms can be edited via admin panel (see Manage forms section). Thus you can easily modify functions and appearance of your app, without changing app.jsx and repacking your project.

Working with global state via client actions

It is also crucial how the form interacts with the application global state. Physically, in DWKit, it is determined by React + Redux combination. Good if you are familiar with these technologies, but if not, it doesn't mean you won't be able to use DWKit at full capacity, as DWKit users don't have to deal with them directly.

Each application has a global state that contains the following information (you can find more details on the structure of global state here):

  • Description of the form or its Model is what determines the composition and location of components in the main and child modal forms.
  • Data of the form - data to be displayed. There are always two copies of the data, Original and Modified, i.e. those that were loaded from server and those that are now displayed in the form.
  • Information about the current user and the impersonated user
  • List of errors in the form
  • Security permissions cache
  • URL of the current page
  • etc., for more information see this section.

A component in the form can send the event that is processed by the chain of actions. Let's look at the example of typical chains which are handling onClick event for different buttons:

  • Save and Exit button will validate,save,exit
  • Save button will validate,save,refresh
  • Cancel button will exit

Each action is a function to which the following parameters must be transmitted:

  • state - global state of the application.
  • Currently displayed data.
  • Other additional parameters, for more details see this section.

In this function you can:

  • Change data object.
  • Return object with modifications for global state. Object must contain only those properties that we want to change in global state. We often call such an object delta for global state.
  • Return Promise to perform asynchronous operations.

After executing the chain of actions, all modifications of global state and/or data are applied to global state of the application, which is then refreshed and used to render form components.

DWKit pack includes most essential actions. See full list here. It is also possible to write your own client user-defined actions in the Action Handlers section of the admin panel.

Main controllers.

Frontend application interacts with server via the following controllers:

  • UserInterfaceController - getting a form via the GetForm method.
  • DataController - getting data via the GetData method, changing data via the ChangeData method, deleting data via the DeleteData method.
  • WorkflowController - getting a list of commands and states via the GetData method, execution of commands via the ExecuteCommand method, setting of states via the SetState method.
  • AccountController - is responsible for signing in and signing out.
  • CodeActionController - launches server actions from client.