Skip to content

Latest commit

 

History

History
323 lines (229 loc) · 11.5 KB

EntityAttributeNumberField.md

File metadata and controls

323 lines (229 loc) · 11.5 KB

EntityAttributeNumberField 🔢

The EntityAttributeNumberField component is a specialized attribute field dedicated to handling numeric attributes within an entity.

It is built on Quasar’s QInput component configured for numeric input and integrates with the lin-pulsar-core design system and scoped i18n to provide a consistent, localized, and customizable number field.


🎯 Purpose

  • Renders a numeric attribute using a number input
  • Automatically casts user input to a number
  • Synchronizes the numeric value with the entity model
  • Emits normalized entity updates on value changes
  • Supports scoped translations for labels and helper texts
  • Enables UI customization through the design system
  • Supports disabling the field via inputSettings.disable

⚙️ Props

The component uses the shared AttributeFieldProps interface with FieldNumberSettings.

Prop Type Required Description
instanceId string Yes Identifier used for contextual data
i18nScope string Yes I18n scope for localizing the component
uiNamespace string Yes Base UI design namespace for styling
definition LinPulsarAttributeConfiguration<FieldNumberSettings> Yes Attribute definition (name, type, input configuration)
entity Record<string, unknown> Yes Entity object containing the numeric attribute value
ignoreRules boolean No Indicates whether to bypass validation rules for this field (default: false)

AttributeFieldProps Interface

export interface AttributeFieldProps<T = Record<string, unknown>> extends CommonComponentProps {
  /** Identifier of the instance used for contextual data. */
  instanceId: string;

  /** Attribute configuration describing how the field should be rendered. */
  definition: LinPulsarAttributeConfiguration<T>;

  /** Entity object holding the attribute value. */
  entity: Record<string, unknown>;

  /**
   * Indicates whether to bypass validation rules for this field.
   * When set to true, validation rules will not be applied.
   * @default false
   */
  ignoreRules?: boolean;
}

FieldNumberSettings

export interface FieldNumberSettings extends FieldSettings {
  /** Minimum value allowed for the input. */
  min?: number;

  /** Maximum value allowed for the input. */
  max?: number;

  /** Indicates whether to bypass validation rules for this field. */
  ignoreRules?: boolean;

  /** When true, the input is rendered as non-interactive (disabled state). */
  disable?: boolean;
}

📤 Events

Event Payload Description
update:entity Record<string, unknown> Emitted when the numeric value changes and the entity is updated

Event Semantics

  • Emits a new entity object with the updated numeric value
  • Avoids mutating the original entity reference
  • Keeps state ownership in the parent component

🌍 Internationalization (i18n)

The component uses useScopedI18n to resolve translations for UI text elements.

Translation Scope

`${i18nScope}.fields.${definition.name}`;

Supported Translation Keys

Key Usage in UI
label Input label
hint Helper text
prefix Input prefix
suffix Input suffix

Fallback Behavior

translateOrDefault('', 'label');
  • Returns the translated value if the key exists
  • Falls back to the provided default value when missing
  • Prevents untranslated keys from appearing in the UI

🎨 UI Customization

UI customization is handled through the lin-pulsar-core design system via useUiDesign().

Namespace Resolution

`${uiNamespace}.${definition.name}`;

Applied Component

  • Quasar component: q-input
  • Input type: number
  • Props type: LinPulsarQInputProps

Example:

uiNamespace = 'entity-editor'

// Final UI namespace
entity-editor.age → q-input (type="number")

This allows precise styling and behavior customization per numeric attribute.


✅ Validation

The component implements automatic validation based on the attribute's inputSettings, the definition.required property, and the definition.hasValidations property.

Validation Rules

Validation rules are generated automatically using useQuasarRules:

const rules = computed(() => (!props.ignoreRules && !props.definition.inputSettings?.ignoreRules ? useQuasarRules(props.instanceId, props.definition, ['min', 'max'], localI18nScope) : []));

Validation Execution Order

The validation rules are executed in a specific order to ensure proper validation flow:

  1. Required validation (if applicable)

    • Depends on the definition.required property
    • If definition.required is true, this validation is automatically added as the first rule in the validation chain
    • Ensures that the field is not empty before proceeding to other validations
  2. Specific validation rules (in order)

    • The rules specified in the useQuasarRules parameters (currently ['min', 'max'] for number fields)
    • These rules are executed in the order specified in the array
    • Execute after the required validation (if present)
  3. Backend API validations (if applicable)

    • Depends on the definition.hasValidations property
    • If definition.hasValidations is true, backend validation rules are added
    • These validations are executed last, after all client-side validations pass
    • Used for server-side validation logic (e.g., checking uniqueness, business rules)

Supported Validation Types

Setting Description Example
required Marks the field as mandatory. Setting comes from the definition.required property. required: true
min Minimum value allowed min: 0
max Maximum value allowed max: 100
ignoreRules Bypass validation when set to true ignoreRules: true

Validation Behavior

  • If ignoreRules (prop) and definition.inputSettings.ignoreRules are both false or undefined, validation rules are applied
  • If ignoreRules (prop) or definition.inputSettings.ignoreRules est true, no validation is performed
  • Validation messages are automatically translated using the instance's i18n scope

🧭 Nested Attributes

The attribute name supports dot notation to target values located inside sub-objects of the entity (e.g. extraParameters.login):

  • The initial value is read from the nested path (getNestedValue from lin-pulsar-core)
  • Updates rewrite only the targeted nested property, preserving the rest of the entity structure (setNestedValue from lin-pulsar-core)
  • Missing intermediate objects are created when updating; intermediate values that are not objects are replaced by objects
  • The update:entity event still emits the complete updated entity object

🔁 Data Flow

  1. Initial value is read from the entity at the path definition.name (dot notation supported)
  2. User enters a numeric value
  3. v-model.number casts the input to a number
  4. localValue updates reactively
  5. updateValue() emits update:entity with a new entity object
QInput (number) → localValue → updateValue → update:entity

🧠 Internal State Management

const localValue = ref(getNestedValue(props.entity, props.definition.name) ?? null);
  • Maintains a local reactive copy of the attribute value
  • Supports null when the numeric attribute is initially undefined
  • A watch on () => getNestedValue(props.entity, props.definition.name) keeps localValue in sync when the parent updates the entity — it only triggers when the specific attribute value changes, not when other fields of the entity change

💡 Usage Example

<script setup lang="ts">
import EntityAttributeNumberField from '@/components/field/EntityAttributeNumberField.vue';

const entity = reactive({
  age: 30,
});

const definition = {
  name: 'age',
  input: 'Number',
  type: 'Integer',
  required: true,
  hasValidations: true,
  inputSettings: {
    min: 18,
    max: 120,
    ignoreRules: false,
  },
};

const onUpdateEntity = (updatedEntity: Record<string, unknown>) => {
  Object.assign(entity, updatedEntity);
};
</script>

<template>
  <EntityAttributeNumberField
    ui-namespace="entity-editor"
    instance-id="user-1"
    :definition="definition"
    :entity="entity"
    @update:entity="onUpdateEntity"
  />
</template>

✅ Advantages

  • Type-aware input: Ensures values are treated as numbers
  • Immutable updates: Emits new entity objects instead of mutating state
  • Localized UI: Fully supports scoped translations
  • Customizable: Integrates with the UI design system
  • Reusable: Works with any numeric attribute definition
  • Framework-aligned: Built with Vue 3 Composition API and Quasar best practices

🧪 Testing Considerations

  • Verify numeric casting via v-model.number
  • Assert update:entity emission on value change
  • Mock useScopedI18n to control translated output
  • Shallow mount the component to isolate behavior from UI rendering
  • Verify that localValue is updated when the entity value at definition.name changes
  • Verify that localValue is not overwritten when only other entity attributes change
  • Verify the input is rendered as disabled when definition.inputSettings.disable is true

📌 Notes

  • The component assumes definition.input === 'Number'
  • Uses FieldNumberSettings type for inputSettings, which supports min, max, ignoreRules, and disable
  • Validation is handled internally using useQuasarRules and can be configured via inputSettings
  • The field is rendered as non-interactive when definition.inputSettings.disable is true
  • Missing translations gracefully fall back to default values
  • Intended for use via EntityAttributeField in most scenarios

🏗️ Architecture Summary

EntityAttributeNumberField is a leaf component in the entity attribute rendering system.

Its sole responsibilities are:

  • Rendering a numeric input
  • Managing local numeric state
  • Emitting normalized entity updates

All higher-level concerns such as validation, layout, and schema orchestration are delegated to parent components, ensuring a clean and scalable architecture.