Client application actions

To be accurate, a client action is a function that returns an object containing the delta of the application global state changing. When a chain of actions is called (for example, 'validate, save, exit'), all actions in it are called one by one. Changes of state, returned by each action, are combined and, once the calling is complete, are applied to the global state of the application, then the DWKit client-side application reacts to these changes. A list of system actions and their architecture can be found here.

User-defined actions are created in the Form/Action Handlers section of the admin panel. Each form is attributed a file with the actions code. Besides, you can also connect a global actions handler to the application by creating a global variable.

window.globalUserActions =
{
    actionName : function (ars)
    {
        ....
    },
    ...
}

When a form component calls an event that is assigned a chain of actions, DWKit searches for them by their names. Search order is as follows (by priority):

  • Actions defined for the form.
  • Global user-defined actions (window.globalUserActions).
  • System actions.
  • Server user defined actions. Thus, you can, for example, redefine or complement the system's 'validate' or any other action.

Input parameters

Each action receives the same set of input parameters as a single object. That's why you can declare actions as function(args) or function({...}) and include the parameters you need in the object. Here is the list of all parameters:

  • args.key - component Name (id), which the action has been called from.
  • args.data - data displayed in the form, which the action has been called from.
  • args.originalData - original, not edited form data.
  • args.state - global state of the client application.
  • args.component - reference to the <DWKitForm/> React component, which the action has been called from.
  • args.formName - name of the form, which the action has been called from.
  • args.controlRef- reference to the component, which the action has been called from.
  • args.sourceControlRef - reference to the component, which event called action. This value coincides with controlRef, if target has not been specified for event on the Events Tab in Form Builder.
  • args.sourceControlValue - value of the component, which event called action.
  • args.parameters - additional parameters that can be created and edited on the Events Tab in Form Builder.
  • args.modalId - modal window id. Is filled in, if the event which called action, was initiated by the form opened in the modal window.

Returned values

DWKit makes it possible to return from action depending on your needs either an empty object or a delta of the change of the global state, or a promise for async operations, or to change its data object or terminate the chain of actions with an error.

Empty value

You can implement a certain code in an action without changing application state. The best solution in this case would be to return an empty object.

actionName : function (args)
{
    return {};
},

Data changing

Sometimes, you just need to change the data displayed in the form with as least effort as possible. In actions you can change args.data object value directly. These changes will be accumulated and form data changing. In the example below, a control with the Property3 name is assigned a value of the sum of controls with names Property1 and Property2.

actionName : function (args)
{
    var data = args.data;
    data.Property3 = data.Property1 + data.Property2;
    return {};
},

Returning delta of the global state

You can return an object that contains only changes of the global state of the application. In the object, you should mention only the fields to be changed. Below is an example of the code that hides or creates the readonly controls in the form.

actionName : function (args)
{
    var hideControls = [];
    hideControls.push("Property1");
    var readOnlyControls = [];
    readOnlyControls.push("Property2");
    return {
        app: {
            form: {
                models: {
                    hideControls: hideControls,
                    readOnlyControls: readOnlyControls
                }
            }
        }
    };
}

The control with the Property1 name will be hidden, while the control with the Property2 name will not be available for editing. Using the mechanism of partial changing of the global state, you can get total control over the entire application. Also note that if several actions are called in a chain, all global state changes returned by these actions will be joined and simultaneously applied to global state after the chain is complete. It it very important to understand that using this mechanism you can add your own sections into the global state.

Returning Promise to execute asynchronous operations

It is better off making asynchronous requests via Promises. You can use fetch to make requests. You've got two methods how to return Promise from action:

  • return f(dispatch){ return Promise } - i.e. function returning promise. In the example below fetch - returns Promise that will be included in the execution chain. If necessary, you can find more details on what Promises are here. dispatch is a function, via which Redux-actions are called (they also change the state). It can be used optionally.

    actionName : function (args)
    {
      return (dispatch) =>
      {
          return fetch('/workflow/execute?' + encodeQueryData(urlData),
          {
            credentials: 'same-origin',
            method: 'post'
          })
          .then(response => response.json())
          .then(response => {
              if (response.success) {
                  let msg = "The command execution has been completed!";
                  dispatch(appActions.app.resetfetchcount());
                  alertify.success(msg);
              }
              else
                  dispatch(appActions.app.form.data.save.failure(response.message, response.details));
          })
          .catch(error => {
              dispatch(appActions.app.form.data.save.failure(error.message, error.stack));
          });
      }
    }
  • return promise directly. For example, you can change global state, if asynchronous request to server was completed successfully.

    actionName : function (args)
    {
      return fetch('/someurl').then(() => {
          return {
              stateDelta: {
                  app: { `some state delta` }
              }
          };
      });
    }

Interruption of the execution of a chain of actions

Use throw in order to interrupt execution. For example, this is how the validate action works. If a form is not valid, throw is called and execution of the chain is interrupted (save and exit do not happen), while the errors are displayed in the form.

actionName : function (args)
{
    var formErrors = {
        plainFormField1 : "Error message", //it is an error with a message
        plainFormField2 : true, //it is an error without a message
        collectionFormField : [ //an error in some row in a collection
            {
                _id : "bb622fb9-d057-44ab-a1d1-a6ae242e286f", //row id
                collectionColumn1 : "Error message", //it is an error in a collection row with a message
                collectionColumn2 : true //it is an error in a collection row without a message
            }
        ]
    };
    throw {
        level: 1,
        message: "Error occurs!",
        formerrors: {
            modalId: args.modalId, //we must specify modal id just in case, suddenly this function will be executed from a modal window
            errors: formErrors
        }
    };
}

Binding chains of client actions to control events

Binding is taking place in the admin in the form builder on the Events tab. Here, you should specify a chain of actions an event executes, the additional parameters, and, if necessary, control the event comes from (for example, a button that opens an editing form for a grid, will send an event from the grid).