Skip to content

Exemple complet : workflow Builder + Viewer

Cet exemple montre deux pages :

  • une page admin pour construire le formulaire ;
  • une page publique pour afficher le formulaire.

1. Composable de stockage partagé

ts
// app/composables/useLocalDynamicFormRepository.ts
import type { FormKitSchemaDefinition } from '@formkit/core'

export type LocalDynamicForm = {
  id: string
  schema: FormKitSchemaDefinition[]
  settings: Record<string, unknown>
  updatedAt: string
}

const STORAGE_PREFIX = 'qform-demo:dynamic-form:'

export function useLocalDynamicFormRepository() {
  function getStorageKey(id: string) {
    return `${STORAGE_PREFIX}${id}`
  }

  function get(id: string): LocalDynamicForm | null {
    if (!import.meta.client) return null

    const raw = localStorage.getItem(getStorageKey(id))
    if (!raw) return null

    return JSON.parse(raw) as LocalDynamicForm
  }

  function save(document: LocalDynamicForm) {
    if (!import.meta.client) return document

    localStorage.setItem(getStorageKey(document.id), JSON.stringify(document))
    return document
  }

  return {
    get,
    save,
  }
}

2. Page admin builder

vue
<!-- app/pages/admin/forms/contact.vue -->
<script setup lang="ts">
import type { FormKitSchemaDefinition } from '@formkit/core'
import type { LocalDynamicForm } from '~/composables/useLocalDynamicFormRepository'

type FormBuilderValues = Record<string, unknown>

const repository = useLocalDynamicFormRepository()

const formId = 'contact'
const schema = ref<FormKitSchemaDefinition[]>([])
const values = ref<FormBuilderValues>({})
const saved = ref(false)

onMounted(() => {
  const document = repository.get(formId)
  schema.value = document?.schema || []
})

function handleSave(payload: {
  schema: FormKitSchemaDefinition[]
  values: FormBuilderValues
  settings: Record<string, unknown>
}) {
  const document: LocalDynamicForm = {
    id: formId,
    schema: payload.schema,
    settings: payload.settings,
    updatedAt: new Date().toISOString(),
  }

  repository.save(document)
  saved.value = true
}
</script>

<template>
  <QPage padding>
    <QBanner v-if="saved" rounded class="bg-positive text-white q-mb-md">
      Formulaire sauvegardé. Ouvre maintenant `/forms/contact`.
    </QBanner>

    <FormBuilder
      builder-id="admin-contact-form"
      v-model:schema="schema"
      v-model:values="values"
      title="Administration du formulaire contact"
      autosave
      @save="handleSave"
    />
  </QPage>
</template>

3. Page publique viewer

vue
<!-- app/pages/forms/contact.vue -->
<script setup lang="ts">
import type { FormKitSchemaDefinition } from '@formkit/core'

const repository = useLocalDynamicFormRepository()
const values = ref<Record<string, unknown>>({})
const schema = ref<FormKitSchemaDefinition[]>([])
const submitted = ref(false)

onMounted(() => {
  const document = repository.get('contact')
  schema.value = document?.schema || []
})

function handleSubmit(data: Record<string, unknown>) {
  console.log('Soumission publique', data)
  submitted.value = true
}
</script>

<template>
  <QPage padding>
    <QCard flat bordered class="q-pa-lg">
      <QCardSection>
        <div class="text-h5">Contact</div>
        <div class="text-body2 text-grey-7">
          Formulaire généré dynamiquement depuis le builder.
        </div>
      </QCardSection>

      <QBanner v-if="submitted" rounded class="bg-positive text-white q-mb-md">
        Formulaire envoyé.
      </QBanner>

      <QBanner v-if="!schema.length" rounded class="bg-warning text-dark">
        Aucun schéma sauvegardé. Construis d’abord le formulaire dans `/admin/forms/contact`.
      </QBanner>

      <FormViewer
        v-else
        v-model="values"
        :form-fields="schema"
        @submit="handleSubmit"
      />
    </QCard>
  </QPage>
</template>

Passage en production

Pour un usage réel, remplace localStorage par :

txt
service NFZ / Feathers
MongoDB
validation Zod côté serveur
RBAC admin
journalisation des versions de schéma

QForm Builder — couche Nuxt 4 / Quasar / FormKit réutilisable.