@softwareproduction/dryvue
v2.0.1
Published
<p align="center"> <img src="public/logo.svg" title="Dryv" width="300"><br> <span style="font-size: 24px; font-weight: bold;">DRY Validation for Distributed Apps</span> </p>
Readme
Dryvue extends DryvJS by providing Vue 3 composables, plugins, and mixins. It seamlessly maps the core validation engine into Vue's reactivity system, yielding computed validation states, automated re-validation on updates, and frictionless integration with the v-model directive.
Like the core package, Dryvue works wonderfully as a standalone validation solution in Vue 3 or as the frontend runtime for full-stack rules generated by Dryv (.NET).
Table of Contents
- Installation
- Why Dryvue?
- Setup
- Composables
- Options API Support
- Complete Form Example
- Using with Dryv (C#/.NET)
- License
Installation
npm install @softwareproduction/dryvue(Requires @softwareproduction/dryvjs and vue ^3.5)
Why Dryvue?
- Native Vue Reactivity: Extends the DryvJS core so that validation states (
text,hasErrors,isDirty) are inherently reactive computed refs. - Effortless Component Binding: Provides dedicated composables like
useDryvValuePropto create reusable, validation-aware UI input components that interoperate directly withv-model. - Zero Boilerplate Validation Forms: Grouped messages and complex state tracking become trivial with structural Vue composables like
useDryvGroupSlot.
Setup
Plugin Registration
Register the Dryv plugin to bootstrap Vue-specific reactivity (Vue.reactive()) inside the validation engine:
import { createApp } from 'vue'
import { Dryv } from '@softwareproduction/dryvue'
import App from './App.vue'
createApp(App).use(Dryv).mount('#app')Static Rule Sets
You can register static rule sets globally, making them accessible by name across all your components:
import { DryvStaticRuleSets } from '@softwareproduction/dryvue'
import { personalDataRules } from './rules/personalData'
app.use(DryvStaticRuleSets, {
PersonalData: personalDataRules
})Composables
useDryv(model, ruleSetOrName, options?)
The main workhorse. It attaches a validation session to your reactive model and exposes the validation state.
<template>
<form @submit.prevent="validate">
<input v-model="validatable.name.value" />
<span class="error" v-if="validatable.name.hasErrors">
{{ validatable.name.text }}
</span>
<button type="submit" :disabled="!valid">Submit</button>
<button type="button" @click="revert" :disabled="!dirty">Revert Changes</button>
</form>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useDryv } from '@softwareproduction/dryvue'
const data = reactive({ name: '', email: '' })
// Use an inline rule set, or reference a static rule set by string ('PersonalData')
const { validatable, validate, valid, dirty, commit, revert } = useDryv(data, 'PersonalData')
</script>What useDryv Returns:
validatable: A reactive proxy reflecting your model structure, enriched with validation metadata (.value,.text,.hasErrors, etc.)valid: Computed boolean returningtrueif there are zero errors/warnings.dirty: Computed boolean indicating if data was altered since the last commit.validate(): Triggers full tree validation.clear(): Resets error messages but preserves dirty states.commit()&revert(): Managing dirty baselines.setValidationResult(): Apply a validation payload generated by the server directly into the UI state.
useDryvValueProp(emit, prop, event?)
Easily build customized input components that intelligently sync with Dryvue validators or plain v-models.
<!-- ValidatingInput.vue -->
<template>
<div>
<label>{{ label }} <span v-if="validatable.required">*</span></label>
<input v-model="validatable.value" />
<span v-if="validatable.hasErrors && !validatable.groupShown">
{{ validatable.text }}
</span>
</div>
</template>
<script setup lang="ts">
import { type DryvValidatable, useDryvValueProp } from '@softwareproduction/dryvue'
const props = defineProps<{
modelValue: string | DryvValidatable<any> | undefined
label: string
}>()
const emit = defineEmits(['update:modelValue'])
const validatable = useDryvValueProp(emit, () => props.modelValue)
</script>In your parent form, you simply drop it in:
<validating-input v-model="validatable.name" label="Name" />
<!-- Or use it independently! -->
<validating-input v-model="plainStringRef" label="Unvalidated Input" />useDryvGroupSlot(slotOrGroupNames?, groupNames?)
Aggregates related validation messages declared inside a child slot. Perfect for grouping cross-field validations (e.g., "Provide either email OR phone").
<!-- ValidationGroup.vue -->
<template>
<slot />
<div v-for="group in groups" :key="group.name">
<div v-for="{ type, texts } in group.results" :class="type">
<p v-for="text in texts">{{ text }}</p>
</div>
</div>
</template>
<script setup lang="ts">
import { useDryvGroupSlot } from '@softwareproduction/dryvue'
const props = defineProps<{ groups: string[] }>()
const groups = useDryvGroupSlot(props.groups)
</script>Mapped Validators
When a UI component's internal value differs from your model value (like storing strings but rendering Date objects in a Datepicker), use mapping:
<script setup lang="ts">
const datePickerValue = ref<Date | undefined>()
// Binds the validation state to a custom ref representation
const birthDateValidator = useMappedField('birthDate', datePickerValue)
</script>Options API Support
For classic Vue architecture, or class-based components (vue-facing-decorator), import dryvValidatableMixin:
import { dryvValidatableMixin } from '@softwareproduction/dryvue'
export default {
mixins: [dryvValidatableMixin<string>()],
props: { label: String }
// Exposes `this.validatable` automatically!
}Complete Form Example
<template>
<form @submit.prevent="onSubmit">
<validation-group :groups="['contact']">
<validating-input v-model="validatable.firstName" label="First Name" />
<validating-input v-model="validatable.email" label="Email" />
<validating-input v-model="validatable.phone" label="Phone" />
</validation-group>
<div class="actions">
<button type="submit" :disabled="!valid">Submit</button>
<button type="button" @click="revert" :disabled="!dirty">Reset Form</button>
</div>
</form>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
import { useDryv } from '@softwareproduction/dryvue'
const data = reactive({ firstName: '', email: '', phone: '' })
const { validatable, validate, valid, dirty, revert, setValidationResult } =
useDryv(data, 'PersonalData')
async function onSubmit() {
const result = await validate()
if (!result.success) return
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data)
})
const serverResult = await response.json()
if (!serverResult.success) {
// Applies server responses mapped to the corresponding fields
setValidationResult(serverResult)
}
}
</script>Using with Dryv (C#/.NET)
Dryvue is intended to gracefully execute validation rules provided by Dryv (.NET).
Because Dryvue completely encapsulates the dryvjs core, all integrations including runtime parameter fetching, generated TypeScript models, and auto-handling of dynamic backend async controllers work out of the box via the useDryv composable.
For a comprehensive guide on integrating your C# backend with Vue, including Server-Rendered Tag Helpers and Build-Time Code Generation strategies, see the Root README.
License
MIT
