Aller au contenu

Modèles d'attributs : définitions des champs du formulaire

Structure d'un schéma

{
    "type":"object",
    "title":"Demande d'adhésion",
    "description":"Demande d'adhésion",
    "$schema":"http://json-schema.org/draft-07/schema",
    "properties":{
        attributs...
    }
}

Description d'un attribut

Nom du champ
"field": {
  ...
  "title": "Mon champ",
  ...
}
Bloc plié par défaut

La propriété collapsable n'a effet que sur les objets (pas d'effet sur l'objet racine).

S'il y a un cache pour l'état du formulaire, cette directive n'est pas prioritaire sauf s'il y a des champs requis (un cache composant est mis en place pour la qualification d'entrée).

"field": {
  ...
  "type": "object",
  "collapsable": true
  ...
}
Champ requis

Doit être mentionné dans son parent

{
  "title": "MyBloc",
  "type":"object",
  "required": [ "field" ],
  "properties": {
    "field": { ... }
  }
}
Position du champ à l'affichage
"field": {
  ...
  "position": 1,
  ...
}

Anomalie répertoriée #33043 : comportement inattendu avec les dizaines (ex : 20 est interprété comme 2)

Champ plus petit
"field": { 
  ...
  "view": {
    "size": "small-block"
  },
  ...
}
Symbole en suffixe du champ

Uniquement pour un champ de type 'number'

{
  ...,
  "view": {
    "suffix": "€"
  },
  ...
}
Comportement pour la recherche
  • partial : valeur partielle (%search%)

  • exact: valeur exacte

  • date: pour les dates

  • numeric: pour les nombres

"field": {
  ...
  "filterNode": "partial",
  ...
}
Validation de format (front)

Expressions régulières utilisées

À noter que regex est aussi utilisé quand type est string coté validation back

{
  ...,
  "pattern": "^\\d*\\,\\?\\d*$",
  ...
}
Ajout d'un mask pour un champ
  • Pour appliquer un mask de formatage dans un champ, on opte pour la syntaxe indiquée à droite

  • Pour plus de documentation sur les formats et les paramètres du mask : https://github.com/JsDaddy/ngx-mask#examples

"field": {
  ...
  "view": {
     ...,
     "mask": {
       "mask": "00 00 00 00 00 00",
       "showMaskTyped": true
     },
  },
...
}
Cacher un champ

Cas d'usage : Un champ user_manager récupère un objet d'un référentiel :

  • displayName : Jean DUPONT
  • identifier : j.dupont

Le champ affiche le displayName mais doit stocker son identifiant dans un autre champ user_manager_id (mais qui est technique et pas intéressant pour l'utilisateur).

"field": {
  ...
  "view": {
    ...,
    "hidden": true,
  },
...
}
Afficher une icone de champ auto-évalué

image

Cas d'usage : Un champ est rempli coté serveur et n'a pas pour vocation d'interagir avec l'utilisateur

Il est préférable dans ce cas de le mettre en readOnly.

"field": {
  ...
  "view": {
    ...,
    "iconInfo": "selfGenerated",
  },
...
}
Valeur par défaut

Permet de proposer une valeur par défaut pour le champ en cours à la création d'un objet.

La valeur dépendra de type de champ (property / field).

"field": {
 ...
 "default": "defaul value",
 ...
}

Dictionnaire d'attributs

Créer un schéma de dictionnaire d'attributs permet de réutiliser les attributs pour d'autres schémas.

Structure d'un schéma dictionnaire
{
  "$schema": "http://json-schema.org/draft-07/schema",
  "title": "Dictionnaire d'attributs",
  "type": "object",
  "$defs": {
    "contact": {
      ...
    }
  }
}
Appel au dictionnaire
"myBloc": {
  "title": "Mon bloc",
  "type": "object",
  "properties": {
    "contact": {
      "$ref": "https://<{url.host}>/schemas/<strval(@schema_dictionary->id)>/content#/\$defs/contact"
    },
  }
}

Conditionner le caractère obligatoire de deux champs : anyOf

Cas d'usage : Un des deux champs email ou phone doit être remplis dans un formulaire

"myBloc": {
  "title": "Mon bloc",
  "type": "object",
  "anyOf": [
    {
      "required": [
        "phone"
      ]
    },
    {
      "required": [
        "email"
      ]
    }
  ],
  "properties": {
    "phone": {
      ...
    },
    "email": {
      ...
    }
  }
}

Conditionner l'apparition d'un champ par rapport à un autre

Deux syntaxes sont supportées pour le moment :

Cas simple

Cas d'usage le plus simple avec une seule condition :

  • Je sélectionne vegetarien pour le champ regime, le champ legumes apparaît
"properties":{
  "regime":{
    "title":"Régime",
    "type":"string",
    "enum":[
          "vegetarien",
          "carnivore"
    ],
    "view":{
      "type":"input-radio"
    },
    "filterNode":"exact"
  }
},
"if": {
  "properties": { "regime": { "const": "vegetarien" } }
},
"then": {
  "properties": {
    "legumes":{
      "title":"Légumes",
      "type":"array",
      "items": {
          "type":"string",
          "enum":[
            "Concombre",
            "Carotte"
          ]
      },
      "view":{
        "type":"input-checkbox"
      },
      "filterNode":"exact"
    }
  }
}

Cas complexe

Cas d'usage :

  • Je sélectionne vegetarien pour le champ regime, le champ legumes apparaît
  • Je sélectionne carnivore pour le champ regime, le champ viandes apparaît

Ce qui nous donne :

"properties": {
  "regime": {
    "title":"Régime",
    "type":"string",
    "enum": [
      "vegetarien", 
      "carnivore"
    ],
    "view": {
      "type":"input-radio"
    },
    "filterNode":"exact"
  }
}
"allOf" : [
  {
    "if": {
        "properties": { "regime": { "const": "vegetarien" } }
    },
    "then": {
      "properties": {
        "legumes":{
          "title":"Légumes",
          "type":"array",
          "items": { 
            "type":"string",
            "enum":[
              "Concombre",
              "Carotte"
            ]
          },
          "view":{
            "type":"input-checkbox"
          },
          "filterNode":"exact"
        }
      }
    }
  },
  {
    "if": {
      "properties": { "regime": { "const": "carnivore" } }
    },
    "then": {
      "properties": { 
        "viandes":{
          "title":"Viandes",
          "type":"array",
          "items": { 
            "type":"string",
            "enum":[
              "Poulet",
              "Porc"
            ]
          },
          "view":{
            "type":"input-checkbox"
          },
          "filterNode":"exact"
        }
      }
    }
  }
]

Référence externe

Si le champ est issu d'un référentiel externe, il faut décrire le service auquel faire appel ainsi que la stratégie d'écriture à mettre en place.

Ci-dessous un exemple d'un code postal issu d'un référentiel externe :

{
  "attributesModel_user": {
    "content": {
      "adresse": {
        "codePostal": {
          "title": "Code postal",
          "type": "string",
          "view": {
            "type": "input-autocomplete"
          }
        },
        "ville": {
          "title": "Ville",
          "type": "string",
          "view": {
            "type": "input"
          }
        }
      }
    },
    "externals": [
      {
        "service": "ban",
        "bindings": [
          {
            "attribute": "adresse.codePostal",
            "initOnField": true,
            "serviceAttribute": "code_postal",
            "searchProperty": "code_postal",
            "searchType": "start",
            "limit": 15,
            "sortField": "code_postal",
            "sort": "asc",
            "isValidationKey": true,
            "viewFormat": "{code_postal} - {nom_commune}"
          },
          {
            "attribute": "adresse.ville",
            "serviceAttribute": "nom_commune",
            "isValidationKey": false
          }
        ]
      }
    ]
  }
}

Autres options (à mettre au même niveau que initOnField) :

Attribut Description
loadOnInit Charge en amont tout le référentiel dans votre champ
autoSelectFirstItem Sélectionne automatiquement la première valeur du référentiel chargé (loadOnInit requis)
queryParams Permet de passer des filtres dynamiques au service. Exemple : identifier=@me.groups.identifier appliquera un filtre basé sur le contexte utilisateur (@me).

Référence interne

Si le champ est issu d'un référentiel interne, il faut décrire le service auquel faire appel à la racine du modèle.

ex :

  1. Le champ nom_complet défini dans le bloc informations et le champ identifiant_utilisateur dans le bloc autres
  2. Le champ nick_name défini dans le bloc informations et le champ dans les attributs nickName dans l'attribut rootSchemaUser et le block information
{
  "attributesModel_user": {
    "internals": [
      {
        "service": "user",
        "url": "users",
        "bindings": [
          {
            "attribute": "informations.nom_complet",
            "initOnField": true,
            "serviceAttribute": "displayName",
            "searchProperty": "displayName",
            "searchType": "partial",
            "limit": 15,
            "sortField": "displayName",
            "sort": "asc",
            "viewFormat": "{displayName}"
          },
          {
            "attribute": "informations.identifiant_utilisateur",
            "serviceAttribute": "identifier",
            "searchProperty": "identifier",
            "isValidationKey": true
          },
          {
            "attribute": "autres.nick_name",
            "serviceAttribute": "attributes:rootSchemaUser.information.nickName"
          }
        ]
      }
    ],
    "content": {
    }
  }
}

Autres options (à mettre au même niveau que initOnField) :

Attribut Description
loadOnInit Charge en amont tout le référentiel dans votre champ.
autoSelectFirstItem Sélectionne automatiquement la première valeur du référentiel chargé (loadOnInit requis).
queryParams Permet de passer des filtres dynamiques au service. Exemple : identifier=@me.groups.identifier appliquera un filtre basé sur le contexte utilisateur (@me).
allowEmptyResults Définit si une valeur non trouvée dans le référentiel interne est autorisée. Si false, la publication est refusée et une erreur NoRecordFound est levée. Doit être positionné sur le champ ayant isValidationKey: true.

🆕 À partir de la version 4.0 : l’attribut allowEmptyResults a été ajouté.

Exemple avec le filtre @me dans une référence interne

Ici, on utilise le filtre @me directement dans queryParams pour restreindre le référentiel interne aux groupes de l’utilisateur connecté.

{
  "attributesModel_user": {
    "internals": [
      {
        "service": "group",
        "url": "groups",
        "bindings": [
          {
            "sort": "asc",
            "limit": 15,
            "attribute": "poste.service",
            "sortField": "displayName",
            "loadOnInit": true,
            "autoSelectFirstItem": true,
            "searchType": "partial",
            "viewFormat": "{displayName}",
            "initOnField": true,
            "queryParams": "identifier=@me.groups.identifier",
            "searchProperty": "displayName",
            "serviceAttribute": "displayName",
            "conflictStrategy": "replace"
          },
          {
            "attribute": "poste.service_id",
            "searchProperty": "identifier",
            "isValidationKey": true,
            "serviceAttribute": "identifier",
            "allowEmptyResults": false
          }
        ]
      }
    ],
    "content": {
    }
  }
}

Comportement attendu du paramètre allowEmptyResults

allowEmptyResults: false

  • Si la valeur saisie par l’utilisateur n’existe pas dans le référentiel lié, la publication est refusée.
  • Une erreur de complétude NoRecordFound est levée.

allowEmptyResults: true (ou absent)

  • La donnée peut être publiée même si elle n’existe pas dans le référentiel.

Pour plus d'informations techniques : InternalService - Principes de fonctionnement

Interfaces disponibles

Interface type jsonschema filterNode jsonschema Gère l'attribut format jsonschema ? Gère le référence externe ? Gère le référence interne ? Gère l'attribut pattern jsonschema ? Gère l'attribut minLength jsonschema ? Gère l'attribut maxLength jsonschema ?
Bloc (section) object ❌ ❌ ❌ ❌ ❌ ❌ ❌
Liste déroulante (à choix unique) enum exact ❌ ✔ ✔ ✔ ❌ ❌
Liste déroulante (à choix multiple) enum exact ❌ ❌ ✔ ✔ ❌ ❌
Champ autocompletion string exact / partial ❌ ✔ ✔ ✔ ❌ ❌
Champ texte string exact / partial ❌ ❌ ❌ ✔ ❌ ❌
Champ textarea string exact / partial ❌ ❌ ❌ ✔ ❌ ❌
Champ numérique number / integer numeric ❌ ❌ ❌ ✔ ❌ ❌
Champ date string date ✔️ (uniquement date) ❌ ❌ ✔ ❌ ❌
Champ radio (à choix unique) enum exact ❌ ❌ ❌ ✔ ❌ ❌
Champ case à cocher (à choix multiple) array exact ❌ ❌ ❌ ✔ ❌ ❌
Champ toggle boolean exact ❌ ❌ ❌ ❌ ❌ ❌
Champ image string exact ❌ ❌ ❌ ✔ ❌ ❌

Propriétés additionnelles dans les modèles d'attributs

La fonctionnalité additionalProperties permet aux utilisateurs d'enrichir dynamiquement un formulaire avec des champs personnalisés non définis dans le modèle initial.

Pour activer cette fonctionnalité, ajoutez la propriété suivante à la racine de votre modèle d'attributs :

image

Une fois activée, un bouton + "Ajouter un nouvel attribut" apparaît en bas à droite du formulaire.

image

  • À noter que ce champ ne possède pas de modèle, la définition de son libellé est limité :

Tous caractères spéciaux seront "nettoyés" afin d'éviter des problèmes de clé / valeur liée au JSON - Si la valeur d'un champ personnalisé est effacée, après validation, le champ disparait

Les données saisies dans ces champs supplémentaires sont stockées dans un objet spécial __additionalProperties :

{
  "dossier_rh": {
    "poste": {
      "mutuelle": "Employeur"
    },
    "collaborateur": {
      "nom": "Doe",
      "prenom": "John"
    },
    "__additionalProperties": {
      "remarques": "lorem ipsum"
    }
  }
}

Limitations :

Types de données gérés :

  • Texte (avec champ texte)

Exemples jsonschema

Bloc (section)

{ 
  "title":"Adresse",
  "description":"Adresse",
  "type":"object",
  "position": 1,
  "properties": { // others attributes... }
}

Liste déroulante (à choix unique)

"sexe": { 
  "title": "Sexe",
  "enum": [ "Féminin", "Masculin" ],
  "type": "string",
  "view": {
    "type": "input-select",
  },
  "pattern": "^(Féminin|Masculin)$",
  "position": 1,
  "filterNode": "exact"
}

Liste déroulante (à choix multiple)

Pour un référentiel, l'attribut loadOnInit est requis, car il faut la liste complète pour que le champ fonctionne correctement.

"sexe": { 
  "title": "Sexe",
  "enum": [ "Féminin", "Masculin" ],
  "type": "array",
  "view": {
    "type": "input-select-multiple",
  },
  "position": 1,
  "filterNode": "exact"
}

Champ auto complété

"color": {
  "title": "Couleur",
  "enum": [ "Bleu", "Blanc", "Rouge", ],
  "type": "string",
  "view": {
    "type": "input-autocomplete"
  },
  "position": 1,
  "filterNode": "partial"
}

Champ texte

"nom": {
  "title": "Nom",
  "position": 1,
  "type": "string",
  "view": {
    "type": "input-text"
  },
  "filterNode": "partial"
}

Champ texte à valeurs multiples

minItems, maxItems, copySeparator et uniqueItems sont facultatifs.

copySeparator permet de définir le caractère utilisé pour l'ajout automatique lors d'une copie (ex : liste d'emails en format scv séparé par un ;).

Les données sont automatiquement triées par ordre alphabétique.

"recipients": {
  "title": "Destinataires",
  "position": 1,
  "type": "array",
  "minItems": 2,
  "maxItems": 5,
  "uniqueItems": true,
  "items": {
    "type": "string"
  },
  "view": {
    "type": "input-text-multiple",
    "copySeparator": ";"
  },
  "filterNode": "partial"
}

Champ zone de texte

Peut avoir un attribut nbLineToShow pour définir le nombre de lignes affichées dans le champ avant scroll. Si pas d'attribut, le champ s'adapte à l'entièreté du texte.

Valeurs possibles : 2, 3, 10, 15, 20

"story": {
  "title": "Récit",
  "position": 1,
  "type": "string",
  "view": {
    "type": "input-textarea",
    "nbLineToShow": 10
  },
  "filterNode": "partial"
}

Champ numérique (entiers)

Valeur de 1 à 10 accepté dans cet exemple

Enlever minimum et maximum si non limité

"age": {
  "title": "Age",
  "position": 1,
  "type": "integer",
  "minimum": 1,
  "maximum": 10,
  "pattern": '^((-?[1-9])\d*)$',
  "view": {
    "type": "input-number"
  },
  "filterNode": "numeric"
}

Champ décimal

Les valeurs sont affichés avec , mais la valeur stockée est avec .

Ex : 20,4 => 20.4

"nom": { 
  "title": "Montant HT",
  "position": 1,
  "type": "number",
  "pattern": "^\d*,?\d*$",
  "view": {
    "type": "input-number",
    "mask": {
      "mask":"separator.2",
      "suffix": " €"
    }
  },
  "filterNode": "numeric"
}

Champ date

Une date minimum peut être défini ex : view.minDate: "2025-01-01" (format YYYY-MMM-DD)

Une date maximum peut être défini ex : view.maxDate: "2025-01-01" (format YYYY-MMM-DD)

La valeur today peut être utilisé pour définir la date du jour

ATTENTION ! Ceci n'est pas soumis à validation côté API, ceci est purement niveau interface

"birthDate": { 
  "title": "Date de naissance",
  "position": 1,
  "type": "string",
  "format": "date",
  "view": { 
    "type": "input-date",
    "minDate": "1970-01-01",
    "maxDate": "today",
    "mask": { 
      "mask":"d0/M0/0000",
      "showMaskTyped": true
    }
  },
  "filterNode": "date"
}

Champ radio (à choix unique)

"sexe": {
  "title": "Sexe",
  "enum": [ "Féminin", "Masculin" ],
  "type": "string",
  "view": {
    "type": "input-radio"
  },
  "pattern": "^(Féminin|Masculin)$",
  "position": 1,
  "filterNode": "exact"
}

Champ case à cocher (à choix multiple)

"menu": { 
  "title": "Choix du menu",
  "type": "array",
  "items": {
    "type": "string",
    "enum": [ "Entrée", "Plat", "Dessert", "Boisson" ]
  },
  "view": { 
    "type": "input-checkbox"
  },
  "filterNode": "exact"
}

Champ toggle

"urgence": {
  "title": "Urgent",
  "type": "boolean",
  "default": false,
  "view": {
    "type": "input-toggle"
  },
  "filterNode": "exact"
},

Champ image

"avatar": { 
  "type": "string",
  "view": { 
     "type": "input-img"
  },
  "title": "Avatar",
  "position": 1,
  "filterNode":"exact"
}

Champ tableau d'objets

Toutes les fonctionnalités du formulaire sont disponibles pour la definition du schéma dans ìtems

À noter toutefois qu'aucune validation serveur ne s'effectuera si un référentiel avec validation a été paramétré.

  "experience": {
    "type": "array",
    "view": {
      "type": "input-array"
    },
    "items": {
      "type": "object",
      "properties": {
        "domaine": {
          "enum": [
            "R&D",
            "Comptabilité",
            "Chefferie de projet",
            "Support",
            "Infra"
          ],
          "type": "string",
          "view": {
            "type": "input-select"
          },
          "title": "Domaine d'activité",
          "position": 1
        },
        "resume": {
          "type": "string",
          "view": {
            "type": "input-textarea"
          },
          "title": "Résumé",
          "position": 2
        },
        "time": {
          "type": "string",
          "view": {
            "type": "input-text"
          },
          "title": "Période",
          "position": 3
        }
      }
    },
    "title": "Expérience(s) professionelle(s)",
    "position": 1,
    "description": "",
    "uniqueItems": true
  }

Autres options disponibles :

Attribut Type Description
minItems integer Nombre minimum d'éléments requis dans le tableau
maxItems integer Nombre maximum d'éléments autorisés dans le tableau
uniqueItems boolean Si true, tous les éléments du tableau doivent être uniques
additionalItems boolean/object Schéma additionnel pour les éléments supplémentaires quand items est un tableau de schémas positionnels
contains object Schéma de validation que le contrat de données doit respecter
minContains integer Nombre minimum d'éléments qui doivent respecter le schéma contains
maxContains integer Nombre maximum d'éléments qui peuvent respecter le schéma contains