FormView
Saisie et soumission de données, validation, champs typés.
Description
Le composant FormView sert à créer des formulaires dynamiques dotés de types de champs variés, de règles de validation et d'actions de soumission. Il prend en charge un large éventail de saisies : texte, e-mail, mot de passe, nombre, date, liste déroulante, envoi de fichiers, enregistrement audio et vidéo, coordonnées GPS, et bien d'autres.
Un formulaire est soumis exactement à l'URL de service dont la réponse l'a renvoyé. Le client conserve cette URL, contrôle qu'elle reste dans le périmètre de l'URL de base déclarée par le service, et elle n'est pas configurable depuis la charge utile du formulaire. content.submit ne pilote que l'apparence du bouton et la méthode HTTP.
Description des champs
idstringrequistypestringrequis"Form"contentFormContentrequiscontent.titlestringrequiscontent.introstringoptionnelcontent.submitSubmitActionoptionnelcontent.submit.textstringrequis*content.submit.methodHttpMethodoptionnel"POST")content.submit.confirmMessagestringoptionnelcontent.fieldsFormField[]requiscontent.fields[].fieldTypestringrequis"text", "email", "password", "number", "date", "select", "photo", "file", "audio", "video", "gps", "pluscode", "hidden", "textarea", "phone", "url", "checkbox", "separator"content.fields[].fieldIdstringrequiscontent.fields[].fieldLabelstringrequiscontent.fields[].valueunknownoptionnelcontent.fields[].requiredbooleanoptionnelcontent.fields[].placeholderstringoptionnelcontent.fields[].helpTextstringoptionnelcontent.fields[].disabledbooleanoptionnelcontent.fields[].readonlybooleanoptionnelcontent.fields[].minnumberoptionnelcontent.fields[].maxnumberoptionnelcontent.fields[].minLengthnumberoptionnelcontent.fields[].maxLengthnumberoptionnelcontent.fields[].patternRegExpoptionnelcontent.fields[].optionsArray<{label: string, value: unknown, selected?: boolean}>optionnelcontent.fields[].acceptstring[]optionnelcontent.fields[].livebooleanoptionnelcontent.fields[].minDatestringoptionnelcontent.fields[].maxDatestringoptionnelcontent.fields[].multiplebooleanoptionnelcontent.fields[].maxCountnumberoptionnelmultiple vaut truecontent.fields[].maxDurationnumberoptionnelvideocontent.fields[].minDurationnumberoptionnelcontent.fields[].sourcestringoptionnel"record", "library" ou "both" (par défaut)content.fields[].qualitystringoptionnel"low", "medium" (par défaut) ou "high"content.fields[].maxSizenumberoptionnelprocessIdstringoptionnelmetadataobjectoptionnelMéthodes
setIntro(intro: string)→ thisintro— Texte d'introduction
setNote(note: string)→ thissetIntro(). Conservé pour compatibilité ascendantenote— Texte de la note
belongsToProcess(processId, options?)→ thisprocessId— Identifiant du processusoptions— Options du processus (processName, currentStep, totalSteps, stepName, canGoBack, canSkip)
addField(fieldType, fieldId, fieldLabel, params?)→ thisfieldType— Nature du champfieldId— Identifiant unique du champfieldLabel— Libellé affichéparams— Paramètres du champ
submitButton(text, method?, confirmMessage?)→ thistext— Libellé du boutonmethod— Méthode HTTP (par défaut : POST)confirmMessage— Confirmation facultative
updateButton(text, confirmMessage?)→ thistext— Libellé du boutonconfirmMessage— Confirmation facultative
deleteButton(text, confirmMessage?)→ thistext— Libellé du boutonconfirmMessage— Message de confirmation
addTextField(fieldId, fieldLabel, isRequired?, maxLength?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requismaxLength— Longueur maximale
addEmailField(fieldId, fieldLabel, isRequired?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requis
addPasswordField(fieldId, fieldLabel, minLength?)→ thisfieldId— Identifiant du champfieldLabel— LibelléminLength— Longueur minimale (par défaut : 8)
addNumberField(fieldId, fieldLabel, isRequired?, minVal?, maxVal?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisminVal— Valeur minimalemaxVal— Valeur maximale
addDateField(fieldId, fieldLabel, isRequired?, minDate?, maxDate?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisminDate— Date minimale (AAAA-MM-JJ)maxDate— Date maximale (AAAA-MM-JJ)
addSelectField(fieldId, fieldLabel, isRequired?, options)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisoptions— Tableau de {label, value}
addPhotoField(fieldId, fieldLabel, isRequired?, formats?, live?, options?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisformats— Formats acceptés (par défaut : ['jpeg', 'png'])live— Mise à jour continueoptions—{multiple, maxCount, source}
addFileField(fieldId, fieldLabel, isRequired?, formats, options?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisformats— Tableau de types MIMEoptions—{multiple, maxCount}
addAudioField(fieldId, fieldLabel, isRequired?, options?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisoptions—{maxDuration, minDuration, source, multiple, maxCount, maxSize, formats}
addVideoField(fieldId, fieldLabel, isRequired?, options)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisoptions—{maxDuration (required), minDuration, quality, source, multiple, maxCount, maxSize, formats}
addGPSField(fieldId, fieldLabel, isRequired?, liveData?, config?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisliveData— Mise à jour continueconfig— Réglages GPS (altitude, precision)
addPlusCodeField(fieldId, fieldLabel, isRequired?, liveData?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisliveData— Mise à jour continue
addHiddenField(fieldId, fieldLabel, value)→ thisfieldId— Identifiant du champfieldLabel— Libellévalue— Valeur du champ
addTextAreaField(fieldId, fieldLabel, isRequired?, minLength?, maxLength?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requisminLength— Longueur minimalemaxLength— Longueur maximale
addPhoneField(fieldId, fieldLabel, isRequired?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requis
addURLField(fieldId, fieldLabel, isRequired?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requis
addCheckboxField(fieldId, fieldLabel, isRequired?)→ thisfieldId— Identifiant du champfieldLabel— LibelléisRequired— Indicateur de champ requis
addSeparator(fieldId?)→ thisfieldId— Identifiant facultatif du champ (généré automatiquement s'il est omis)
injectData(data)→ Result<void, string[]>data— Objet associant des identifiants de champs à leurs valeurs
setFieldValue(fieldId, value)→ thisfieldId— Identifiant du champvalue— Valeur à affecter
validateFormData(formData)→ ValidationResultformData— Objet contenant les données du formulaire
getField(fieldId)→ FormField | undefinedfieldId— Identifiant du champ
removeField(fieldId)→ Result<void, string>fieldId— Identifiant du champ
updateField(fieldId, updates)→ Result<void, string>fieldId— Identifiant du champupdates— Modifications partielles du champ
getFields()→ FormField[]getFieldCount(excludeSeparators?)→ numberexcludeSeparators— Exclure les séparateurs (par défaut : false)
hasRequiredFields()→ booleangetRequiredFields()→ string[]serve()→ Record<string, unknown>toJSON()→ Record<string, unknown>setState(key, value)→ voidkey— Clé d'étatvalue— Valeur d'état
getState(key)→ unknownkey— Clé d'état
setNext(url)→ thisurl— URL de la vue suivante
setPrev(url)→ thisurl— URL de la vue précédente
setProcess(processId, context?)→ thisprocessId— Identifiant du processuscontext— Contexte du processus
Exemples de code JavaScript
Formulaire simple
1import { YeriaApp } from '@numerum-tech/yeriasdk';
2
3const yeriaApp = new YeriaApp({ appId: 'my-app' });
4
5const form = yeriaApp
6 .createFormView('user-registration', 'User Registration')
7 .setIntro('Please fill in all required fields')
8 .addTextField('firstName', 'First Name', true, 50)
9 .addTextField('lastName', 'Last Name', true, 50)
10 .addEmailField('email', 'Email Address', true)
11 .addPhoneField('phone', 'Phone Number', false)
12 .submitButton('Create Account', 'POST');
13
14const response = yeriaApp.serve(form);Formulaire avec liste déroulante
1const form = yeriaApp
2 .createFormView('survey', 'Customer Survey')
3 .addSelectField('country', 'Country', true, [
4 { label: 'France', value: 'FR' },
5 { label: 'Canada', value: 'CA' },
6 { label: 'Belgium', value: 'BE' }
7 ])
8 .addSelectField('rating', 'Rating', true, [
9 { label: 'Excellent', value: 5 },
10 { label: 'Good', value: 4 },
11 { label: 'Average', value: 3 },
12 { label: 'Poor', value: 2 },
13 { label: 'Very Poor', value: 1 }
14 ])
15 .submitButton('Submit Survey', 'POST');Formulaire avec envoi de fichier
1const form = yeriaApp
2 .createFormView('document-upload', 'Upload Document')
3 .addFileField('document', 'Document', true, [
4 'application/pdf',
5 'image/jpeg',
6 'image/png'
7 ])
8 .addPhotoField('photo', 'Profile Photo', false, ['jpeg', 'png'], true)
9 .submitButton('Upload', 'POST');Formulaire avec enregistrement audio et vidéo
1const form = yeriaApp
2 .createFormView('incident-report', 'Report an Incident')
3 .addTextField('summary', 'What happened', true, 200)
4 .addAudioField('statement', 'Spoken statement', false, {
5 maxDuration: 120, // seconds
6 minDuration: 2, // rejects an accidental tap-and-release
7 source: 'record' // microphone only — no picking an old file
8 })
9 .addVideoField('evidence', 'Video evidence', false, {
10 maxDuration: 30, // REQUIRED — this is what bounds the upload size
11 quality: 'low', // ~480p; ~7 MB per minute
12 source: 'both', // record now, or pick from the gallery
13 maxSize: 15 * 1024 * 1024
14 })
15 .submitButton('Send report', 'POST');Comment l'enregistrement parvient au fournisseur
Il n'existe pas d'endpoint d'envoi séparé. Le fichier capturé voyage dans la soumission ordinaire du formulaire : le client émet un multipart/form-data vers la même URL de service validée que celle ayant renvoyé le formulaire, l'enregistrement constituant une partie fichier nommée d'après son fieldId (fieldId_0, fieldId_1, … lorsque multiple est actif), et chacun des autres champs une partie ordinaire.
Le dimensionnement incombe au fournisseur. C'est maxDuration × quality qui borne la charge utile :
quality | Résolution / débit approximatifs | Par minute |
|---|---|---|
low | 480p / ~1 Mbps | ~7 Mo |
medium (par défaut) | 720p / ~2,5 Mbps | ~19 Mo |
high | 1080p / ~4 Mbps | ~30 Mo |
L'audio pèse bien moins lourd : à l'encodage retenu par défaut, la voix tourne autour de 0,5 Mo par minute. Ces ordres de grandeur correspondent à la production habituelle des encodeurs mobiles, sans constituer une garantie : réglez la limite de corps de requête du service au-dessus de ce qu'impliquent les contraintes déclarées, avec de la marge. Servez-vous de maxSize pour que le client écarte un fichier trop volumineux avant même d'entamer l'envoi, plutôt que d'échouer en cours de requête.
maxDuration est obligatoire sur video : la construction de la vue lève une erreur en son absence.
Ce que produit réellement une capture
accept déclare ce que le champ accepte. Ce que le moteur de rendu capture est plus restreint, et un fournisseur qui stocke ou rediffuse le fichier doit en tenir compte :
| Capturé sous forme de | |
|---|---|
audio | AAC, mono, ~64 kbps, dans un conteneur MP4 (.m4a) |
video | H.264 dans un conteneur MP4 (.mp4), à la résolution indiquée par quality |
| orientation | Celle dans laquelle le téléphone était tenu. Portrait comme paysage sont possibles |
L'orientation vidéo circule sous forme de métadonnée de rotation MP4, non de pixels déjà pivotés. Les images stockées gardent l'orientation du capteur et le conteneur transporte la rotation à appliquer ; c'est ainsi que tout appareil photo de téléphone écrit une vidéo. Un lecteur qui respecte cette métadonnée affiche la séquence à l'endroit, celui qui l'ignore l'affiche couchée. Si le service réencode, génère une vignette ou diffuse le clip, il lui revient de propager cette rotation : l'oublier explique presque toujours qu'une séquence correcte sur le téléphone se retrouve ensuite de travers.
Un fichier choisi dans la bibliothèque plutôt qu'enregistré peut être tout ce qu'autorise accept : n'importe quelle orientation, n'importe quelle durée.
Revalidez tout côté serveur
Le moteur de rendu fait respecter maxDuration en arrêtant l'enregistreur, et refuse une capture qui contrevient à minDuration ou à maxSize. Rien de tout cela ne constitue une garantie :
minDurationn'est vérifié que sur une prise enregistrée. Un fichier choisi dans la bibliothèque n'a pas de durée mesurée : la règle est écartée au lieu d'être appliquée sans élément probant.- Chacune de ces contraintes réside dans une charge utile que le client peut très bien ignorer.
Considérez les contraintes déclarées comme le contrat que vous avez demandé, et recontrôlez durée, taille et format à réception.
Formulaire avec champ GPS
1const form = yeriaApp
2 .createFormView('location-form', 'Record Location')
3 .addGPSField('location', 'Your Location', true, true, {
4 altitude: true,
5 precision: true
6 })
7 .addPlusCodeField('pluscode', 'Plus Code', false, true)
8 .submitButton('Save Location', 'POST');Formulaire avec champ date
1const form = yeriaApp
2 .createFormView('appointment', 'Schedule Appointment')
3 .addDateField('appointmentDate', 'Appointment Date', true, '2025-01-01', '2025-12-31')
4 .addTextField('notes', 'Additional Notes', false)
5 .submitButton('Book Appointment', 'POST');Formulaire pré-rempli
1const form = yeriaApp
2 .createFormView('edit-profile', 'Edit Profile')
3 .addTextField('name', 'Full Name', true)
4 .addEmailField('email', 'Email', true)
5 .addSelectField('country', 'Country', false, [
6 { label: 'France', value: 'FR' },
7 { label: 'Canada', value: 'CA' }
8 ]);
9
10// Inject pre-filled data
11form.injectData({
12 name: 'John Doe',
13 email: 'john@example.com',
14 country: 'FR' // Automatically selects the option with value 'FR'
15});
16
17form.submitButton('Update Profile', 'PUT');Formulaire avec validation
1const form = yeriaApp
2 .createFormView('registration', 'Register')
3 .addPasswordField('password', 'Password', true, 8)
4 .addTextField('username', 'Username', true)
5 .addField('text', 'username', 'Username', {
6 required: true,
7 minLength: 3,
8 maxLength: 20,
9 pattern: /^[a-zA-Z0-9_]+$/
10 })
11 .submitButton('Register', 'POST');Formulaire avec séparateurs
1const form = yeriaApp
2 .createFormView('registration', 'User Registration')
3 .addTextField('firstName', 'First Name', true)
4 .addTextField('lastName', 'Last Name', true)
5 .addSeparator() // Visual separator between sections
6 .addEmailField('email', 'Email', true)
7 .addPhoneField('phone', 'Phone', false)
8 .addSeparator('billing-separator') // With explicit ID
9 .addTextField('address', 'Address', true)
10 .addTextField('city', 'City', true)
11 .submitButton('Register', 'POST');Note : les séparateurs sont purement visuels et ne collectent aucune donnée. Ils sont exclus du contrôle « au moins un champ » : un formulaire doit donc comporter au moins un champ qui ne soit pas un séparateur.
Formulaire dans un processus
1const form = yeriaApp
2 .createFormView('step-1', 'Personal Information')
3 .belongsToProcess('onboarding', {
4 processName: 'User Onboarding',
5 currentStep: 1,
6 totalSteps: 3,
7 stepName: 'Personal Info',
8 canGoBack: false,
9 canSkip: false
10 })
11 .addTextField('firstName', 'First Name', true)
12 .addTextField('lastName', 'Last Name', true)
13 .submitButton('Next', 'POST');Exemple JSON complet
1{
2 "id": "user-registration",
3 "type": "Form",
4 "content": {
5 "title": "User Registration",
6 "intro": "Please fill in your information to create an account",
7 "submit": {
8 "text": "Register",
9 "method": "POST",
10 "confirmMessage": "Are you sure you want to submit?"
11 },
12 "fields": [
13 {
14 "fieldType": "text",
15 "fieldId": "firstName",
16 "fieldLabel": "First Name",
17 "required": true,
18 "placeholder": "Enter your first name",
19 "maxLength": 50,
20 "helpText": "Your legal first name"
21 },
22 {
23 "fieldType": "text",
24 "fieldId": "lastName",
25 "fieldLabel": "Last Name",
26 "required": true,
27 "placeholder": "Enter your last name",
28 "maxLength": 50
29 },
30 {
31 "fieldType": "separator",
32 "fieldId": "separator-1234567890-1234",
33 "fieldLabel": ""
34 },
35 {
36 "fieldType": "email",
37 "fieldId": "email",
38 "fieldLabel": "Email Address",
39 "required": true,
40 "placeholder": "you@example.com",
41 "pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
42 },
43 {
44 "fieldType": "phone",
45 "fieldId": "phone",
46 "fieldLabel": "Phone Number",
47 "required": false,
48 "placeholder": "+1234567890"
49 },
50 {
51 "fieldType": "select",
52 "fieldId": "country",
53 "fieldLabel": "Country",
54 "required": true,
55 "options": [
56 {
57 "label": "France",
58 "value": "FR",
59 "selected": false
60 },
61 {
62 "label": "United States",
63 "value": "US",
64 "selected": true
65 },
66 {
67 "label": "Canada",
68 "value": "CA",
69 "selected": false
70 }
71 ]
72 },
73 {
74 "fieldType": "date",
75 "fieldId": "birthDate",
76 "fieldLabel": "Date of Birth",
77 "required": true,
78 "minDate": "1900-01-01",
79 "maxDate": "2010-12-31"
80 },
81 {
82 "fieldType": "number",
83 "fieldId": "age",
84 "fieldLabel": "Age",
85 "required": false,
86 "min": 18,
87 "max": 120,
88 "value": 25
89 },
90 {
91 "fieldType": "audio",
92 "fieldId": "statement",
93 "fieldLabel": "Spoken statement",
94 "required": false,
95 "accept": ["audio/mp4", "audio/mpeg", "audio/wav", "audio/aac"],
96 "maxDuration": 120,
97 "minDuration": 2,
98 "source": "record"
99 },
100 {
101 "fieldType": "video",
102 "fieldId": "evidence",
103 "fieldLabel": "Video evidence",
104 "required": false,
105 "accept": ["video/mp4", "video/quicktime", "video/webm"],
106 "maxDuration": 30,
107 "quality": "low",
108 "source": "both",
109 "maxSize": 15728640
110 }
111 ]
112 },
113 "metadata": {
114 "version": "1.0.0",
115 "createdAt": "2025-01-28T10:00:00.000Z"
116 }
117}