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 & Atom Class

When we create a business module, we will automatically create an atom class. For example, the atom class of party in the module test-party. Since the module test-party already exists, the command to create the module is listed here, and it does not need to be executed again

  1. 1$ cd /path/to/project
  2. 2$ npm run cli :create:module test-party -- [--template=module-business] [--suite=test-party]
Name Description
moduleName Module Name, such as test-party
template Template Name, such as module-business
suite Suite Name, such as test-party. The test-party suite contains many modules, and the test-party module is just one of them

Declaration of Atom Class

Atom Class is the metadata information corresponding to Atom, which is also set in the meta.js file of the module

src/suite-vendor/test-party/modules/test-party/backend/src/meta.js

  1. 1const meta = {
  2. 2 base: {
  3. 3 atoms: {
  4. 4 party: {
  5. 5 info: {
  6. 6 bean: 'party',
  7. 7 title: 'Party',
  8. 8 tableName: 'testParty',
  9. 9 tableNameModes: {
  10. 10 default: 'testPartyView',
  11. 11 },
  12. 12 language: false,
  13. 13 category: true,
  14. 14 tag: true,
  15. 15 },
  16. 16 actions: {
  17. 17 },
  18. 18 validator: 'party',
  19. 19 search: {
  20. 20 validator: 'partySearch',
  21. 21 },
  22. 22 },
  23. 23 },
  24. 24 },
  25. 25};
Name Description
info.bean The Bean Component of Atom Class
info.title Atom Class’s title
info.tableName The data table of Atom Class
info.tableNameModes.default Can specify data view as needed
info.language Whether to enable localization feature
info.category Whether to enable category feature
info.tag Whether to enable tag feature
validator Used to render form and validate form data
search.validator Used to render custom search form fields

Data Table

Data schema change management related to the module is in the bean component version.manager

src/suite-vendor/test-party/modules/test-party/backend/src/bean/version.manager.js

  1. 1async update(options) {
  2. 2 if (options.version === 1) {
  3. 3 // create table: testParty
  4. 4 let sql = `
  5. 5 CREATE TABLE testParty (
  6. 6 id int(11) NOT NULL AUTO_INCREMENT,
  7. 7 createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  8. 8 updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  9. 9 deleted int(11) DEFAULT '0',
  10. 10 iid int(11) DEFAULT '0',
  11. 11 atomId int(11) DEFAULT '0',
  12. 12 personCount int(11) DEFAULT '0',
  13. 13 partyTypeId int(11) DEFAULT '0',
  14. 14 PRIMARY KEY (id)
  15. 15 )
  16. 16 `;
  17. 17 await this.ctx.model.query(sql);
  18. 18 }
  19. 19}

src/suite-vendor/test-party/modules/test-party/backend/src/beans.js

  1. 1const versionManager = require('./bean/version.manager.js');
  2. 2
  3. 3module.exports = app => {
  4. 4 const beans = {
  5. 5 // version
  6. 6 'version.manager': {
  7. 7 mode: 'app',
  8. 8 bean: versionManager,
  9. 9 },
  10. 10 };
  11. 11 return beans;
  12. 12};

Model

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

src/suite-vendor/test-party/modules/test-party/backend/src/model/party.js

  1. 1module.exports = app => {
  2. 2 class Party extends app.meta.Model {
  3. 3 constructor(ctx) {
  4. 4 super(ctx, { table: 'testParty', options: { disableDeleted: false } });
  5. 5 }
  6. 6 }
  7. 7 return Party;
  8. 8};
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/suite-vendor/test-party/modules/test-party/backend/src/config/validation/schemas.js

  1. 1module.exports = app => {
  2. 2 const schemas = {};
  3. 3 // party
  4. 4 schemas.party = {
  5. 5 type: 'object',
  6. 6 properties: {
  7. 7 atomName: {
  8. 8 type: 'string',
  9. 9 ebType: 'text',
  10. 10 ebTitle: 'Party Name',
  11. 11 notEmpty: true,
  12. 12 },
  13. 13 personCount: {
  14. 14 type: 'number',
  15. 15 ebType: 'text',
  16. 16 ebTitle: 'Person Count',
  17. 17 minimum: 1,
  18. 18 notEmpty: true,
  19. 19 },
  20. 20 partyTypeId: {
  21. 21 type: 'number',
  22. 22 ebType: 'select',
  23. 23 ebTitle: 'Party Type',
  24. 24 ebOptionsUrl: '/test/party/party/types',
  25. 25 ebOptionTitleKey: 'name',
  26. 26 ebOptionValueKey: 'id',
  27. 27 ebOptionsBlankAuto: true,
  28. 28 notEmpty: true,
  29. 29 },
  30. 30 atomCategoryId: {
  31. 31 type: 'number',
  32. 32 ebType: 'category',
  33. 33 ebTitle: 'Category',
  34. 34 },
  35. 35 atomTags: {
  36. 36 type: [ 'string', 'null' ],
  37. 37 ebType: 'tags',
  38. 38 ebTitle: 'Tags',
  39. 39 },
  40. 40 },
  41. 41 };
  42. 42 // party search
  43. 43 schemas.partySearch = {
  44. 44 type: 'object',
  45. 45 properties: {
  46. 46 partyTypeId: {
  47. 47 type: 'number',
  48. 48 ebType: 'select',
  49. 49 ebTitle: 'Party Type',
  50. 50 ebOptionsUrl: '/test/party/party/types',
  51. 51 ebOptionTitleKey: 'name',
  52. 52 ebOptionValueKey: 'id',
  53. 53 ebOptionsBlankAuto: true,
  54. 54 },
  55. 55 },
  56. 56 };
  57. 57 return schemas;
  58. 58};

Atom Bean & Atom Action

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.

Just provide a bean component for the atom class to encapsulate the business logic of all atom actions

src/suite-vendor/test-party/modules/test-party/backend/src/bean/atom.party.js

  1. 1module.exports = app => {
  2. 2
  3. 3 const gPartyTypeEmojis = {
  4. 4 Birthday: '🎂',
  5. 5 Dance: '💃',
  6. 6 Garden: '🏡',
  7. 7 };
  8. 8
  9. 9 class Atom extends app.meta.AtomBase {
  10. 10
  11. 11 async create({ atomClass, item, user }) {
  12. 12 // super
  13. 13 const key = await super.create({ atomClass, item, user });
  14. 14 // add party
  15. 15 const res = await this.ctx.model.party.insert({
  16. 16 atomId: key.atomId,
  17. 17 });
  18. 18 return { atomId: key.atomId, itemId: res.insertId };
  19. 19 }
  20. 20
  21. 21 async read({ atomClass, options, key, user }) {
  22. 22 // super
  23. 23 const item = await super.read({ atomClass, options, key, user });
  24. 24 if (!item) return null;
  25. 25 // read
  26. 26 await this._getMeta(item, options);
  27. 27 // ok
  28. 28 return item;
  29. 29 }
  30. 30
  31. 31 async select({ atomClass, options, items, user }) {
  32. 32 // super
  33. 33 await super.select({ atomClass, options, items, user });
  34. 34 // select
  35. 35 for (const item of items) {
  36. 36 await this._getMeta(item, options);
  37. 37 }
  38. 38 }
  39. 39
  40. 40 async write({ atomClass, target, key, item, options, user }) {
  41. 41 // super
  42. 42 await super.write({ atomClass, target, key, item, options, user });
  43. 43 // update party
  44. 44 const data = await this.ctx.model.party.prepareData(item);
  45. 45 data.id = key.itemId;
  46. 46 await this.ctx.model.party.update(data);
  47. 47 }
  48. 48
  49. 49 async delete({ atomClass, key, user }) {
  50. 50 // delete party
  51. 51 await this.ctx.model.party.delete({
  52. 52 id: key.itemId,
  53. 53 });
  54. 54 // super
  55. 55 await super.delete({ atomClass, key, user });
  56. 56 }
  57. 57
  58. 58 async _getMeta(item, options) {
  59. 59 // layout: list/table/mobile/pc
  60. 60 const layout = options && options.layout;
  61. 61 // meta
  62. 62 const meta = this._ensureItemMeta(item);
  63. 63 // meta.flags
  64. 64 if (item.partyOver) {
  65. 65 meta.flags.push(this.ctx.text('PartyOverFlag'));
  66. 66 }
  67. 67 if (layout !== 'table' && item.personCount) {
  68. 68 meta.flags.push(item.personCount + 'P');
  69. 69 }
  70. 70 // meta.summary
  71. 71 if (item.partyTypeCode) {
  72. 72 const dictItem = await this.ctx.bean.dict.findItem({
  73. 73 dictKey: 'test-party:dictPartyType',
  74. 74 code: item.partyTypeCode,
  75. 75 });
  76. 76 meta.summary = `${dictItem.options.emoji}${dictItem.titleLocaleFull}`;
  77. 77 }
  78. 78 }
  79. 79
  80. 80 }
  81. 81
  82. 82 return Atom;
  83. 83};
Name Description
create create a new record of party
read select one record of party
select select some records of party, supporting paged
write update one record of party
delete delete one record of party

src/suite-vendor/test-party/modules/test-party/backend/src/beans.js

  1. 1const atomParty = require('./bean/atom.party.js');
  2. 2
  3. 3module.exports = app => {
  4. 4 const beans = {
  5. 5 // atom
  6. 6 'atom.party': {
  7. 7 mode: 'app',
  8. 8 bean: atomParty,
  9. 9 },
  10. 10 };
  11. 11 return beans;
  12. 12};