Skip to main content

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

TargetNotes
ViewLayoutShow an action button on a view tab
EditLayoutShow an action button alongside edit form
CreateLayoutProvide a utility during record creation

Pattern Structure

  1. Read the relevant attribute value from pageContext.attributeValues (view/saved) or pageContext.editedValues (current in-flight)
  2. Use protrakApiClient for the action (not useProtrakApi — imperative, one-shot call)
  3. Manage loading/error state locally with useState
  4. 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

protrakApiClientuseProtrakApi
StyleImperative — await in event handlerDeclarative — auto-executes
When to useOne-shot actions triggered by user (download, save, program call)Data fetching on mount or when dependencies change
AuthRequires authContext from useAuthContext()Handled automatically
Error handlingtry/catch around the callstate.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