Skip to main content

Exercise: Build and integrate a new module

In this exercise, you'll create a standalone module and integrate it into an existing modular project.

As a prerequisite, you should have created a modularized project in GitLab.

note

While this guide enables quick, hands-on customizing in modular projects, you should still understand the theory of modularization (module boundaries, extension points, services, dependency management, and the transpile/merge model). A solid conceptual grasp is essential to make correct design decisions, avoid brittle overrides, manage module lifecycles, and keep your project maintainable.

After completing this exercise, you can:

  • Create a standalone module and link it to a modular project.
  • Configure peerDependencies and aliases, enable Liquibase, and manage DB schema via changelogs.
  • Generate an Entity from an alias, configure fields, parameters, providers, and processes.
  • Build a Context with multiple Views and integrate it into the project's global menu.
  • Maintain translations that merge into the project.
  • Implement and consume Services for modular behavior.
  • Define Extension Points to allow further modular extensions by other modules or the project.

Create a module

Create a new module using this guide.
Alternatively, fork the module-template and place it in your GitLab space as memo-module.

Add the module in the Designer via: File -> New Project -> Clone from Git repository.

Update the package.json to set the module name and type:

{ 
"name": "@myusername/memo-module",
"version": "0.0.1",
"adito": {
"type": "module"
}
}

Unlike a project (type: "project"), a module has type: "module". Modules use peerDependencies instead of dependencies. These enable access to referenced modules in the Designer (after npm install) but are not bundled or resolved during transpile. Modules themselves do not need to be transpiled to be usable by a project.

note

Compare memo-project and memo-module in the Designer's Project view; some folders are intentionally absent in modules.

Add the module to a project

In your modularized project, add a local dependency in its package.json:

package.json
{
"dependencies": {
"@myusername/memo-module": "../memo-module"
}
}

Run npm install in the project to link the module.

Create a new context

We'll build a simple context with an Entity, Views, a database table, and required configuration inside the module, then test it in the project.

Database table, Data_alias, and Liquibase

Create a new database alias in memo-module with the name Memo_Data_alias.

In its properties, the targetAliasName must refer to the project's main alias (usually Data_alias). To make it selectable in the module, add the sharedalias module as a peer dependency:

adding peerDependeny to package.json
{
"peerDependencies": {
"@aditosoftware/sharedalias": "^3.0.0"
}
}

Run npm install.

note

Ensure the sharedalias version matches the one used in memo-project.

You should now see aliases from sharedalias under the Aliases node in the Designer. Select the shared Data_alias as the target.

target-alias.png
Figure: Target alias for the module alias

tip

You can toggle visibility of referenced entries (from dependencies and peerDependencies) in the project tree using the toolbar toggle described here: Show dependencies in project tree.

Enable Liquibase for Memo_Data_alias:

  • In the module's file system, create .liquibase/Memo_Data_alias/changelog.xml as a placeholder file.
  • In the Designer, the Liquibase context menu should now appear for the alias.

liquibase-context-menu.png
Figure: Liquibase context menu for the alias

Create Liquibase files:

changelog.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<include relativeToChangelogFile="true" file="memo/changelog.xml"/>
</databaseChangeLog>

Create memo/create_Memo.xml:

memo/create_Memo.xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="j.doe" id="16ce338e-e132-418c-9d9c-5c04c0532b9e">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="memo" />
</not>
</preConditions>
<createTable tableName="memo">
<column name="MEMOID" type="CHAR(36)">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="TITLE" type="VARCHAR(50)" />
<column name="CONTENT" type="LONGTEXT" />
<column defaultValueComputed="NULL" name="IS_OLD" type="TINYINT" />
<column defaultValueComputed="NULL" name="DATE_EDIT" type="datetime" />
<column defaultValueComputed="NULL" name="DATE_NEW" type="datetime" />
<column name="USER_EDIT" type="VARCHAR(63)" />
<column name="USER_NEW" type="VARCHAR(63)" />
</createTable>
</changeSet>
</databaseChangeLog>

Create memo/changelog.xml:

memo/changelog.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<include relativeToChangelogFile="true" file="create_Memo.xml" />
</databaseChangeLog>

Your structure should now look like this:

liquibase-memo-structure.png
Figure: Liquibase structure for the module

Run Liquibase Update. Keep memo-project open while doing this. In the dialog, choose the correct data alias (for SSP systems typically cloud_data_alias). If it doesn't appear, briefly open the system -> default editor in memo-project.

liquibase-update.png
Figure: Liquibase Update dialog

You can proceed immediately; if there are no contexts, you don't need to wait for the spinner.

After a successful update:

  • Add a new table node (MEMO) under the Memo_Data_alias in the Navigator.
  • Run Diff against the project to import the structure into the module.

diff-alias-context-menu.png
Figure: Diff context menu for the module

Although being in the memo-module, you need to select the memo-project an its system in the diff dialog. Otherwise, you cannot diff against the database and will not get any results.

diff-alias-dialog.png
Figure: Diff dialog for the module

Now you can see your database structure in the module.

diff-alias-result.png
Figure: Diff result for the module

Create the Entity

Create Memo_entity in memo-module.

For the easiest way to create an Entity, we will use the "New with blueprint → Create Entity from AliasDefinition" action. Ensure there's an entity folder in the module first, otherwise the context menu will not be available.

entity_blueprint.png
Figure: Entity blueprint dialog for the Memo_entity

Adjust your created entity:

  • Add iconProcess:
    iconProcess
    import { result, vars } from "@aditosoftware/jdito-types";

    result.string("TEXT:" + vars.get("$field.TITLE"));
  • Configure your Fields:
    • CONTENT (Title=Memo_Content; contentType=HTML)
    • DATE_EDIT (Title=Memo_Edited_on; contentType=DATE; resolution=SECOND)
    • DATE_NEW (Title=Memo_Created_on; contentType=DATE; resolution=SECOND)
    • IS_OLD (Title=Memo_old, contentType=BOOLEAN)
    • MEMOID (Title=Memo_ID)
    • TITLE (Title=Memo_Title)
    • USER_EDIT (Title=Memo_Editor)
    • USER_NEW (Title=Memo_Creator)
    • Ensure your DATE_NEW/DATE_EDIT and USER_NEW/USER_EDIT fields use the standard valueProcess.
  • Add parameter OnlyCurrent_param with expose = true. Add a valueProcess to it:
  • Add provider Memos (do not set OnlyCurrent_param).
  • Add provider CurrentMemos with OnlyCurrent_param set and this valueProcess:
    import { result } from "@aditosoftware/jdito-types";

    result.string("true");

At the end, your Entity should look like this:

entity_with_fields_and_db_rc.png
Figure: Entity with necessary fields for the Memo_entity

Create the Context

Create context Memo with title Memo_Memo and any icon you like. Assign Memo_entity to the context.

Create Views

Create and assign these views to the Memo context (layout is flexible; below is a suggested structure):

  • MemoEdit_view: GenericViewTemplate (small edit)
  • MemoFilter_view: TableViewTemplate with relevant columns
  • MemoPreview_view:
    • CardViewTemplate:
      • iconField: #ICON
      • titleField: TITLE
    • GenericViewTemplate (name: details)
      • fields: IS_OLD, USER_NEW, USER_EDIT
      • showDrawer: true
      • drawerCaption: Memo_Details
    • GenericViewTemplate (name: content)
      • fields: CONTENT
      • showDrawer: true
      • drawerCaption: Memo_Content
    • ScoreCardViewTemplate
      • fields: DATE_NEW, DATE_EDIT
  • MemoMain_view: any elements you like to have in the main view

You should now have a context Memo with the following structure:

context-with-views.png
Figure: Context with views

Add to the project's global menu

Switch to memo-project.

Update dependencies (npm update or npm clean-install). You should now see the Memo context in the project.

In application/_____SYSTEM_APPLICATION_NEON, create a new menu section and add the Memo context. Assign a role so it becomes visible in the Designer.

menu-designer.png
Figure: Memo entry in the application menu

Transpile and deploy. After re-login in the web client, the new menu group and Memo context should appear.

Now you can create some test data:

memo_client_no_translations.png
Figure: Memo filter view in the web client with some test data

Maintain translations

The keys used in titles and labels must be translated. Currently, they are not translated.

In memo-module, under language, create en and de language files. Use "Extract Key" to collect keys from the module (the System field stays empty because modules don't have DB connections).

In the extraction dialog, you can skip Modifications and external modules for this module-only setup. Once you add Modifications/Service Implementations/overridden processes containing keys, include them in extraction.

After extraction, synchronize keys and maintain values for English and German.

Run npm update. If the language files don't show in memo-project, use Refresh in the Designer. Deploy as usual. The module's language entries should merge into the project's _____LANGUAGE_en/de files (not as separate _____LANGUAGE_en_memo-module entries).

Flexibility with Services

Services allow modular behavior injection. Services can be global (project-wide) or entity-scoped. Create them under the "Service" node or via "New Service" within an entity. Implement behavior via "Add Implementation".

In Memo_entity, add service memoConditionProcess_service. Create an implementation onlyCurrent_impl with:

onlyCurrent_impl
import { Utils } from "@aditosoftware/utility/process/Utils_lib";

// @ts-ignore
return (pCond) => {
if (Utils.toBoolean(vars.get("$param.OnlyCurrent_param"))) {
pCond.and("MEMO.IS_OLD", 0);
}
};

If the import is marked invalid, add a matching @aditosoftware/utility version as a peerDependency in memo-module and run npm install.

Use the service in a process

Add parameter OnlyCurrent_param (exposed) with the following valueProcess:

import { result } from "@aditosoftware/jdito-types";

result.string("Creator");

Load the service in the db RecordContainer's conditionProcess:

conditionProcess
import { modules, result } from "@aditosoftware/jdito-types";
import { newWhere } from "@aditosoftware/utility/process/SqlBuilder_lib";

let cond = newWhere();
let impls = modules.loadService("memoConditionProcess_service");
impls.forEach((impl) => impl()(cond));
result.string(cond.toString());

After transpile, the Designer generates explicit calls to discovered implementations. You can inspect the generated file under dist/entity/Memo_entity/recordcontainers/db/conditionProcess.js to verify. The transpiled code should look like this:

generated conditionProcess
import { modules, result } from "@aditosoftware/jdito-types";
import { newWhere } from "@aditosoftware/utility/process/SqlBuilder_lib";
import { Utils } from "@aditosoftware/utility/process/Utils_lib";


let cond = newWhere();
let impls = [_____GENERATED_memoConditionProcess_service_onlyCurrent_impl];
impls.forEach((impl) => impl()(cond));
result.string(cond.toString());

function _____GENERATED_memoConditionProcess_service_onlyCurrent_impl()
{
// @ts-ignore
return (pCond) => {
if (Utils.toBoolean(vars.get("$param.OnlyCurrent_param"))) {
pCond.and("MEMO.IS_OLD", 0);
}
};
}

After you have deployed your project, you should only see the new memos in the web client:

memo_client_without_old.png
Figure: Memo filter view in the web client with some test data

Extensibility with Extension Points

Use Extension Points to make entities and views extensible from other modules or the project. They define where and how external additions can be made (fields, providers, parameters, view templates, etc.).

Example goal: Extend Memo with object linkage (e.g., link to a Company) via new fields OBJECT_TYPE and OBJECT_ROWID, connected to the existing logic through extension points.

Entity Extension Points

Add extension points in Memo_entity for:

  • ActionField
  • Consumer
  • Field
  • Parameter
  • Provider

This enables controlled extensions without modifying the original entity.

entity-extension-points.png
Figure: Entity extension points

View Extension Points

For each Memo view, add one extension point to allow insertion of additional ViewTemplates at specific positions:

  • MemoEditExtensionPoint
  • MemoFilterExtensionPoint
  • MemoMainExtensionPoint
  • MemoPreviewExtensionPoint

Conclusion

Your module is now ready to be extended by other modules and the project. As an exercise, enhance memo-project by adding fields or view templates to the Memo context using these extension points.