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)

Si il y a un cache pour l'état du formulaire, cette directive n'est pas prioritaire sauf si 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 champs, on opte pour le syntaxe indiqué à 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 champs affiche le displayName mais doit stocker sont 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 cour à 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": "http://<{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 champ 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 par rapport à un autre

Deux syntaxes sont supportées pour le moment :

  1. If .. then 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"
    }
  }
}
  1. allOff avec des condition (au moins deux conditions différentes):

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 :

"codePostal": {
 "type": "string",
 "external": {
   "service": "matricule-service",
   "bindings": [
     {
       "attribute": "nom_commune",
       "serviceAttribute": "nom_commune",
       "initOnField": true,
       "searchProperty": "nom_commune",
       "searchType": "partial",
       "limit": 15,
       "sortField": "nom_commune",
       "sort": "asc",
       "viewFormat": "{nom_commune} - {Code_postal}" // serviceAttributes
     }
   ]
 },
 "title": "Code Postal",
 "view": {
   "type": "input-autocomplete",
   "size": "small-block"
 },
 "position": 6,
 "pseudoFormat": "postcode",
 "filterNode": "partial"
}

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 champs 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: {
        ...
    },
    ...
}

Pour plus d'information technique : https://labs.maarch.org/maarch/mdf-api-full/-/wikis/Reference/InternalService/R%C3%A9ferentiel-interne#principe

Interfaces disponibles

Interface type jsonchema filterNode jsonchema Gère l'attribut format jsonchema ? Gère le référence externe ? Gère le référence interne ? Gère l'attribut pattern jsonchema ? Gère l'attribut minLength jsonchema ? Gère l'attribut maxLength jsonchema ?
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 textearea 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 ❌ ❌ ❌ ✔ ❌ ❌

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.

Peut être 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

ATTTENTION! Ceci n'est pas soumis à validation coté 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"
}