Définition de vue Model-Driven
Identifiant du schéma : model-driven-view@2
Schéma lisible par machine : model-driven-view.schema.json
Version remplacée : model-driven-view@1
(model-driven-view-v1.schema.json) reste
publiée et enregistrée, afin qu’une solution déjà déployée avec elle soit toujours validée
selon le schéma pour lequel elle a été conçue. La version 2 ne fait jamais qu’ajouter des
propriétés facultatives — le tableau Structure ci-dessous en donne la liste actuelle — :
toute définition v1 est donc aussi une définition v2 valide, et rien n’est à réécrire.
La représentation canonique d’une vue Model-Driven Apps (section 6.7) — une liste tabulaire d’enregistrements avec ses colonnes, ses filtres et ses tris.
Structure
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
logicalName | string | oui | Préfixé par l’ éditeur, en snake-case minuscule. |
displayName | string | oui | Nom lisible de la vue. |
entity | string | oui | Le nom logique de la table propriétaire. |
description | string | non | La ligne affichée sous le titre dans la barre de titre de la vue. |
icon | string | non | L’icône affichée dans la barre de titre de la vue. |
viewType | string | non | Ce à quoi sert la vue : Public (par défaut), Lookup, QuickFind, Associated, QuickView, Card, SeedData. |
seedDataKind | string | sur une vue SeedData | Obligatoire sur une vue SeedData et refusé sur toute autre : Configuration (des lignes dont la solution a besoin pour fonctionner ; une importation les apporte toujours, Production comprise) ou Demonstration (des lignes d’exemple ; importées uniquement lorsque Inclure les données de démonstration est coché, jamais en Production). Reporté dans le package comme kind du document de données initiales. |
columns | array | oui | Noms logiques des champs affichés en colonnes. |
filters | array | non | Clauses de filtre à plat (combinées par ET) : field, operator et (pour les opérateurs à valeur) value. |
filterGroup | object | non | Une arborescence de filtres ET/OU hiérarchique (à la manière de la recherche avancée), combinée par ET avec filters. |
sorts | array | non | Clauses de tri : field, descending. |
actions | array | non | Actions de barre de commandes configurables. |
hierarchy | object | non | Présent lorsque la vue s’affiche sous forme d’arbre plutôt que de grille. |
calendar | object | non | Présent lorsque la vue s’affiche sous forme de calendrier plutôt que de grille. Jamais en même temps que hierarchy. |
columnProperties | array | non | La manière dont chaque colonne est présentée — en-tête, largeur, format, alignement, contrôle. Clairsemé. |
commandOverrides | array | non | Ce que cette vue modifie dans les commandes détenues par la plateforme. |
commandOrder | array | non | Une barre, un ordre : commandes intégrées et commandes du créateur entremêlées, par id. |
rowSelection | boolean | non | Indique si les lignes portent une case à cocher de sélection. true par défaut. |
Opérateurs : Equals, NotEquals, Contains, GreaterThan, LessThan, IsNull,
IsNotNull. Chaque champ référencé (colonnes, filtres, conditions de groupe de filtres,
tris) doit exister sur la table.
viewType
Le type d’une vue indique quelle surface la lit. Une même table peut porter simultanément une vue par défaut de chaque type, car elles répondent à des questions différentes :
| Valeur | À quoi elle sert |
|---|---|
Public | La page de liste normale de la table. C’est la valeur par défaut, et ce qu’est une définition sans viewType. |
Lookup | Les colonnes affichées dans le panneau de résultats d’un contrôle de recherche — généralement deux à quatre. |
QuickFind | Les colonnes de recherche : les colonnes sur lesquelles porte une recherche saisie. Ses columns forment l’ensemble interrogé ; ses filtres et tris ne sont pas lus. |
SeedData | Les lignes et colonnes qui voyagent avec la solution (PF22). Jamais affichée : une exportation de solution l’exécute — ses filters déterminent les lignes, ses columns les champs — et empaquette le résultat sous forme de document seed-data@1, qu’une importation insère ou met à jour par id. Une table qui en a plusieurs utilise celle par défaut, sinon la première par nom logique. |
Associated | La sous-grille des enregistrements associés. Réservée — rien ne l’affiche encore. |
Un contrôle de recherche qui ne nomme aucune vue en résout une automatiquement : la vue
Lookup par défaut de la table cible, sinon sa vue Public par défaut réduite à ses trois
premières colonnes, sinon le seul champ principal de la table. Une recherche sans vue
QuickFind ne porte que sur le champ principal. Les colonnes de recherche doivent être des
champs texte, car la correspondance est un « contient » insensible à la casse.
calendar (une vue qui s’affiche sous forme de calendrier)
Une vue dotée d’un bloc calendar dessine ses enregistrements placés sur une feuille de
jour, de semaine ou de mois — selon deux de ses propres champs de date — au lieu d’une
grille. Son absence signifie la grille, exactement comme pour hierarchy, de sorte que toute
vue stockée reste valide. Une vue qui porte les deux blocs est refusée : une vue se
dessine d’une seule manière.
C’est une correspondance, pas un modèle de données. Il n’y a ni table de réservation ni type de rendez-vous : toute table comportant une date peut être disposée sur un calendrier, et c’est au créateur de dire laquelle de ses dates signifie « commence ».
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
start | string | oui | Le champ Date et heure auquel un bloc est placé. Le seul rôle obligatoire : un enregistrement sans début ne peut pas être placé sur une feuille, il n’y a donc aucune solution de repli. Un champ système est autorisé — created_at_utc dispose les enregistrements selon leur date d’arrivée. |
end | string | non | Le champ Date et heure auquel un bloc se termine. Son absence convient à une table qui enregistre un instant plutôt qu’une durée, et donne à chaque bloc une longueur de defaultDurationMinutes — et non zéro, car un bloc sans hauteur ne peut pas être cliqué. |
title | string | non | Le champ d’où provient la première ligne du bloc. Son absence signifie le champ principal de la table, ce qu’affichent déjà une grille, un panneau de recherche et un fil d’Ariane. |
category | string | non | Un champ Choix, État ou Raison dont les options colorent les blocs et remplissent la légende. |
bookedFor | string | non | Le champ qui indique pour qui est le créneau, affiché discrètement sous le titre. N’importe quel type : une recherche se résout en nom de l’enregistrement cible. |
note | string | non | Le champ affiché sur un bloc assez haut pour une troisième ligne, et sinon dans son info-bulle. |
defaultScale | string | non | La feuille sur laquelle la vue s’ouvre : Day, Week (par défaut) ou Month. |
startHour | integer | non | La première heure affichée par les feuilles de jour et de semaine, 0–23. 0 par défaut. |
endHour | integer | non | L’heure à laquelle ces feuilles se terminent, 1–24, et supérieure à startHour. 24 par défaut. |
defaultDurationMinutes | integer | non | La durée d’un créneau lorsque le lecteur clique sur un créneau vide — celle avec laquelle arrive le nouvel enregistrement — et la hauteur de chaque bloc sur une vue qui n’associe aucun end. Au moins 1 ; 60 par défaut. |
🔴 start et end doivent être des champs Date et heure, et category doit porter des
couleurs. Les deux sont refusés à l’enregistrement, car tous deux échouent en silence à
l’écran : un start qui est une colonne texte dessine une feuille vide, qui se lit
comme « rien n’est réservé cette semaine » — la seule conclusion sur laquelle un lecteur
agira et qu’il ne peut pas vérifier. La couleur réside sur les options mêmes du choix, ce qui
permet de dire une seule fois « Examen est ambre » et que le calendrier, une cellule de
grille et un graphique le lisent tous de la même façon.
startHour et endHour font la différence entre une feuille de réservation utilisable et
une feuille qui s’ouvre aux deux tiers vide : une clinique ouverte de 8 h à 19 h ne veut pas
seize lignes de nuit. Ils indiquent où la feuille s’ouvre — le lecteur peut toujours
changer d’échelle et restreindre les heures depuis l’en-tête de la feuille, car la fenêtre
lui appartient plutôt qu’au créateur.
Il n’y a volontairement pas de rôle resource. Les feuilles disposent les jours en
colonnes : un champ désignant la colonne à laquelle appartient un bloc serait une
correspondance qu’un créateur pourrait établir pour ensuite la voir ne rien faire. Il
arrivera avec l’axe qui le dessine.
hierarchy (une vue qui s’affiche sous forme d’arbre)
Une vue dotée d’un bloc hierarchy dessine ses enregistrements imbriqués selon une
recherche — un organigramme avec les personnes de chaque unité, une arborescence de
catégories, une nomenclature — au lieu d’une grille. Son absence signifie la grille qu’ont
toujours été les vues, et c’est pourquoi toute vue stockée reste valide.
Un arbre sert à la navigation : les lignes sont récupérées par fenêtre, de sorte qu’un organigramme de cinquante mille personnes s’ouvre sans en charger aucune.
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
nodeTypes | array | oui | Chaque sorte de nœud que l’arbre peut dessiner. Au moins une. |
order | array | non | L’ordre dans lequel les segments sous un nœud sont dessinés, par id de type de nœud. Son absence signifie l’ordre de déclaration. |
rootPicker | boolean | non | Indique si un lecteur peut centrer l’arbre sur un nœud — l’ouvrir sur une ville plutôt que sur toute l’organisation. |
expandRoots | boolean | non | Indique si le premier niveau se déplie au chargement. true par défaut. |
Chaque entrée de nodeTypes :
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
id | string | oui | Le nom de ce type au sein du bloc — unit, person. Unique ici, et c’est à lui que se réfèrent order et chaque childOf.of. Une même table peut apparaître deux fois dans un organigramme, c’est pourquoi il s’agit du mot propre au créateur plutôt que d’un nom de table. |
entity | string | oui | La table dont ces nœuds sont des enregistrements. |
childOf | object | oui | La façon dont ce type se rattache à son parent — voir ci-dessous. |
label | string | non | Le champ dont la valeur constitue la ligne du nœud. Son absence signifie le champ principal de la table. |
secondary | string | non | Le champ affiché discrètement après le libellé. Son absence signifie le champ secondaire propre à la table ; une chaîne vide signifie aucun — sur une unité, le nom du parent est la ligne juste au-dessus, un organigramme est donc un endroit où la seconde ligne par défaut d’une table est erronée. |
icon | string | non | Le glyphe de la ligne. Son absence signifie l’icône propre à la table. |
image | boolean | non | Indique si la ligne porte l’image de l’enregistrement. |
detail | object | non | Ce qui remplit le volet à côté de l’organigramme lorsqu’un nœud est sélectionné. Son absence signifie le formulaire principal de la table. |
childOf prend un field — la recherche de la table de ce type qui contient
l’enregistrement parent — et un of facultatif, le type de nœud auquel il se rattache, par
id.
🔴 Exactement un type de nœud omet of. Celui-là est la table même de l’arbre imbriquée
en elle-même, et ses enregistrements au parent vide forment le niveau supérieur de
l’organigramme ; chaque autre type nomme ce à quoi il se rattache. Les nœuds hétérogènes sont
le cas général et non un ajout : les unités, puis les personnes qu’elles contiennent
relève du même mécanisme que province → ville → agence modélisé en trois tables.
La recherche nommée par childOf.field doit cibler la table du type parent — sa propre
table, pour le type racine. Une recherche pointant ailleurs ne décrit pas une imbrication, et
est refusée à l’enregistrement plutôt que d’afficher un niveau sans rien en dessous.
detail prend un kind valant Form (le formulaire propre à la table, enregistré via le
chemin d’écriture du formulaire d’enregistrement), Card (une vue Carte disposée pour être
lue plutôt que saisie, ce que l’on veut généralement pour une personne sous une unité) ou
None (pour un type qui n’est jamais qu’une structure), plus un name facultatif indiquant
quel formulaire ou quelle vue Carte.
columnProperties (comment une colonne est présentée)
Une liste clairsemée : seules les colonnes configurées par un créateur y figurent, et une
colonne sans entrée s’affiche à partir des seules métadonnées de son champ. Elle se place à
côté de columns plutôt que de transformer celle-ci en liste d’objets, afin que toute
définition v1 reste valide et que tout lecteur qui n’a besoin de savoir que quelles
colonnes a une vue ne soit pas affecté.
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
field | string | oui | La colonne que configure l’entrée. Doit faire partie des columns de la vue. |
label | string | null | non | Remplacement de l’en-tête. En son absence, le nom d’affichage du champ est utilisé. |
width | integer | null | non | Largeur en pixels, 40–1200. En son absence, la colonne se dimensionne elle-même. |
format | string | null | non | Format de la valeur. Pour une colonne DateTime, le même jeu de jetons que celui qu’acceptent les contrôles de date du formulaire (yyyy, yy, MM, dd, HH, mm, ss) — un seul vocabulaire de mise en forme dans tout le produit. |
align | string | null | non | start, center ou end. Logique, et non gauche/droite, afin de suivre le sens de lecture (§6.13.6). |
controlType | string | null | non | Le contrôle avec lequel la colonne s’affiche — un glyphe d’interrupteur pour un booléen, une date persane pour une date. |
controlType prend Text, TextArea, Check, Toggle, Date, DateTime, Time,
PersianDate, PersianDateTime, PersianTime, Lookup, PolymorphicLookup, Dropdown ou
Label. En son absence, il hérite de la valeur par défaut du champ, puis de celle du type de
données.
🔴 Il est toujours affiché en lecture seule. Une grille n’a pas de modification en ligne :
un interrupteur y montre donc un état au lieu de réagir à un clic. C’est aussi le seul
moyen d’afficher une colonne en calendrier jalali chez un locataire grégorien, puisque
format est un motif sans calendrier associé.
commandOverrides (modifier une commande détenue par la plateforme)
Ce que cette vue modifie dans les commandes intégrées — Nouveau, Modifier, Supprimer, Actualiser, Enregistrer, Enregistrer et fermer, Épingler. Son absence ne change rien, ce que signifie chaque vue conçue avant l’existence de cette fonction.
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
command | string | oui | L’id stable de la commande remplacée. |
label | string | non | Remplace le libellé de la plateforme. Son absence le conserve — y compris ses traductions, qu’un remplacement saisi perd nécessairement. |
icon | string | non | Remplace le glyphe de la plateforme. |
initiallyHidden | boolean | non | Indique si la commande est masquée au départ. |
initiallyDisabled | boolean | non | Indique si la commande est désactivée au départ. |
🔴 Indexé par un id de commande stable, jamais par libellé ni par position. L’id est le nom propre de la plateforme et ne change pas quand le libellé change : un remplacement ne peut donc pas se détacher de ce qu’il remplace dès que quelqu’un le modifie — et une commande intégrée peut être renommée ou retirée plus tard sans que chaque document stocké doive être réconcilié, puisqu’un remplacement nommant une commande qui n’existe plus est simplement ignoré.
🔴 Un créateur peut masquer et désactiver, jamais afficher ni activer. Le Player masque Nouveau pour qui ne peut pas créer et désactive Supprimer lorsque rien n’est sélectionné ; un remplacement capable d’annuler cela construirait une barre proposant ce que le serveur refusera.
« Au départ » dit tout. C’est l’état dans lequel la barre s’ouvre, et non une condition permanente — une règle métier ou le propre script de la commande peut l’afficher ensuite. Une commande qui ne doit jamais apparaître pour quelqu’un ne relève pas de ce mécanisme : c’est une autorisation, décidée sur le serveur, là où un navigateur ne peut pas la contester.
commandOrder (une barre, un ordre)
Les commandes intégrées et celles du créateur, entremêlées comme le créateur les a disposées, par id.
Une liste explicite plutôt qu’un rang sur chaque commande, car un rang doit être réécrit sur chaque commande dès que l’une d’elles bouge, et deux commandes peuvent avoir le même — une égalité que le moteur de rendu départagerait alors autrement que le concepteur.
🔴 Tout ce que cette liste ne mentionne pas conserve sa place naturelle, à la fin. C’est ce qui permet à la règle de résister au temps : une commande intégrée ajoutée à la plateforme l’an prochain, ou une commande ajoutée par une mise à niveau, apparaît au lieu de disparaître parce qu’un ordre stocké lui est antérieur. Un id désignant quelque chose qui n’existe plus est ignoré.
rowSelection (si les lignes portent une case à cocher)
true par défaut, et ce que signifie chaque vue conçue avant l’existence de cette fonction :
la grille reçoit son en-tête « tout sélectionner » et une case par ligne, ce sur quoi agit la
barre de commandes.
false convient à une vue qui n’est jamais que lue — une liste depuis laquelle personne
ne supprime ni ne réaffecte — où une case à cocher offre une sélection multiple dont la
surface n’a pas l’usage et coûte une colonne de largeur.
filterGroup (ET/OU hiérarchique)
Un filterGroup est un nœud récursif qui combine ses membres avec un connector :
| Champ | Type | Obligatoire | Remarques |
|---|---|---|---|
connector | string | oui | And (tous les membres correspondent) ou Or (au moins un membre correspond). |
conditions | array | non | Conditions feuilles (field, operator, value) de ce groupe. |
groups | array | non | Sous-groupes imbriqués (même forme), pour une imbrication ET/OU arbitraire. |
Exemple
{
"logicalName": "par_invoice_active",
"displayName": "Active Invoices",
"entity": "par_invoice",
"viewType": "Public",
"columns": ["par_name", "par_total"],
"filters": [ { "field": "par_total", "operator": "GreaterThan", "value": 0 } ],
"filterGroup": {
"connector": "And",
"conditions": [ { "field": "par_name", "operator": "Contains", "value": "INV" } ],
"groups": [
{
"connector": "Or",
"conditions": [
{ "field": "par_total", "operator": "GreaterThan", "value": 1000 },
{ "field": "par_total", "operator": "IsNull" }
]
}
]
},
"sorts": [ { "field": "par_name", "descending": false } ]
}
Exemple — une vue de recherche
{
"logicalName": "par_contact_pick",
"displayName": "Contact Lookup",
"entity": "par_contact",
"viewType": "Lookup",
"columns": ["par_name", "par_email", "par_city"],
"sorts": [ { "field": "par_name", "descending": false } ]
}
Exemple — une vue de recherche rapide
Seul columns est lu : ce sont les colonnes sur lesquelles porte une recherche saisie.
{
"logicalName": "par_contact_find",
"displayName": "Contact Quick Find",
"entity": "par_contact",
"viewType": "QuickFind",
"columns": ["par_name", "par_email", "par_accountnumber"]
}