Skip to main content

Create Layout Widget

A Create Layout widget appears as a section on the new-record form. Use it when the standard field inputs in the form are not enough — for example, when you need to build a dynamic list, a complex picker, or a multi-step form inside a single section.


pageContext shape

Source: CreateWidgetRenderer.jsx

{
// Record type (no instanceId — record doesn't exist yet)
instanceType,

// Attribute data (from the form so far)
attributeValues, // { [attrName]: attrObj } — current values
editedValues, // { [attrName]: attrObj } — in-progress edits
getAttributeWorkingValue, // function(attrName) → current working value

// Edit integration
onAttributeEdit, // function(attrName, attrObj, errorMsg) — sync to form
createInstance, // function() — trigger record creation programmatically
saveOperationState, // { isLoading, isError, data }

// Layout config
layoutConfig,
instanceEditDispatch, // Low-level reducer dispatch (advanced)

// Clone support
cloneSourceInstance, // object | null — source record when creating from clone

// Permissions
allowedOperations,
isPromoteInProgress,

isCreateLayout: true, // Always true — identifies this as a create context

// Common (always present)
settings,
userData,
}

Note: There is no instanceId and no saveInstance in Create Layout. Use createInstance() to save the new record.


How it differs from Edit Layout

On the Create layout:

  • There is no instanceId yet — the record doesn't exist until the user saves
  • Use createInstance() (not saveInstance()) to trigger record creation from the widget
  • cloneSourceInstance is available if the user started from a "clone" action

Example 1 — Dynamic option list stored as JSON

The widget lets the user build a list of poll options. Each time the list changes, it calls onAttributeEdit so the JSON value is stored in a text attribute.

function QuickPollOptionsWidget(pageContext) {
const { protrakComponents } = React.useContext(customWidgetContext);
const { Box, Button, ButtonEnums, Label, TextBox } = protrakComponents;

const [options, setOptions] = React.useState([]);

const syncToForm = (updated) => {
pageContext.onAttributeEdit(
'QuickPollOptions',
{
name: 'QuickPollOptions',
type: 'Text',
canUpdate: true,
textValue: JSON.stringify(updated),
},
''
);
};

const addOption = () => {
const updated = [...options, { value: '', text: '' }];
setOptions(updated);
syncToForm(updated);
};

const removeOption = (index) => {
const updated = options.filter((_, i) => i !== index);
setOptions(updated);
syncToForm(updated);
};

const updateField = (index, field, val) => {
const updated = options.map((o, i) =>
i === index ? { ...o, [field]: val } : o
);
setOptions(updated);
syncToForm(updated);
};

return (
<Box style={{ display: 'block', padding: '1rem' }}>
<Button
onClick={addOption}
text="Add Option"
appearance={ButtonEnums.Appearance.Primary}
/>
<table>
<tbody>
{options.map((opt, i) => (
<tr key={i}>
<td>
<Label>Label</Label>
<TextBox
value={opt.value}
onEdit={(v) => updateField(i, 'value', v)}
/>
</td>
<td style={{ paddingLeft: '1rem' }}>
<Label>Value</Label>
<TextBox
value={opt.text}
onEdit={(v) => updateField(i, 'text', v)}
/>
</td>
<td>
<span
onClick={() => removeOption(i)}
style={{
marginLeft: '1rem',
color: 'var(--error)',
cursor: 'pointer',
}}
>
<i className="fa fa-times" />
</span>
</td>
</tr>
))}
</tbody>
</table>
</Box>
);
}

Before adding options:

target_type_create_layout1.png

After clicking "Add Option":

target_type_create_layout2.png


Example 2 — Pre-fill a field based on another field's value

Watch attributeValues to auto-fill a derived value when the user changes a related field.

function AutoFillCodeWidget(pageContext) {
const { protrakComponents } = React.useContext(customWidgetContext);
const { Text } = protrakComponents;

const name = pageContext.getAttributeWorkingValue?.('Name')?.textValue ?? '';
const autoCode = name.replace(/\s+/g, '-').toUpperCase().slice(0, 10);

React.useEffect(() => {
if (autoCode) {
pageContext.onAttributeEdit(
'Code',
{
name: 'Code',
type: 'Text',
canUpdate: true,
textValue: autoCode,
},
''
);
}
}, [autoCode]);

return <Text style={{ color: 'grey' }}>Auto-generated code: {autoCode}</Text>;
}

Tips

  • onAttributeEdit pushes values into the standard form groups on the same layout — the standard Save button picks them up.
  • If your widget is the only way to enter a value, make sure the attribute is also marked as a required field in the Admin, so the form validates it on save.
  • To handle the "clone" case (user duplicating a record), check pageContext.cloneSourceInstance and pre-populate your state from it.
  • For deep-dive patterns, see JSON Attribute Editor Pattern.