Utility / Action Widget Pattern
Overview
A widget that reads one or more attribute values from the current instance and uses them to trigger an external action — typically a file download, an API call, or a program execution. The widget does not edit any attributes and is not involved in the parent form's save lifecycle.
When to Use
- Allow the user to download a template file based on a picklist selection
- Show a button that calls a backend program using the current instance's attribute as input
- Provide a utility action (e.g., copy to clipboard, send a one-time notification) that is not a form edit
Applicable Layout Types
| Target | Notes |
|---|---|
ViewLayout | Show an action button on a view tab |
EditLayout | Show an action button alongside edit form |
CreateLayout | Provide a utility during record creation |
Pattern Structure
- Read the relevant attribute value from
pageContext.attributeValues(view/saved) orpageContext.editedValues(current in-flight) - Use
protrakApiClientfor the action (notuseProtrakApi— imperative, one-shot call) - Manage loading/error state locally with
useState - Disable the action button while loading or when required input is missing
Code Example — Template File Download
function DownloadTypeTemplateWidget(pageContext) {
const { protrakComponents, protrakUtils } =
React.useContext(customWidgetContext);
const { Container, Button, ButtonEnums, Text } = protrakComponents;
const { protrakApiClient, useAuthContext } = protrakUtils;
const authContext = useAuthContext();
// Read the selected type from the attribute — check both attributeValues and editedValues
const importTypeAttr =
pageContext?.attributeValues?.find?.(
(a) => a.name === 'ProtrakBulkImportType'
) ||
pageContext?.attributeValues?.['ProtrakBulkImportType'] ||
pageContext?.editedValues?.['ProtrakBulkImportType'] ||
null;
const selectedType = importTypeAttr?.arrayValue?.[0] || null;
const [isDownloading, setIsDownloading] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState(null);
const handleDownload = async () => {
if (!selectedType) return;
setIsDownloading(true);
setErrorMessage(null);
const tenantId = pageContext.settings.id.toUpperCase();
const endpoint = `files/download/pubc-protrak/${tenantId}/${selectedType}UploadTemplate`;
try {
const response = await protrakApiClient(
endpoint,
{ method: 'GET', responseType: 'blob' },
authContext
);
if (!response?.data) {
throw new Error('No file data returned');
}
// Trigger browser download
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${selectedType}UploadTemplate.xlsx`;
a.click();
window.URL.revokeObjectURL(url);
} catch {
setErrorMessage(
'Failed to download template. Please try again or contact support.'
);
} finally {
setIsDownloading(false);
}
};
return (
<Container>
<div style={{ textAlign: 'center', padding: '1rem' }}>
<Text style={{ fontWeight: 'bold', marginBottom: '0.5rem' }}>
{selectedType
? `Download Upload Template for: ${selectedType}`
: 'Select Type to download template'}
</Text>
<Button
text={isDownloading ? 'Downloading...' : 'Download Template'}
title="Download Template"
onClick={handleDownload}
appearance={ButtonEnums.Appearance.Primary}
disabled={!selectedType || isDownloading}
style={{ padding: '0.5rem 1.5rem' }}
/>
{errorMessage && (
<Text
role="alert"
style={{
color: 'var(--error)',
marginTop: '0.5rem',
fontSize: '0.9rem',
}}
>
{errorMessage}
</Text>
)}
</div>
</Container>
);
}
protrakApiClient vs useProtrakApi
protrakApiClient | useProtrakApi | |
|---|---|---|
| Style | Imperative — await in event handler | Declarative — auto-executes |
| When to use | One-shot actions triggered by user (download, save, program call) | Data fetching on mount or when dependencies change |
| Auth | Requires authContext from useAuthContext() | Handled automatically |
| Error handling | try/catch around the call | state.isError flag |
File Download Pattern
const { protrakApiClient, useAuthContext } = protrakUtils;
const authContext = useAuthContext();
const response = await protrakApiClient(
`files/download/pubc-protrak/${tenantId}/FileName`,
{ method: 'GET', responseType: 'blob' },
authContext
);
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'filename.xlsx';
a.click();
window.URL.revokeObjectURL(url);
Program Execution Pattern
const response = await protrakApiClient(
'programs/MyProgramName/executeCommonProgram',
{
method: 'POST',
data: [JSON.stringify({ InstanceId: instanceId, SomeParam: value })],
},
authContext
);
Real Examples
DownloadTypeTemplateWidget.js— Buildcast, DevinciPrecastPOC, GEECI