The problem at hand: The Interactive Grid’s Save button should be styled as “Hot” (aka primary) if there are pending changes, and not Hot otherwise.
Inspired by Jan’s interesting problem and possible solution, I’d like to propose an alternative. Prerequisites:
- No Mutation Observers- they scare me
- No complex selectors- they will break (eventually)
- Let’s use the native APEX JS APIs
- All code should go in the IG’s Init JS Code attribute
Step 1. The Save button should not be Hot on page load. Let’s copy the default toolbar and make our changes.
function( options ) {
const toolbarData = $.apex.interactiveGrid.copyDefaultToolbar();
const saveButton = toolbarData.toolbarFind( "save" );
saveButton.hot = false;
saveButton.id = "saveBtn";
options.toolbarData = toolbarData;
return options;
}
Here we found the correct button by its action name. There are many ways to find the available actions and their names. For example, you can inspect the button and look for the data-action attribute. You can also check the docs. In our case, it was just "save"
. Let’s also give this button an ID, as it didn’t have one already. This will come in handy later.
Step 2. Finding out when the grid data is changed, saved, reset, etc. Let’s get this info straight from the source, the grid’s model. We can listen to these events via a model observer.
const region = apex.regions.myGridId;
const model = region.call( "getViews", "grid" ).model;
model.subscribe( {
onChange: ( changeType, change ) => {
console.log( model.isChanged() );
}
} );
The changeType parameter tells us in more detail what just happened, but in this case we don’t really care. For our purpose, we can just use the model.isChanged()
function and ignore the rest.
Step 3. Actually updating the button. First, we need the toolbar widget- myRegion.call("getToolbar")
will do. We can update the button again like in Step 1, and refresh the whole toolbar via toolbar$.toolbar("refresh")
, but you would notice a flash as the toolbar is re-rendered. Let’s instead only toggle the Hot class on the specific button instead.
const toolbar$ = region.call( "getToolbar" );
const isHot = model.isChanged();
saveButton.hot = isHot;
toolbar$.toolbar( "findElement", buttonId ).toggleClass( "a-Button--hot", isHot );
Step 4. Putting it all together in the IG’s Init JS Code attribute:
function( options ) {
const buttonId = "saveBtn";
const toolbarData = $.apex.interactiveGrid.copyDefaultToolbar();
// uses the predefined action name
const saveButton = toolbarData.toolbarFind( "save" );
// initial hot state
saveButton.hot = false;
// the save button has no id by default. let's define one
saveButton.id = buttonId;
// use this new custom toolbar
options.toolbarData = toolbarData;
// on page load
$(() => {
const region = apex.regions[ options.regionStaticId ];
const model = region.call( "getViews", "grid" ).model;
const toolbar$ = region.call( "getToolbar" );
model.subscribe( {
onChange: () => {
// let the model dictate if there are any changes
const isHot = model.isChanged();
// update the toolbar data in case it is refreshed externally
saveButton.hot = isHot;
// we don't do a full toolbar refresh to avoid the visual flash
toolbar$.toolbar( "findElement", buttonId ).toggleClass( "a-Button--hot", isHot );
}
} );
});
return options;
}
Conclusion. The “Hot” state is indeed a tricky one. It lives at toolbar-item level, and is not part of the action, like disabled
, or hidden
. When dealing with action properties, it gets much easier. Here’s how you would disable/enable the button based on the same criteria.
function( options ) {
$(() => {
const region = apex.regions[ options.regionStaticId ];
const actions = region.call( "getActions" );
const model = region.call( "getViews", "grid" ).model;
model.subscribe( {
onChange: () => {
if( model.isChanged() ) {
actions.enable( "save" );
} else {
actions.disable( "save" );
}
}
} );
actions.disable( "save" );
});
return options;
}