Aller au contenu principal

Créer votre première application Studio

Objectif​

Construire une application qui fonctionne à partir de rien : une table pour contenir les enregistrements, un écran pour les afficher, une barre d’outils qui agit sur celui que vous cochez, et un tiroir qui réécrit un nouvel enregistrement.

Cela prend environ quarante minutes la première fois. Rien ici n’est un jouet — chaque étape est celle que vous suivriez pour une vraie application, et ce que vous obtenez à la fin correspond à peu près à l’exemple Support desk.

Lisez d’abord le résultat final

studio-starter-app dans samples/solutions est cette application, dessinée. Importez-la depuis Solutions → Importer, ouvrez-la et appuyez sur les boutons. Revenir ici ensuite rend chaque étape ci-dessous plus courte.

Avant de commencer​

  • Vous êtes connecté en tant que créateur.
  • Un environnement et une solution sont sélectionnés dans les sélecteurs en haut de la page. Rien de ce que vous créez ci-dessous n’existe en dehors d’eux.
  • Vous connaissez le préfixe d’éditeur de votre solution — les deux ou trois lettres placées devant chaque nom logique. Cette page écrit par ; le vôtre peut être différent.

1. Une table pour contenir les enregistrements​

Une application qui n’affiche rien est un dessin. Créez d’abord la table.

Tables → Nouveau, puis renseignez :

ChampValeur
Nom d’affichageCase
Nom d’affichage au plurielCases
Nom logiquepar_case (le préfixe est ajouté pour vous)
Champ principalSummary

Enregistrez, puis ajoutez trois champs de plus dans l’onglet Champs :

Nom d’affichageNom logiqueType
Prioritypar_priorityChoix — nouveau groupe d’options par_case_priority, options Low, Normal, High
Urgentpar_urgentOui/Non
Notespar_notesTexte multiligne
Un champ Oui/Non commence à rien, pas à Non

Un enregistrement créé sans toucher à Urgent n’a aucune valeur pour ce champ — et aucune valeur n’est pas un booléen. Une expression comme record.par_urgent ? "Yes" : "No" échoue donc avec Only booleans are allowed in this expression (seuls les booléens sont autorisés) précisément sur ces enregistrements. Écrivez (record.par_urgent ?? false) partout où vous lisez un champ facultatif, et la question disparaît. C’est de loin la surprise la plus fréquente dans une première application.


2. L’application​

Applications Studio → Nouveau :

ChampValeur
Nom d’affichageSupport desk
Nom logiquepar_support
DescriptionUne phrase. C’est ce que la vignette du lanceur affiche sous le nom.
IcôneAppuyez sur le sélecteur et choisissez-en une — LifeBuoy convient à une application de support
Afficher dans la liste des applicationsactivé

Appuyez sur Enregistrer, puis sur Conception. Le canevas s’ouvre.


3. L’écran, et ce qu’est un écran​

Dessinez un cadre qui remplit le canevas — environ 1200 × 760 est une taille de dessin confortable — puis, avec ce cadre sélectionné, ouvrez l’onglet Écran de l’inspecteur à droite :

┌─ Inspector ───────────────────────┐
│ Graphic │ Screen │ Code │
├───────────────────────────────────┤
│ This object is … │
│ ┌─────────────────────────────┐ │
│ │ The screen ▾ │ │ ← choose this
│ └─────────────────────────────┘ │
│ │
│ Content that does not fit │
│ ┌─────────────────────────────┐ │
│ │ Show it — it spills out ▾ │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘

(Dans l’interface en français : Graphique │ Écran │ Code, Cet objet est → L’écran, et Contenu qui ne tient pas → L’afficher — il déborde.)

Renommez le cadre Cases dans le panneau Calques. Ce nom est le nom de l’écran — c’est ce que navigate("Cases") demandera plus tard.

La taille que vous avez dessinée n’est pas la taille finale. Tout ce qui est à l’intérieur est épinglé aux bords du cadre, et le cadre remplit la fenêtre, quelle qu’elle soit, dans laquelle l’application est ouverte.


4. La mémoire propre de l’écran​

Un écran se souvient de choses : la page de la liste sur laquelle vous êtes, ce qui a été saisi dans la zone de recherche, les lignes cochées. Cette mémoire est l’état de l’écran, et elle est déclarée une fois sur le cadre de l’écran.

Avec le cadre de l’écran sélectionné, dans l’onglet Écran, ajoutez ceci sous État de l’écran :

NomValeur initiale
cases[]
caseTotal0
page0
pageSize20
picked""
pickedCount0
pickedId""
form{ par_name: "", par_urgent: false, par_notes: "" }

Déclarez la forme de form, ne la laissez pas à {}. Une liaison qui lit un membre qu’un objet ne possède pas lève une erreur, et la première évaluation de l’écran s’exécute avant que quoi que ce soit n’ait rempli le formulaire.


5. Remplir la liste​

Toujours sur le cadre de l’écran, dans l’onglet Écran, trouvez Gestionnaires et placez ceci dans Chargé :

loadRecords("par_case", "cases", {
top: getState("pageSize"),
skip: getState("page") * getState("pageSize"),
orderBy: "par_name",
countInto: "caseTotal"
});

Une seule ligne de requête suffit à toute la pagination : top et skip constituent la page, et countInto rapporte combien il y a de lignes au total — ce qu’une page de vingt ne peut pas dire et qu’un bouton Suivant doit savoir. Voir Afficher une liste page par page pour la suite.

Rien ne s’affiche encore. Rien n’est dessiné pour l’afficher.


6. Une ligne, dessinée une fois​

C’est l’étape qui surprend, alors autant le dire clairement : vous dessinez une ligne, et la plateforme en dessine une par enregistrement.

  1. Dessinez un cadre à l’intérieur de l’écran pour la liste — toute la largeur du contenu, quelques centaines de pixels de haut. Nommez-le Rows. Dans l’onglet Écran, réglez Cet objet est → Une liste, puis Contenu qui ne tient pas → Y accéder par défilement, puis Sens de défilement → Vers le bas. Une zone dessinée pour six lignes en contient cent dès qu’une vraie table se trouve derrière.

  2. Donnez-lui une disposition automatique, verticale, dans l’onglet Graphique. Sans elle, chaque copie de la ligne se superpose à la première.

  3. Dessinez un cadre à l’intérieur de Rows, pleine largeur, 64 de haut. Nommez-le Case row, et réglez Cet objet est → Un élément de liste.

  4. Avec Case row sélectionné, trouvez Un de ceux-ci par dans l’onglet Écran et saisissez :

    state.cases
  5. Dessinez un objet texte dans la ligne pour le résumé. Sélectionnez-le, et dans Liaisons, définissez Texte :

    row.par_name

row est l’enregistrement de cette copie. Enregistrez, puis appuyez sur Aperçu : une ligne par dossier, issue de la table, sans rien répéter à la main.

┌─ Rows ────────────────────────────────────────────────┐
│ ☐ 1 Sample case 1 Normal │ ← Case row, drawn once
│ ☐ 2 Sample case 2 High Urgent │
│ ☐ 3 Sample case 3 Low │
│ … │
└───────────────────────────────────────────────────────┘

7. La case à cocher, et une seule idée de ce qui est choisi​

Dessinez un petit cadre au début de la ligne, 20 × 20, et réglez Contrôle → Case à cocher. Ensuite :

  • Liaisons → Valeur : state.picked.includes(row.id)

  • Gestionnaires → Valeur modifiée :

    var ids = "";
    var count = 0;
    var firstId = "";
    var i = 0;
    for (i = 0; i < state.cases.length(); i = i + 1) {
    var id = state.cases[i].id;
    var on = state.picked.includes(id);
    if (id == row.id) { on = !on; }
    if (on) {
    ids = ids + id + "|";
    count = count + 1;
    if (count == 1) { firstId = id; }
    }
    }
    setState("picked", ids);
    setState("pickedCount", count);
    setState("pickedId", firstId);

La valeur est une comparaison, pas un champ. Chaque case relit la même liste unique : vider la liste décoche donc toutes les cases à la fois — ce qu’un indicateur par ligne ne pourrait jamais faire, puisque rien ne saurait où se trouvent les autres lignes.


8. Une barre d’outils qui se réveille​

Dessinez une barre au-dessus de la liste — un cadre pleine largeur, 56 de haut, Cet objet est → Une région — et placez-y trois boutons. Pour chacun : dessinez un cadre, réglez Contrôle → Bouton, et dessinez à l’intérieur un objet texte pour la légende.

Mettez un pictogramme sur chacun. Sélectionnez le bouton, puis utilisez le sélecteur d’icônes de l’onglet Graphique — Plus, Pencil, Trash2. Ce qui arrive est un chemin dessiné ordinaire à l’intérieur du bouton, et le bouton final dispose lui-même le pictogramme, l’espacement et la légende.

BoutonLiaisons → ActivéGestionnaires → Appuyé
Nouveau(laisser vide)setState("form", { par_name: "", par_urgent: false, par_notes: "" });
openEndDrawer("New case");
Modifierstate.pickedCount == 1openStartDrawer("Edit case");
Supprimerstate.pickedCount > 0showModal("Confirm delete");

Liez Activé plutôt que d’écrire du code pour griser les choses. Une liaison est réévaluée à chaque changement d’état : la barre se réveille et se tait donc d’elle-même.

Peignez le texte et la bordure du bouton Supprimer avec le jeton color.error, pas avec un rouge que vous avez choisi. Un jeton suit le mode de couleur du lecteur ; une valeur hexadécimale convient à la personne qui l’a dessinée et pas à celles dont le thème est différent.


9. Le tiroir, et ce qu’est une carte superposée​

Une superposition n’est pas un écran. C’est un composant que vous cochez comme carte, et un gestionnaire décide de l’endroit où il s’ouvre.

  1. Dessinez le formulaire dans un endroit dégagé, à l’écart de l’écran — un cadre d’environ 420 de large, avec un titre, les champs et deux boutons. Peu importe où il se trouve sur le canevas ; une carte est compilée à l’origine, et seule sa largeur est conservée.
  2. Sélectionnez le cadre, appuyez sur Créer un composant, et nommez-le New case.
  3. Dans le panneau du composant, cochez Une carte superposée.
┌─ Inspector · Component ───────────┐
│ ☑ An overlay card │
│ Show it with showModal("New │
│ case") … Its drawn width is │
│ the panel's width. │
│ │
│ ☐ It has to be answered │
│ Clicking outside, Escape and │
│ the close button all stop │
│ working while it is open. │
└───────────────────────────────────┘

(Dans l’interface en français : Une carte superposée et Une réponse est obligatoire.)

À l’intérieur, pour chaque champ : dessinez un cadre, réglez Contrôle → Zone de texte (ou Zone de texte multiligne pour les notes), et liez Valeur au chemin d’état qu’il modifie — state.form.par_name, state.form.par_notes. Un contrôle lié à un simple chemin d’état y réécrit, il n’y a donc rien d’autre à câbler.

Puis le gestionnaire Appuyé du bouton Enregistrer :

if (getState("form").par_name == "") {
notifyError("A case needs a summary.");
} else {
createRecord("par_case", {
par_name: getState("form").par_name,
par_urgent: getState("form").par_urgent,
par_notes: getState("form").par_notes
});
notify("Case created.");
closeEndDrawer();
setState("page", 0);
loadRecords("par_case", "cases", { top: getState("pageSize"), skip: 0,
orderBy: "par_name", countInto: "caseTotal" });
}

Chaque écriture se termine par une relecture de la liste. Rien ne s’actualise tout seul — l’écran affiche ce que contient son état, et l’état contient ce que la dernière requête y a mis.

Une question à laquelle il faut répondre​

Dessinez la confirmation de suppression de la même façon, cochez Une carte superposée, puis cochez aussi Une réponse est obligatoire. Cliquer à l’extérieur, appuyer sur Échap et le bouton de fermeture cessent tous de fonctionner — dessinez donc votre propre sortie, un bouton Annuler qui exécute closeModal().

Laissez cette seconde case décochée partout ailleurs. Une superposition ouverte par erreur doit se fermer comme elle s’est ouverte ; la seule qui doit refuser est celle pour laquelle cliquer à côté n’est pas une réponse.


10. Enregistrer, et l’ouvrir pour de vrai​

Appuyez sur Enregistrer. Des avertissements apparaissent en haut si le compilateur a remarqué quelque chose — un bouton sans texte, un cadre d’écran manquant, une liaison qui n’est pas une expression pure. Ils nomment l’objet : corrigez-les donc en le sélectionnant.

Quittez ensuite le concepteur et ouvrez l’application depuis la liste des applications du Shell. L’aperçu n’est pas la même chose : il affiche les mêmes écrans sans environnement derrière eux, et ce qu’il ne peut donc pas vous montrer, ce sont vos données. Tout le reste — les tiroirs, les cases à cocher, la barre d’outils — se comporte de façon identique.


Et ensuite​

  • Un second écran. Dessinez un autre cadre à côté du premier, balisez-le L’écran, et nommez-le. Un sélecteur S’ouvre sur apparaît dans la barre de titre dès qu’il y en a deux. navigate("Board") permet de passer de l’un à l’autre.
  • Des onglets. Une bande d’onglets n’est pas un contrôle : c’est un champ d’état et un panneau par valeur. Dessinez les panneaux dans le même rectangle, liez l’Affiché de chacun à state.tab == "notes", et donnez aux boutons d’onglet une liaison Sélectionné sur la même comparaison. Utilisez Dessiner uniquement sur celui-ci sur la ligne d’un calque pour atteindre un panneau situé sous les autres.
  • Des contrôles que vous n’avez pas dessinés. L’exemple studio-component-library fournit une liste déroulante, une recherche et un interrupteur. Cochez Partager les composants de cette application sur cette application et ils apparaissent dans votre palette — voir Utiliser un composant dans une autre application.
  • Un tableau de bord. Le même canevas en dessine un : voir Placer un tableau de bord sur un écran dessiné.

Dépannage​

Ce que vous voyezCe que c’est
Only booleans are allowed in this expressionUn champ facultatif lu comme une condition. Enveloppez-le : (state.x.par_urgent ?? false).
La liaison d’une ligne se lit comme viderow n’existe qu’à l’intérieur de l’élément qui porte Un de ceux-ci par et de ses enfants.
Les lignes s’empilent les unes sur les autresLa zone qui entoure la ligne répétée a besoin d’une disposition automatique.
Le texte dépasse du bas d’une zonePlacez-le dans un cadre réglé sur Y accéder par défilement → Vers le bas, et ancrez le texte en haut.
Une liste déroulante ferme le tiroir depuis lequel elle a été ouverteCorrigé en 0.31. Mettez à jour, ou sortez le sélecteur du tiroir.
Enregistrer est désactivéRien n’a changé depuis le dernier enregistrement.