Dependent Field and Initial Value

I’m trying to use the feature “Initial value” in the “Dependent field” definition.
I started from the “e-commerce” example workspace and reduced the model to the smallest.

My Doc model:

  • I have a field A (here the price) (in my real case it would be an enumeration, but I think it makes no difference)
  • An intermediary field B (here hasPrice) is computed from A and yields a Boolean.
  • a third field C (here OnlineProduct) was a Boolean and I changed it to an Enum with 2 values

My Form model:

  • whenever A has a certain value, and therefore B is computed to true, I want to set the initial value of C to a certain enumeration value “vrai”, but it should stay editable by the user
  • whenever A has another value, and therefore B is computed to false, I want to enforce the enumeration value “faux” for field C, and it should be read-only.

It should work when loading the data, because in my real application, field A will be filled by the backend after a new document is uploaded to the system. So I want the field C to be correct (pre-filled and read-only/editable) when the user opens the form for the first time.

What I get in the SME preview (and also with my running application):

  • the read-only/editable property is applied correctly, either when uploading test data, or when changing field A manually
  • but the initial value is not applied, it stays either empty, or to whatever value I set manually while the field is editable.

How is it supposed to work? did I understand correctly the feature?
I check the documentation: it states " Value: Write the given value into the Dependent Field."

Here a minimal doc model, form model, and preview data:
ProductBook_DM_minimal.json (6.0 KB)
ProductBook_FM_minimal.json (10.7 KB)
prod_with_price.json (98 Bytes)
prod_without_price.json (57 Bytes)

Hello @marieestelle-firm-summit , I took a look into your models. As I see it, there are two problems.

Problem 1:

The first one is that the dependency is not working while you manually enter the price or leave the price empty. This problem can be solved like this:

I recognized that you are using a boolean (HasPricing). The important thing to know is that a boolean can have three states. 1. Null

  1. True

  2. False

Therefore, you need to add a second calculation to your computation (CompHasPrice). To also compute the false status of the boolean. See the linked picture below. I also added the now working DM and FM.

grafik

Problem2:
The second problem is that you want to upload only the price via a JSON from the backend into the running system.
Here you have to distinguish between the visibility control and the changing of values due to the dependency. These two features of the dependency work in different ways. On the one hand, the visibility control is not dependent on the changing of a field. It is only dependent on the static value. So this is functional. On the other hand, the changing of a value due to dependency control is dependent on the changing of a field in a Form. So it is really connected to changes in the Form.

In this context, it is important to know that your uploaded file does not lead to a change in the field “Price”, therefore it does not lead to changes in the Form. The system thinks that the entry was always there. Therefore, the changing of the value of the bolean “HasPrice” will not be triggered, since it depends on the changing of the Form. But the visibility control of the bolean will be triggered, since this is only dependent on the value of the field “price”.

If you want to solve the problem, you also have to upload the status change from the backend into the form. I added two working JSON files you could use to test this.

Price.json (152 Bytes)
No price.json (155 Bytes)
ProductBook_DM_minimal.json (6.2 KB)
ProductBook_FM_minimal.json (10.7 KB)

Dear @marieestelle-firm-summit, is the response helping your case? Or do you have some additional questions? If the response helped, feel free to use the checkbox-like icon below the response so that other users know if the proposed solution is helpful.
Thanks ahead.
The Discourse moderation team

Hi, the response helped me to understand how it currently works, but it doesn’t solve my need. Thus my team created a ticket (A12-15397) to improve the initial value behavior.

Hi @svenja-still-dawn ,

I am facing the Problem 1 that you described in comment. I tried to apply your solution, but it only works when I open a new form. I still encounter the problem when I try to open an existing document. Do you have any ideas?

Hi @quynh-still-birch,
what version of A12 do you use?
Thanks ahead for clarification.

Hi @katerina-icy-token , I am using A12 v2023.06 ext5

Hi @katerina-icy-token
For more information, I have a string field named CountryOfService that depends on a boolean field named IsReverseCharge. I want to hide the CountryOfService field if IsReverseCharge is not filled or is set to False. I created a computation as @svenja-still-dawn suggested in the comment and added a new field to contain the computed value (I marked it as a transient field). Later on, I used the computed field for my dependent field (CountryOfService). However, the computation seems to work only on a new form but not when opening an existing document.

Thanks for these details, I will try to check how it could be achieved.
Is your project planning to upgrade to newer A12?

We haven’t planned it yet. We are almost done with the upgrade to version 2023.06, but this issue still remains.

Moin @quynh-still-birch,
I am facing the same issue for another project now and also reproduced it using the Project Template in 2023.06.

Currently I am trying to implement the triggering of computations also for already existing documents. I will leave an update on this thread as soon as my client-side customization works.
(I already have a back-end implementation which works, but this creates huge overhead and a performance hit)

The reason why it works for new documents is, that both the Preview Application and the Project Template use the createEmptyDocumentDataProvider() from the Form Engine. This DataProvider triggers computations for specifically on load for new documents. But there is no such DataProvider configured to trigger computations on load for existing documents.

If you have any questions already, feel free to reach out to me.

Moin @quynh-still-birch,

I created the following example for triggering all computations for existing documents using the Project Template:

initialComputeDataProvider.ts

import { call, put, select } from "typed-redux-saga";

import { Model, ModelSelectors } from "@com.mgmtp.a12.client/client-core/lib/core/model";
import { DataProvider } from "@com.mgmtp.a12.client/client-core/lib/core/data";
import { Activity, ActivityActions, ActivitySelectors } from "@com.mgmtp.a12.client/client-core/lib/core/activity";
import {
    DocumentJsonRpc2Request,
    JsonRpc2Request,
    JsonRpc2ResponseOK
} from "@com.mgmtp.a12.dataservices/dataservices-access";
import {
    ConnectorLocator,
    RestRequestPayload,
    RestServerConnector
} from "@com.mgmtp.a12.utils/utils-connector/lib/main";
import {
    DocumentRtService,
    DocumentRtServiceFactory,
    DocumentServiceFactory
} from "@com.mgmtp.a12.kernel/kernel-md-facade";

const documentService = new DocumentServiceFactory().getDocumentService();

export function initialComputeDataProvider(): DataProvider {
    const name = "InitialComputeDataProvider";
    let operationCounter = 0;

    return {
        name,
        canHandle({ operation, dataHolder }) {
            const descriptorInstance = dataHolder.descriptor.instance;
            const descriptorModel = dataHolder.descriptor.model;

            // This DataProvider shall only serve data when an existing document is loaded
            if (operation === "load" && descriptorInstance && descriptorModel) {
                return (descriptorInstance as string).includes(descriptorModel);
            }
            return false;
        },
        *provideData({ activityId, dataHolders, ...config }) {
            if (config.operation !== "load") {
                throw new Error(`${name} does not support ${config.operation}.`);
            }
            const activity = yield* select(ActivitySelectors.activityById(activityId));
            const dataHolder = Activity.findDefaultDataHolder(activity);

            if (!dataHolder) {
                throw new Error("Activity Dataholder does not exist.");
            }
            const activityDescriptor: Activity.DataHolderDescriptor = dataHolder.descriptor;
            if (!activityDescriptor.instance) {
                throw new Error("Activity Descriptor Instance does not exist.");
            }

            // Retrieve document for given document reference
            const getDocumentRpcRequest: DocumentJsonRpc2Request.GetDocumentJsonRpc2Request = {
                jsonrpc: "2.0",
                method: "GET_DOCUMENT",
                id: `GET_DOCUMENT-${++operationCounter}`,
                params: {
                    docRef: activityDescriptor.instance
                }
            };

            const [{ result }] = yield* call(() => dispatchJsonRpc2Requests(getDocumentRpcRequest));

            const { document, docRef, documentModelName } = result;

            // Select DocumentModel for the generated code
            if (!activityDescriptor.model) {
                throw new Error("Activity Descriptor Model does not exist.");
            }
            const documentModelSelector = ModelSelectors.modelByName(
                documentModelName,
                Model.isDocumentAndValidationModel
            );

            const documentModel = yield* select(documentModelSelector);
            if (!documentModel) {
                throw new Error("Document Model could not be loaded.");
            }

            // Compute document with 'DocumentRtService'
            const documentRtService: DocumentRtService = DocumentRtServiceFactory.createDocumentRtService(
                documentModel.generatedCodeAccessor
            );
            const computationResult = documentRtService.compute(document);

            // TODO: Error handling in case of computation failing

            const computedDocument = documentRtService.applyComputationResult(computationResult, document);

            // Provide the data with computed document
            yield* put(
                ActivityActions.setData({
                    activityId,
                    data: {
                        document: {
                            id: docRef,
                            modelId: documentModelName,
                            ...documentService.parseDates(computedDocument, documentModel)
                        }
                    }
                })
            );
        }
    };
}

async function dispatchJsonRpc2Requests(...requests: JsonRpc2Request[]): Promise<JsonRpc2ResponseOK[]> {
    const request = JsonRpc2Request.build(requests);

    return await RestRequestDispatcher.json(request);
}

const RestRequestDispatcher = {
    async json<T>(request: RestRequestPayload): Promise<T> {
        const response = await fetchServerRequest(request);

        return await response.json();
    }
};

async function fetchServerRequest(request: RestRequestPayload): Promise<Response> {
    const response = await (ConnectorLocator.getInstance().getServerConnector() as RestServerConnector).fetchData(
        request
    );

    if (!response.ok) {
        throw new Error(response.statusText);
    }

    return response;
}

Then registering the Data Provider in the setup.
appsetup.ts

const dataHandlers: DataHandlers = {
        dataEditors: [],
        dataLoaders: platformServerConnectors.loaders.dataLoaders,
        dataProviders: [
            initialComputeDataProvider(),
            cddDataProvider,
            RelationshipFactories.createRelationshipDataProvider(),
            createEmptyDocumentDataProvider()
        ]
    };

@marieestelle-firm-summit the corresponding feature ticket is now done and will be releases with the 2024.06-ext4 release. Thus this problem should be solved after migrating.

Hi @svenja-still-dawn ,
Does it work with CDM based form? I’m using 2024.06-ext4, the initial computed value shown in the SME preview mode, but not my running application.

Hey @huyen-gentle-brush,

there are two new setting in the Form Model, to trigger the Computations an Dependencies accoring to your business requirements. Please ensure, that they are set correctly.

Moreover, Computations were and are always triggered in CDM cases, in order to fill the CDM-only Fields. However, this is a feature that is only seen in the running application, as there is no CDM handling in the Modeling Environment. (The Form Model Preview treats the form as a regular form. This is also why there are only placeholders shown for Bindings.)

I tried the use of Computations and Dependencies in CDM form in 2024.06-ext4 and can confirm that Computations, that use Fields with Initial Values set in Form Models, work correctly. They are triggered correctly.
Dependencies based on such computed Fields are also triggered regarding the visibility. However, there is a bug regarding the Setting of Values by Dependencies. I created A12-17565 regarding this.
So in short:

  • Computations work. If they reference Fields, that have Initial Values set in the Form Model, these values are used
  • Dependent Enumeration with a computed Field as Trigger
    • does restrict the possible enumeration values :check_mark:
    • does not set the initial value :cross_mark:
  • Field Value Dependency with a computed Field as Trigger
    • does mark the Field Read Only/notRelevant :check_mark:
    • does not set the Value :cross_mark:

Hello @marieestelle-firm-summit, I am a member of the Discourse team. I can see that there was a new response to your question. Do you find it helpful or should the question remain open?

Thanks ahead for your feedback and have a nice rest of the day!