[data-reveal]{opacity:1!important;transform:none!important}
AntelopeJS DMS
DocumentationCustom inputs

Custom inputs

Replace a field's input with a component of your own, and keep its validation, its error and its save.

A Form field draws the input of its data type. To draw it another way, give the field an input component of your own. The field keeps its validation, its error message and its place in the save.

Overriding the input component

Each field renders the input its DataType chooses. Set inputComponent to override it with a factory from the FormComponents namespace (same import as Form), each taking its own options object:

  • Text and numbers: InputText, InputTextarea, InputEmail, InputPassword, InputPhone, InputNumber, InputPercentage, InputSlider, RichText.
  • Choices: InputSelect, InputRadioGroup, InputSwitch, InputCheckbox, InputCascader, InputRelation.
  • Dates and times: DatePicker, DatePickerRange, Calendar, InputTime.
  • Media and specialized: File, Image, InputColor, InputAddress.

Prefer configuring the DataType; reach for inputComponent only when you need a different control than the type's default.

A module's own input

An inputComponent may also name a component of the module's frontend layer. It receives the field's id (give it to its control, so the error summary and the field's anchor find it), v-model, disabled and its own options as props. What it needs of the form it sits in, it asks useEnclosingForm(), auto-imported, rather than the DOM (closest("form"), requestSubmit()):

MemberTypeContract
submit()() → Promise<void>Submits the form as its save button does: validation, the validators, then the request. A refused submit shows its errors.
addValidator(validator)(() => boolean) → () => voidRuns validator before each submit; false stops the submit before anything is sent. Removed with the component, or by calling the returned function.
focusField(fieldId)(string) → Promise<void>Brings a field of the form into view and focuses its control (a group's first focusable part).

useEnclosingForm() returns null outside a Form. A field that asks before a save, then saves once confirmed:

app/components/RetentionInput.vue ts
const form = useEnclosingForm();
const { confirm } = useConfirm();
let confirmedValue: number | null = null;

form?.addValidator(() => {
  if (!isLowered.value || confirmedValue === model.value) return true;
  void confirm({ title: "Delete older logs?", color: "error" }).then((accepted) => {
    if (!accepted) return;
    confirmedValue = model.value ?? null;
    void form.submit();
  });
  return false;
});

A number the field adjusted

DmsInputNumber, the number control a module's own input can draw, keeps a typed value within its min and max when the user leaves it or presses Enter: 250 typed in a field of max: 100 becomes 100. Its step only drives the steppers and the arrow keys: a value is never put on it, typed or loaded (9,692 stays 9,692 with steps of 100), unless the control is given stepSnapping: true, where 1020 typed in a field of steps of 50 becomes 1,000. It then shows the value it kept, never the one typed, and emits adjust with an InputNumberAdjustment (auto-imported type):

MemberTypeContract
typednumberThe number as typed, read in the field's locale ("1,020").
valuenumberThe number the field keeps, already in v-model.
reason"min" · "max" · "step"Above max, below min, or off step (stepSnapping only).

Nothing is emitted when the field keeps what was typed, or when nothing was typed since it last settled. With clamp: false the field keeps a value outside min / max and reports it as its error, so only "step" can be emitted. A custom field says why its value changed with a note under the control, cleared once the value changes again:

app/components/BodyCapInput.vue vue
<script setup lang="ts">
const model = defineModel<number | null>();
const adjustment = ref<InputNumberAdjustment | null>(null);
const { formatNumber } = useRegionalFormat();

watch(model, (value) => {
  if (value !== adjustment.value?.value) adjustment.value = null;
});
</script>

<template>
  <DmsInputNumber v-model="model" :min="0" :max="5000" :step="50" @adjust="adjustment = $event" />
  <p v-if="adjustment" class="text-muted text-xs" role="status">
    {{ formatNumber(adjustment.typed) }} → {{ formatNumber(adjustment.value) }}
  </p>
</template>

Custom input in a real form

The example below uses RetentionInput.vue, a component of the DMS playground's frontend layer. It binds a numeric model to DmsInputNumber, reports control errors with useControlError, and asks the enclosing form to confirm a reduction before submitting.

Form Form
Connecting to the DMS preview…
Connecting DMS 0.19.4 4 options set

It keeps the number in whole weeks from 7 to 364 days:

  • Type 400 and leave the input: it sets 364, and its adjust handler explains the correction. The enclosing form still owns the pending value and its save state.
  • Type 10: the control reports an error, because 10 days is not a whole number of weeks.
  • Lower the loaded 91 days to 14 and save: a confirmation opens before the form submits.

See Custom component for rendering a module component, Frontend layer for installing its Vue code, and Component events for the surrounding event system.