Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export const component: IComponent = {

Every `errorCode` returned by your check function must be listed in `reservedErrorCodes`. You cannot use the Mendix reserved prefixes `cw`, `ce`, or `ci`during registration or the check will fail and a generic error message will appear in the **Errors** pane.

It is also not possible for your extension to use error codes that a different extension has already reserved. The api will also fail in that case.

{{< figure src="/attachments/apidocs-mxsdk/apidocs/extensibility-api/web/consistencyChecks/generic_error.png" alt="" >}}

This error also appears if one of your checks throws an unexpected exception. To find out what the exception was, check the Mendix logs.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

Studio Pro allows you to extend its metamodel by adding custom document types. These documents can store arbitrary data that can be serialized as strings. When you register an editor (a user-defined UI component) for a specific document type, documents of that type appear in the UI alongside built-in document types such as constants, Java actions, and pages. They appear in the **New Document** and **Find Advanced** dialogs, context menus for adding documents, the App Explorer, and other UI elements that display Studio Pro documents. You can register custom editors to appear as tabs or as modal dialogs.

**It is also possible to add consistency checks and java action activities for Custom Blob Documents. Please see the respective tutorials at [Consistency Checks](/apidocs-mxsdk/apidocs/web-extensibility-api-11/consistency-checks/) and [Java Action Activities](/apidocs-mxsdk/apidocs/web-extensibility-api-11/java-action-activities-blob-documents/).**

Check failure on line 22 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/custom-blob-document-api.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.Capitalization] Use ''Java'' (capitalized) Raw Output: {"message": "[Mendix.Capitalization] Use ''Java'' (capitalized)", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/custom-blob-document-api.md", "range": {"start": {"line": 22, "column": 53}}}, "severity": "ERROR"}

## Registering a New Document Type

To register a new document type, do the following:
Expand All @@ -28,15 +30,15 @@

```typescript {hl_lines=["8-24"]}
import { IComponent, getStudioProApi } from "@mendix/extensions-api";
import { personDarkThemeIcon, personDocumentType, personLightThemeIcon } from "../model/constants";
import { personDarkThemeIcon, personDocumentType, personLightThemeIcon, personReadableDocumentType } from "../model/constants";
import { PersonInfo } from "../model/PersonInfo";

export const component: IComponent = {
async loaded(componentContext) {
const studioPro = getStudioProApi(componentContext);
await studioPro.app.model.customBlobDocuments.registerDocumentType<PersonInfo>({
type: personDocumentType,
readableTypeName: 'Person',
readableTypeName: personReadableDocumentType,
defaultContent: {
firstName: '',
lastName: '',
Expand All @@ -60,6 +62,7 @@

```typescript
export const personDocumentType = 'myextension.Person';
export const personReadableDocumentType = 'Person';
export const personLightThemeIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAGKADAAQAAAABAAAAGAAAAADiNXWtAAABKElEQVRIDd2Vyw3CMBBEAxIUAWVQBxIcKIBiuNAAFVAIV2iAA2cKoAGYF9nIctaxscIBRhrZ2Z3d9T9N8++YaoIb8ShexYcjfWz40FRhraib+MwQDdpijKXci7nEsZ8YYrOoSe6LEdsLpurFtW1yudiskjXPFSaHufGciFxwqZ9cLcJNWXnjAK2Zi7NdOsKcjlwdcIlygaV+crUIl8jbwnauj5F4CY2ujw0fmiTCAndDtXC2g+HzNq8JJVau9m2Jl+CkKAYxEbfi2ZE+Nnxo4jjeqQ5Sx3QnZThTH4gNX5yc7/cx9WLavovGKJfizJG+NXKSJy+afO2raI3oE1vyqaAA+OpjRwHWtqYIMdZekdMEUy15/NBkl8WsICMbz4ng2A3+y1TOH8ALNqHxhf/P+xwAAAAASUVORK5CYII=';
export const personDarkThemeIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAWdJREFUSIm1ljFuwkAQRd/giFTkABS5gMsolBRcIFBwCOTGNUfgDtDRJ9yDioaCKlJ8B0dYmyLjZGLtrh0Jj7SyNPP3f894dtbinHP0aIM+yQHuYkERuQdegDnwBIw1VABH4BV4c86VQRIXMGABXADXsi7AIsjjIR4AG0NwAnIgBUa6UvWdDG4DDLoI1OQlkAFJJMtEMWUtEhXQstTksxCxR2hmRP6UCwMamppnXcnN/sx8k6FPYGlqHixLRCAx32RZ++05mOtz65y7Btsu3I1XYNvgwmZwJty1XbNINYOzL4MxgIg8/Pftjb1bLmgZFSJSiAgiMvHEJhorYhxWoAY+Gt9RnyvP3lUDY/f+ipr67fmuX258U6ACPoEd8Kxrp74KmBp8rhz7H58JetsUWCtRcwZVwLqtTTsdNM3kAHzoOtg3V0z8oCmov1FhwP0NO93U77g2Qje5cETJvHaLKzMqcAvr/a/iC+JcVEP5CMhEAAAAAElFTkSuQmCC';
```
Expand Down Expand Up @@ -267,6 +270,40 @@

The code then provides a way to save changes.

### Creating a Document from Code {#creating-a-document-from-code}

It is also possible to create a new document directly using the api, by using the `createDocument` method. It requires a container ID (a module or a folder), type, content, and the name of the actual document. It is important to remember that documents can only be created if the project is currently initialized. When an extension first load when the containing project gets opened, its database will not be built yet. After the extension updates and gets reloaded, it will be. So it is advisable to check if the project is currently available before creating (or updating) documents.

```typescript
const project: ProjectMetadata | null = await studioPro.app.projectManager.getProjectMetadata();

if (project !== null){
await studioPro.app.model.customBlobDocuments.createDocument<PersonInfo>({
containerId: myModuleContainer.$ID,
type: personReadableDocumentType,
content: {
firstName: 'John',
lastName: 'Doe',
age: 30,
email: 'john.doe@info.com'
},
documentName: "person_document"
});
}
```

### Updating a Document from Code

It is also possible to update an existing document from the api, using the `updateDocumentContent` method. It is also necessary for the project to be initialized, or the document will not be found.

```typescript
const project: ProjectMetadata | null = await studioPro.app.projectManager.getProjectMetadata();

if (project !== null){
await studioPro.app.model.customBlobDocuments.updateDocumentContent<SimpleOpenProjectDocument>(documentId, newContent);
}
```

### Update Build and Manifest Files

The highlighted text in `build-extension.mjs` and `manifest.json` shows the changes necessary to ensure the `editor` entry point builds and loads properly.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
---
title: "Java Action Activities for Custom Blob Documents"
linktitle: "Java Action Activities"
url: /apidocs-mxsdk/apidocs/web-extensibility-api-11/java-action-activities-blob-documents/
description: "Describes how to allow a Custom Blob Document to have its own Java Action Activity in a Microflow"
---

## Introduction

Java Actions can have Custom Blob documents as a parameter. It is possible to link that java action directly to a document type when the type is registered. This allows the user of Studio Pro to simply drag a blob document from the App Explorer directly into a Microflow, and a new Java Action Activity will be automatically generated with that exact Blob document as the parameter value for the Java Action.

Check failure on line 10 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.Capitalization] Use ''Java'' (capitalized) Raw Output: {"message": "[Mendix.Capitalization] Use ''Java'' (capitalized)", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md", "range": {"start": {"line": 10, "column": 89}}}, "severity": "ERROR"}

## Prerequisites

* This how-to uses the results of [Get Started with the Web Extensibility API](/apidocs-mxsdk/apidocs/web-extensibility-api-11/getting-started/). Complete that how-to before starting this one.
* Familiarize yourself with creating custom documents as described in [Custom Blob Documents](/apidocs-mxsdk/apidocs/web-extensibility-api-11/custom-blob-document-api/). Also useful to know about [consistency checks for blob documents](/apidocs-mxsdk/apidocs/web-extensibility-api-11/consistency-checks/).

## Registering a Custom Blob Document with a Java Action
If the Java Action which contains a Blob Document type as its parameter already exists in your solution, you can simply use its qualified name during the registration call of your Blob Document type. The registration method will trigger when the Studio Pro app opens and extensions get loaded, and the two will be linked.

```typescript
async loaded(componentContext) {
const studioPro = getStudioProApi(componentContext);
await studioPro.app.model.customBlobDocuments.registerDocumentType<PersonInfo>({
type: personDocumentType,
readableTypeName: 'Person',
defaultContent: {
firstName: '',
lastName: '',
age: 0,
email: ''
},
javaActionQualifiedName: 'MyModule.MyJavaAction'
});

...
}
```

If you want to create the Java Action that has your new Blob Document Type as a parameter at the same time as registering the document, you can do so like shown below, but be aware that the Java Action will be created every time your extension gets loaded. This code below is a simple example to show how to create a Java Action and assign its parameter types to a Blob Document type.

```typescript

async loaded(componentContext) {
const studioPro = getStudioProApi(componentContext);

const moduleName = "MyModule";
const javaActionName = "MyJavaAction";

await createJavaActionWithBlobDocumentParameter(studioPro, moduleName, javaActionName, personDocumentType, "Person");

await studioPro.app.model.customBlobDocuments.registerDocumentType<PersonInfo>({
type: personDocumentType,
readableTypeName: 'Person',
defaultContent: {
firstName: '',
lastName: '',
age: 0,
email: ''
},
javaActionQualifiedName: `${moduleName}.${javaActionName}`
});

...
}

async function createJavaActionWithBlobDocumentParameter(studioPro: StudioProApi, moduleName: string, javaActionName: string, customDocumentTypeName: string, customDocumentReadableTypeName: string) {
const module = await studioPro.app.model.modules.getModule(moduleName);

if (!module) {
throw new Error(`Module was not found.`);
}

const javaActions = studioPro.app.model.javaActions;

const javaAction = await javaActions.createUnit(module.$ID, {
name: javaActionName
});

const parameterType = await javaActions.createElement<CodeActions.CustomBlobDocumentParameterType>(
"CodeActions$CustomBlobDocumentParameterType"
);

parameterType.customDocumentTypeName = customDocumentTypeName;
parameterType.customDocumentReadableTypeName = customDocumentReadableTypeName;

const parameter = await javaActions.createElement<JavaActions.JavaActionParameter>("JavaActions$JavaActionParameter", {
name: "document"
});

parameter.actionParameterType = parameterType;

javaAction.actionParameters.push(parameter);

await javaActions.save(javaAction);

return javaAction;
}

```

### Limitations and Suggestions

A Custom Blob Document and Java Action relationship is one to one. There can only be one Java Action per document type. If an extension tries to link a Java Action that is already linked to another type, the api will throw an error.
It is also advisable to write some [consistency checks](/apidocs-mxsdk/apidocs/web-extensibility-api-11/consistency-checks/) that detect when the Java Action is renamed or deleted, or its parameter types get changed by the user. So it is good practice to add the `javaActionQualifiedName` property to the contents of the Custom Blob Document as well, so that it is included in the document data when the consistency checks run. See below for a few sample consistency checks.

### Sample type that keeps track of the java action name

Check failure on line 106 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.Capitalization] Use ''Java'' (capitalized) Raw Output: {"message": "[Mendix.Capitalization] Use ''Java'' (capitalized)", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md", "range": {"start": {"line": 106, "column": 41}}}, "severity": "ERROR"}

Check failure on line 106 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.HeadingTitleCase] Use title case capitalization for 'Sample type that keeps track of the java action name'. Raw Output: {"message": "[Mendix.HeadingTitleCase] Use title case capitalization for 'Sample type that keeps track of the java action name'.", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md", "range": {"start": {"line": 106, "column": 5}}}, "severity": "ERROR"}
```typescript
export type JavaActionDocument = {
javaActionQualifiedName: string | undefined;
renamedJavaActionQualifiedName?: string | undefined;
someValue?: string | undefined;
};
```

### Consistency checks for lost action and parameter types

Check failure on line 115 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.HeadingTitleCase] Use title case capitalization for 'Consistency checks for lost action and parameter types'. Raw Output: {"message": "[Mendix.HeadingTitleCase] Use title case capitalization for 'Consistency checks for lost action and parameter types'.", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md", "range": {"start": {"line": 115, "column": 5}}}, "severity": "ERROR"}
```typescript
const withJavaActionDocumentType = "myextension.JavaActionDocument";

const wrongActionParameterErrorCode = "WRNJAP";
const noJavaActionErrorCode = "NOJAA";
const wrongNamedJavaActionErrorCode = "WRNJAA";
const reservedErrorCodes = [wrongActionParameterErrorCode, noJavaActionErrorCode, wrongNamedJavaActionErrorCode];

async function getConsistencyCheck(studioPro: StudioProApi) {
return async (data: JavaActionDocument) => {
const errors: ConsistencyError[] = [];

if (!data.javaActionQualifiedName || data.javaActionQualifiedName.trim().length === 0) {
errors.push({
errorCode: noJavaActionErrorCode,
errorDescription: `The Document of type ${withJavaActionDocumentType} must have a java action associated with it.`,
severity: "error",
elementText: "Parameter"
});
}

const [action] = await studioPro.app.model.javaActions.loadAll(unit => {
const name = `${unit.moduleName}.${unit.name}`;
return name === data.javaActionQualifiedName || name === data.renamedJavaActionQualifiedName;
});

const dependentElementIds: string[] = [];

if (!action) {
errors.push({
errorCode: noJavaActionErrorCode,
errorDescription: `The Document of type ${withJavaActionDocumentType} must have a java action associated with it.`,
severity: "error",
elementText: "Parameter"
});

return {
errors,
dependentElementIds
};
} else dependentElementIds.push(action.$ID); // track the JavaAction as a dependency of this document.

if (data.renamedJavaActionQualifiedName && data.renamedJavaActionQualifiedName !== data.javaActionQualifiedName) {
errors.push({
errorCode: wrongNamedJavaActionErrorCode,
errorDescription: `The Java action was renamed from ${data.javaActionQualifiedName} to ${data.renamedJavaActionQualifiedName}.`,
severity: "error",
elementText: "Name"
});

return {
errors,
dependentElementIds
};
}

const blobDocumentParameters = action.actionParameters.filter(
parameter => parameter.actionParameterType.$Type === "CodeActions$CustomBlobDocumentParameterType"
);

const correctTypeParameter = blobDocumentParameters.filter(
parameter =>
parameter.actionParameterType.$Type === "CodeActions$CustomBlobDocumentParameterType" &&
parameter.actionParameterType.customDocumentTypeName === withJavaActionDocumentType
);

const wrongTypeParameter = blobDocumentParameters.filter(
parameter =>
parameter.actionParameterType.$Type === "CodeActions$CustomBlobDocumentParameterType" &&
parameter.actionParameterType.customDocumentTypeName !== withJavaActionDocumentType
);

if (correctTypeParameter.length !== 1 || wrongTypeParameter.length > 0) {
errors.push({
errorCode: wrongActionParameterErrorCode,
errorDescription: `The Java Action "${data.javaActionQualifiedName}" must have a single parameter of type ${withJavaActionDocumentType}.`,
severity: "error",
elementText: "Parameter"
});
}

return {
errors,
dependentElementIds
};
};
}
```

### Tracking JavaAction renamed or re-added with same name after deletion

Check failure on line 205 in content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md

View workflow job for this annotation

GitHub Actions / vale

[vale] reported by reviewdog 🐶 [Mendix.HeadingTitleCase] Use title case capitalization for 'Tracking JavaAction renamed or re-added with same name after deletion'. Raw Output: {"message": "[Mendix.HeadingTitleCase] Use title case capitalization for 'Tracking JavaAction renamed or re-added with same name after deletion'.", "location": {"path": "content/en/docs/apidocs-mxsdk/apidocs/studio-pro-11/extensibility-api/web/web-extensions-howtos/java-action-activities-blob-documents.md", "range": {"start": {"line": 205, "column": 5}}}, "severity": "ERROR"}
Using events from `studioPro.app.projectChanges` it is possible to know when the Java Action gets renamed or re-added with the same name:

```typescript
studioPro.app.projectChanges.addEventListener("elementsRenamed", async ({ elements }) => {
const javaActionsRenamed = elements.filter(element => element.documentType === "JavaActions$JavaAction");

const javaActionBlobDocuments = await studioPro.app.model.customBlobDocuments.getDocumentsOfType(withJavaActionDocumentType);
for (const doc of javaActionBlobDocuments) {
const d = await studioPro.app.model.customBlobDocuments.getDocumentById<JavaActionDocument>(doc.id);

if ("document" in d && d.document) {
for (const javaActionRenamed of javaActionsRenamed) {
// renamed JavaAction's old name matches our JavaAction, so we track the new name.
if (javaActionRenamed.oldName.qualifiedName === d.document.contents.javaActionQualifiedName) {
d.document.contents.renamedJavaActionQualifiedName = javaActionRenamed.newName.qualifiedName;

// always save the document so that the consistency checks run again
await studioPro.app.model.customBlobDocuments.updateDocumentContent(d.document.$ID, d.document.contents);
}

// renamed JavaAction new name matches our name, we can stop tracking the rename
if (javaActionRenamed.newName.qualifiedName === d.document.contents.javaActionQualifiedName) {
d.document.contents.renamedJavaActionQualifiedName = undefined;

// always save the document so that the consistency checks run again
await studioPro.app.model.customBlobDocuments.updateDocumentContent(d.document.$ID, d.document.contents);
}
}
}
}
});

studioPro.app.projectChanges.addEventListener("documentAdded", async ({ document }) => {
const javaActionDocuments = await studioPro.app.model.customBlobDocuments.getDocumentsOfType(withJavaActionDocumentType);

for (const doc of javaActionDocuments) {
const d = await studioPro.app.model.customBlobDocuments.getDocumentById<JavaActionDocument>(doc.id);

if ("document" in d && d.document) {
const javaAction = (await studioPro.app.model.javaActions.loadAll(ja => ja.$ID === document.documentId)).find(
ja => ja.$ID === document.documentId
);

if (javaAction) {
const qualifiedName = (javaAction as JavaActions.JavaAction & { $QualifiedName: string }).$QualifiedName;

// new JavaAction is in fact our own
if (d.document.contents.javaActionQualifiedName === qualifiedName) {
d.document.contents.javaActionQualifiedName = qualifiedName;
d.document.contents.renamedJavaActionQualifiedName = undefined;

// trigger the change to run consistency checks again, since this new
// action is probably missing the required parameters of the correct type.
await studioPro.app.model.customBlobDocuments.updateDocumentContent(d.document.$ID, d.document.contents);
}
}
}
}
});
```
{{% alert color="info" %}}
Studio Pro does not track the deletion or renaming of Java Actions that are linked to Custom Blob Documents. So these consistency checks suggested here can help an extension developer handle those cases. It is important to remember that these code samples are simple examples meant to be used as a basis for your own production code.
{{% /alert %}}
Loading
Loading