Freedom UI: HasUnsavedData = true on page load due to ForwardReference attributes and calculated fields — how to suppress it?

Dear colleagues,

I have a Freedom UI page (Creatio v8.3.2.4166) that activates the Save button immediately on load, without any user interaction.

After extensive debugging, I identified three contributing factors:

1. ForwardReference attributes in modelConfigDiff. The page has multiple attributes of type "ForwardReference" resolving data from two related entities. These are all read-only fields. Example:

"NcsRelatedEntityNcsDescription_abc123": {
    "path": "NcsRelatedEntity.NcsDescription",
    "type": "ForwardReference"
}

When these attributes resolve asynchronously after page load, the framework registers them as user changes and sets HasUnsavedData = true.

2. RichTextEditor controls with <strong>needHandleSave: true</strong> — for some reason the page wizard generated them with true by default, even though all of them are readonly and display data from a related entity, not from the current object's own fields. We changed all of them to needHandleSave: false, which helped partially but did not fully resolve the issue.

3. Calculated field handlers. The page has a handler that calculates age from a birth date field (and another similar purpose handlers):

{
    request: "crt.HandleViewModelAttributeChangeRequest",
    handler: async (request, next) => {
        if (request.attributeName === 'PDS_NcsBirthDate_abc123') {
            const birthDate = await request.$context.PDS_NcsBirthDate_abc123;
            if (birthDate) {
                const today = new Date();
                const birth = new Date(birthDate);
                let age = today.getFullYear() - birth.getFullYear();
                if (today.getMonth() < birth.getMonth() ||
                   (today.getMonth() === birth.getMonth() &&
                    today.getDate() < birth.getDate())) {
                    age--;
                }
                request.$context.PDS_NcsAge_xyz789 = age;
            } else {
                request.$context.PDS_NcsAge_xyz789 = null;
            }
        }
        return next?.handle(request);
    }
}

When the page loads with an existing record, PDS_NcsBirthDate_abc123 fires as an attribute change event during model initialization, the handler writes PDS_NcsAge_xyz789, and that write marks the page dirty — even though both values were already saved in the database.

The same pattern applies to a handler that composes a full name from first and last name fields, and to the RichTextEditor ForwardReference fields that display read-only rich content from a related entity.

What we tried:

Approach 1 — Subscribing to events$ in HandleViewModelInitRequest to detect finish-load-model-attributes, setting a window._NcsPage_pageReady flag, and guarding all attribute change handlers behind it. This correctly prevents our own handlers from triggering dirty state, but does not prevent the framework itself from setting HasUnsavedData = true when ForwardReferences resolve.

Approach 2 — Adding a HandleViewModelResumeRequest handler that forces HasUnsavedData = false after the model is ready. This fires too early — ForwardReferences resolve after Resume, so they overwrite the reset.

Approach 3 — Using setInterval (up to 35 cycles × 200ms = 7 seconds) inside the Init handler to repeatedly reset HasUnsavedData = false. This does not work because the request.$context captured in the Init handler closure becomes stale after initialization — Creatio replaces the ViewModel reference, so we are resetting a dead object.

Approach 4 — Intercepting all HandleViewModelAttributeChangeRequest as the first handler in the chain, calling await next?.handle(request) and then forcing HasUnsavedData = false on the fresh request.$context. This works during the initial load phase (while _pageReady = false), but ForwardReferences continue resolving after _pageReady = true, still triggering dirty state.

Approach 5 — Combining Approach 4 with permanent suppression for all known ForwardReference attributes (since they are always readonly):

{
    request: "crt.HandleViewModelAttributeChangeRequest",
    handler: async (request, next) => {
        const result = await next?.handle(request);
        const isForwardRef = request.attributeName?.startsWith('PDS_NcsRelatedEntity');
        if (!window._NcsPage_pageReady || isForwardRef) {
            request.$context.HasUnsavedData = false;
        }
        return result;
    }
}

This still fails — HasUnsavedData keeps flipping back to true after each reset.

Questions:

  1. Is there a supported Freedom UI pattern to prevent HasUnsavedData from being set by ForwardReference resolution on page load?
  2. Is there a lifecycle event or request that fires reliably after all ForwardReferences have fully resolved?
  3. Is there a way to prevent a handler that writes a calculated field (like age from birth date) from marking the page dirty when it fires during initialization?
  4. Is there any way to mark specific attributes as non-dirty-tracking so the framework ignores their changes for save state purposes?

Any guidance from the community would be greatly appreciated. Thank you very much

Regards

Julio Falcón

Like 1

Like

0 comments
Show all comments