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:
- Is there a supported Freedom UI pattern to prevent
HasUnsavedData from being set by ForwardReference resolution on page load?
- Is there a lifecycle event or request that fires reliably after all ForwardReferences have fully resolved?
- 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?
- 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