Atom is the most basic element of CabloyJS, such as article, announcement, leave application, etc.

Through the combination of Atom, we can achieve any desired requirements, such as CMS, OA, CRM, ERP and so on.

It is precisely because a general concept of Atom has been abstracted from various business models, that CabloyJS realizes many common features and functions for Atom, thus enabling various practical business conveniently

For more detailed concepts of Atom, see: Cabloy: Basic Concepts of Atom

Here, the basic concepts and usage are introduced mainly through the atomClass party

Business Module Template: module-business

If business logics to be developed, it is recommended that the business module template module-business be used to create the file skeleton of the business module. This template automatically creates business-related code, greatly simplifying the workload

$ cd /path/to/project
$ npm init cabloy src/module/test-party --type=module-business

Declaration of AtomClass

AtomClass is the metadata information corresponding to Atom, which is also set in meta of the module

src/module/test-party/backend/src/meta.js

const meta = {
  base: {
    atoms: {
      party: {
        info: {
          title: 'Party',
          tableName: 'testPartyView',
          flow: 0,
        },
        actions: {
        },
        flags: {
        },
        validator: 'party',
        search: {
          validator: 'partySearch',
        },
      },
    },
  },
};
Name Description
info.title AtomClass’s title
info.tableName The name of the data table or view
info.flow Whether to enable simple flow
validator used to render form and validate form data
search.validator used to render custom search form fields

Data Table

All code related to module data architecture change is in the backend API route version/update, which facilitates the maintenance of module data version, thus achieving seamless upgrade of module

src/module/test-party/backend/src/service/version.js

async update(options) {
  if (options.version === 1) {
    // create table: testParty
    let sql = `
      CREATE TABLE testParty (
        id int(11) NOT NULL AUTO_INCREMENT,
        createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        deleted int(11) DEFAULT '0',
        iid int(11) DEFAULT '0',
        atomId int(11) DEFAULT '0',
        personCount int(11) DEFAULT '0',
        partyTypeId int(11) DEFAULT '0',
        PRIMARY KEY (id)
      )
    `;
    await this.ctx.model.query(sql);
  }
}

Model

Defining model objects corresponding to data tables makes it easier to manipulate data

src/module/test-party/backend/src/model/party.js

module.exports = app => {
  class Party extends app.meta.Model {
    constructor(ctx) {
      super(ctx, { table: 'testParty', options: { disableDeleted: false } });
    }
  }
  return Party;
};
Name Default Description
table The name of the data table or view corresponding to the model object
disableDeleted false Disable Soft Deletion feature

Form Validation

CabloyJS’s Form Validation mechanism uses ajv. Suggest you have a preliminary understanding of ajv

With the Form Validation, we can automatically render the form by defining the business-related JSON Schema, and validate the form data automatically. If the form data does not meet expectations, the error messages will be displayed automatically

For more information about Form Validation, see: Cabloy: Form Validation

src/module/test-party/backend/src/config/validation/schemas.js

module.exports = app => {
  const schemas = {};
  // party
  schemas.party = {
    type: 'object',
    properties: {
      atomName: {
        type: 'string',
        ebType: 'text',
        ebTitle: 'Party Name',
        notEmpty: true,
      },
      personCount: {
        type: 'number',
        ebType: 'text',
        ebTitle: 'Person Count',
        notEmpty: true,
      },
      partyTypeId: {
        type: 'number',
        ebType: 'select',
        ebTitle: 'Party Type',
        ebOptionsUrl: '/test/party/party/types',
        ebOptionTitleKey: 'name',
        ebOptionValueKey: 'id',
        ebOptionsBlankAuto: true,
        notEmpty: true,
      },
    },
  };
  // party search
  schemas.partySearch = {
    type: 'object',
    properties: {
      partyTypeId: {
        type: 'number',
        ebType: 'select',
        ebTitle: 'Party Type',
        ebOptionsUrl: '/test/party/party/types',
        ebOptionTitleKey: 'name',
        ebOptionValueKey: 'id',
        ebOptionsBlankAuto: true,
      },
    },
  };
  return schemas;
};

Atom Action & API Route

CabloyJS refers to all operations of business data as Atom Action, which fall into two main categories:

  1. Basic Action: create、read、write、delete
  2. Custom Action:business-specific operations, such as review, publish, etc.

CabloyJS provides a set of basic API routes, encapsulates atom actions, so that middlewares such as database transaction, validator',right` can be configured uniformly

Business module only need to provide business API routes, which will be called by basic API routes when appropriate

Business API Routes

src/module/test-party/backend/src/routes.js

  // party
  // atom: party
  { method: 'post', path: 'party/create', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/read', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/select', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/write', controller: party, middlewares: 'inner,validate',
        meta: {
          auth: { enable: false },
          validate: { validator: 'party', data: 'item' },
        },
      },
  { method: 'post', path: 'party/delete', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/action', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
  { method: 'post', path: 'party/enable', controller: party, middlewares: 'inner', meta: { auth: { enable: false } } },
Name Description
middlewares: ‘inner’ middleware inner, indicates this API route only be allowed inner access
middlewares: ‘auth’ Since the basic API routes will pass in the object user when calling the business API routes, the global middleware auth is disabled here to reduce unnecessary operations
party/create create an item of party
party/read select single item of party
party/select select some items of party
party/write update an item of party
party/delete delete an item of party
party/action custom actions of party
party/enable change status of party from draft to normal

Business Logic Codes

src/module/test-party/backend/src/service/party.js

module.exports = app => {

  class Party extends app.Service {

    async create({ atomClass, key, item, user }) {
      // add party
      const res = await this.ctx.model.party.insert({
        atomId: key.atomId,
      });
      // return key
      return { atomId: key.atomId, itemId: res.insertId };
    }

    async read({ atomClass, key, item, user }) {
      // read
    }

    async select({ atomClass, options, items, user }) {
      // select
    }

    async write({ atomClass, key, item, validation, user }) {
      // update party
      await this.ctx.model.party.update({
        id: key.itemId,
        personCount: item.personCount,
        partyTypeId: item.partyTypeId,
      });
    }

    async delete({ atomClass, key, user }) {
      // delete party
      await this.ctx.model.party.delete({
        id: key.itemId,
      });
    }

    async action({ action, atomClass, key, user }) {
    }

    async enable({ atomClass, key, atom, user }) {
    }

  }

  return Party;
};