Suivez votre progression
Accueil Trailhead
Accueil Trailhead

Connexion à Salesforce avec des contrôleurs côté serveur

Objectifs de formation

Une fois cette unité terminée, vous pourrez :
  • Créer des méthodes Apex pouvant être appelées à distance par du code de composant Aura
  • Créer des appels depuis des composants Aura vers des méthodes à distance
  • Traiter des réponses du serveur de manière asynchrone avec des fonctions de rappel.
  • En bonus : expliquer la différence entre « c. », « c: » et « c. ».

Concepts de contrôleurs côté serveur

Jusqu’ici, tout ce que nous avons fait se trouvait strictement côté client. Nous n’avons pas encore réenregistré nos dépenses dans Salesforce. Créez quelques dépenses puis cliquez sur Recharger. Que se passe-t-il ? Exact, toutes les dépenses disparaissent. Super, argent facile !

Sauf que la comptabilité vient d’appeler, et bizarrement ce genre de choses ne les fait pas beaucoup rire. Et en fait, ne voulions-nous pas être remboursés de ces dépenses qui sinon sortiraient de notre poche ? Pas facile ! Enregistrer nos données vers Salesforce est certainement un bug niveau P0 !

Plaisanterie à part, il est temps d’ajouter des contrôleurs côté serveur à notre application. Nous attendions de le faire depuis que nous avons compris les bases. Maintenant que vous êtes prêt, plongeons-nous dans ce sujet !

Prenons quelques images. Assurons-nous de savoir où nous allons et d’avoir fait le plein avant de prendre la route.

Pour commencer, revoyons le premier schéma étudié dans ce module, un aperçu à (très) haut niveau de l’architecture des applications à composants Lightning.

Architecture à très haut niveau des composants Lightning : vue du client et du contrôleur, contrôleur Apex serveur et base de données

Jusqu’à présent, tout ce que nous avons étudié se trouvait côté client de ce tableau (et notez que nous avons simplifié les choses en fusionnant les contrôleurs et assistances ici). Bien que nous ayons référencé un type d’objet personnalisé qui est défini côté serveur, Expense__c, nous ne sommes jamais intervenus directement sur le serveur.

Vous vous souvenez que nous avons parlé de relier les éléments ensemble pour établir un circuit complet ? Le formulaire expenses que nous avons conçu lors de la dernière unité pourrait ressembler à ce qui suit :

Côté client du flux

Le circuit commence par le bouton Créer, qui est relié au gestionnaire d’actions clickCreate (1). Lorsque ce gestionnaire d’actions s’exécute, il extrait les valeurs des champs de formulaire (2) puis ajoute une nouvelle dépense au tableau expenses (3). Lorsque le tableau est mis à jour via set, le nouveau rendu automatique de la liste des dépenses se déclenche (4), terminant ainsi le circuit. Simple, n’est-ce pas ?

Bon, lorsque nous câblons notre circuit pour l’accès côté serveur, le schéma devient un peu plus compliqué. Plus de flèches, plus de couleurs, plus de numéros (nous vous expliquerons tout cela plus tard) !

Flux complet : côtés client et serveur

De plus, ce circuit ne permet pas la même démarche de contrôle régulière et synchrone. Les appels serveur sont coûteux en termes de ressources et de temps. Des millisecondes lorsque tout se passe bien, et de longues secondes en cas d’encombrement du réseau. Vous ne souhaitez pas que les applications soient bloquées pendant que vous attendez des réponses du serveur.

La solution pour qu’elles restent réactives tout en attendant ces réponses serveur consiste à les gérer de manière asynchrone. Cela signifie que lorsque vous cliquez sur le bouton « Créer dépense », votre contrôleur côté serveur envoie une requête au serveur puis continue le traitement. Non seulement il n’attend pas le serveur, mais oublie même qu’il a émis la requête !

Donc, lorsque la réponse revient du serveur, le code empaqueté avec la requête, nommé fonction de rappel, s’exécute et gère la réponse, y compris la mise à jour des données côté client et de l’interface utilisateur.

Si vous êtes programmeur JavaScript expérimenté, l’exécution asynchrone et les fonctions de rappel feront probablement partie de vos bases. Si vous n’avez pas encore travaillé avec ces éléments, ils seront nouveaux, et peut-être relativement différents. Ils sont également vraiment cool.

Requête d’obtention de données Salesforce

Nous allons commencer par lire des données de Salesforce, ce qui nous permettra de charger la liste des dépenses existantes au démarrage de l’application Expenses.

Remarque

Remarque

Si vous n’avez pas encore créé quelques vrais enregistrements de dépenses dans Salesforce, c’est le bon moment pour le faire. Sinon, une fois que vous aurez implémenté ce qui suit, vous pourriez vous retrouver à perdre du temps en vous demandant pourquoi rien ne se charge, alors que c’est en fait parce qu’il n’y a rien à charger. L’humble auteur de ces lignes vous recommande donc de le faire une bonne fois pour toutes. Là tout de suite.

La première étape consiste à créer votre contrôleur Apex. Les contrôleurs Apex contiennent des méthodes distantes que vos composants Lightning peuvent appeler. Dans ce cas précis, pour interroger Salesforce afin de recevoir les données des dépenses.

Examinons une version simplifiée du code. Sur la Developer Console, créez une nouvelle classe Apex nommée « ExpensesController » et collez-y le code suivant.

public with sharing class ExpensesController {
    // STERN LECTURE ABOUT WHAT'S MISSING HERE COMING SOON
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
}

Nous aborderons les contrôleurs Apex de manière plus détaillée dans la prochaine section, mais pour le moment, la méthode Apex est vraiment très directe. Elle exécute une requête SOQL et retourne les résultats. Seuls deux éléments spécifiques rendent cette méthode accessible au code de vos composants Lightning.

  • L’annotation @AuraEnabled figurant avant la déclaration method. « Aura » est le nom du framework au cœur des composants Lightning. Vous avez pu observer son utilisation dans l’espace de noms pour certaines balises fondamentales, telles que <aura:component>. Vous savez maintenant d’où elle vient.
  • Le mot clé static. Toutes les méthodes de contrôleur @AuraEnabled doivent être statiques, avec un périmètre public ou global.

Si ces exigences vous rappellent les méthodes distantes des fonctions de communication à distance JavaScript de Visualforce, ce n’est pas une coïncidence. Ces exigences sont identiques, car l’architecture est très similaire au niveau des éléments clés.

Autre point qui mérite d’être signalé : la méthode n’effectue aucune opération particulière pour empaqueter les données pour les composants Lightning. Elle se contente de retourner directement les résultats de la requête SOQL. Le framework des composants Lightning gère tous les rassemblements / séparations impliqués dans la plupart des situations. Très bien !

Chargement des données de Salesforce

La prochaine étape consiste à relier le composant expenses au contrôleur Apex côté serveur. C’est si simple que vous risquez de sauter de joie. Modifiez la balise d’ouverture <aura:component> du composant expenses afin qu’elle pointe vers le contrôleur Apex, de la manière suivante :

<aura:component controller="ExpensesController">

La nouvelle partie est mise en évidence en caractères gras, et oui, c’est vraiment aussi simple que cela.

Cependant, ce n’est pas le simple fait de pointer vers le contrôleur Apex qui charge les données ou appelle la méthode distante. Tout comme l’autoconnexion entre le composant et le contrôleur (côté client), ce pointage permet simplement à ces deux éléments de « se connaître ». Cette « connaissance mutuelle » prend la même forme, un autre fournisseur de valeurs, que nous aborderons dans un moment. Mais l’autoconnexion s’arrête là. C’est tout de même nous qui devons terminer le circuit.

Dans ce cas, terminer le circuit signifie la chose suivante.

  1. Lorsque le composant expenses est chargé :
  2. interroger Salesforce pour obtenir les enregistrements de dépenses existants, et
  3. ajouter ces enregistrements à l’attribut de composant expenses.

Nous traiterons ces deux opérations l’une après l’autre. Pour le premier élément, qui déclenche un comportement lorsque le composant expenses est chargé pour la première fois, il est nécessaire que nous écrivions un gestionnaire init. Il s’agit d’un gestionnaire d’actions relié à l’événement init d’un composant, qui se déclenche lorsque le composant est chargé pour la première fois.

Une seule ligne de balisage vous suffira pour cette liaison. Ajoutez ce qui suit au composant expenses, immédiatement en dessous des définitions d’attribut.

<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>

La balise <aura:handler> vous aide à indiquer qu’un composant peut bien gérer un événement spécifique. Dans ce cas, nous proposons de gérer l’événement init avec le gestionnaire d’actions doInit de notre contrôleur (la définition de value="{!this}" marque « this » comme étant un « événement de valeur ». La signification est trop complexe pour être abordée ici. Notez que vous devez toujours attribuer cette paire attribut-valeur à un événement init).

Appel des méthodes du contrôleur côté serveur

Une étape terminée, plus que deux. Les deux étapes suivantes ont lieu dans le gestionnaire d’actions doInit, donc c’est à lui que nous nous intéressons maintenant. Ajoutez le code suivant au contrôleur du composant expense.

    // Load expenses from Salesforce
    doInit: function(component, event, helper) {
        // Create the action
        let action = component.get("c.getExpenses");
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            let state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.expenses", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
        // Send action off to be executed
        $A.enqueueAction(action);
    },

Si toutes ces nouveautés vous laissent perplexe, notez qu'il s'agit seulement d’un nouveau gestionnaire d’actions. Il est formaté de la même manière et la signature de la fonction est la même. Nous sommes en territoire familier.

Ceci étant dit, toutes les lignes de code situées après la signature de la fonction sont nouvelles. Nous allons toutes les examiner dans un instant, mais voici un aperçu de l’effet de ce code :

  1. Création d’un appel de méthode à distance.
  2. Configuration de ce qui se passera au renvoi de l’appel de méthode à distance.
  3. Mise en file d’attente de l’appel de méthode à distance.

Ça a l’air assez simple, non ? Peut-être que la structure ou les détails du code sont nouveaux, mais les exigences de base de ce qui va se passer sont familières.

Est-ce que nous avons l’air de vous encourager ? Un peu comme si on essayait de vous aider à franchir un passage difficile ? Il faut que l’on parle de quelque chose de difficile. Le problème se trouve dans la première ligne de code de la fonction.

        let action = component.get("c.getExpenses");

Cette ligne de code crée notre appel de méthode à distance, ou action à distance. À première vue, component.get() semble familier. Nous l’avons déjà fait souvent.

Sauf qu’avant... nous obtenions « v.quelquechose », v étant le fournisseur de valeur de la vue. Ici c’est « c », et oui, c est un autre fournisseur de valeur. Nous avons déjà vu un fournisseur de valeur c, dans les expressions comme press="{!c.clickCreate}" et action="{!c.doInit}".

Ces expressions étaient situées dans le balisage du composant, dans la vue. Ici, dans le contrôleur, le fournisseur de valeur c représente quelque chose d’autre. Il représente le contrôleur Apex distant.

« Attendez une minute. Vous êtes en train de me dire que les composants Aura contiennent le contrôleur côté client c, l’espace de noms par défaut c et le contrôleur côté serveur c ? »

En un mot, oui. Respirez profondément.

Nous serons honnêtes avec vous. Si nous pouvions repartir à zéro, nous ferions sans doute certaines choses autrement. Si nous n’avons pas fait ces choix par accident, ces trois « c » sont clairement source de confusion. Nous aussi, on se mélange les pinceaux.

Mais comme on dit, c’est comme ça. Un homme averti en vaut deux. Et maintenant vous l’êtes.

Identifiant
Contexte
Signification
c.
Balisage composant
Contrôleur côté client
c.
Code contrôleur
Contrôleur côté serveur
c:
Balisage
espace de noms par défaut

OK, revenons-en à notre code. Avant de nous laisser distraire, nous en étions à cette ligne.

        let action = component.get("c.getExpenses");

Dans le code que nous avions vu auparavant, component.get("v.quelquechose") nous renvoyait une référence à un composant enfant de la vue (balisage composant). Ici, component.get("c.peuimporte") renvoie une référence à une action disponible au contrôleur. Dans ce cas, il renvoie un appel de méthode à distance à notre contrôleur Apex. Voici comment créer un appel à une méthode @AuraEnabled.

La « ligne » suivante, action.setCallback(…), est un bloc de code qui sera exécuté lorsque l’appel de méthode à distance sera retourné. Puisque ce sera exécuté « plus tard », laissons-le de côté pour l’instant.

La prochaine ligne à être vraiment exécutée est celle-ci.

        $A.enqueueAction(action);

Nous avons brièvement rencontré $A auparavant sans prendre le temps d’en parler. C’est une variable globale du framework qui offre de nombreux services et fonctions importants. $A.enqueueAction(action) ajoute l’appel au serveur que nous venons de configurer à la file d’attente des requêtes de l’infrastructure du composant Aura. Avec d’autres requêtes serveur en attente, elle sera envoyée au serveur au cours du prochain cycle de requête.

Ça a l’air un peu vague. Les détails sont intéressants et importants pour une utilisation avancée des composants Aura. Mais pour l’instant, voici ce que vous devez savoir à propos de $A.enqueueAction(action).

  • Il place les requêtes serveur en file d’attente.
  • Du point de vue de votre action de contrôleur, c’est tout.
  • Vous ne savez pas si vous aurez de ses nouvelles un jour, ni quand.

C’est là que le bloc de code que nous avons mis de côté entre en jeu. Mais avant d’y venir, un peu de culture pop.

Appels serveur, exécution asynchrone et fonctions de rappel

Sorti en 2011, le single de Carly Rae Jepsen « Call Me Maybe » a connu un énorme succès commercial et critique, devenant n° 1 dans de nombreux pays. Plus de 18 millions d’exemplaires de ce single ont été vendus dans le monde entier, et il s’agit apparemment de l’un des singles numériques les plus vendus. Les paroles les plus mémorables, issues du refrain, sont « Here’s my number. So call me maybe. » En plus d’être entraînante et follement accrocheuses, ces paroles sont également une métaphore illustrant la manière dont les composants Aura gèrent les appels au serveur.

Vous allez voir. Examinons notre gestionnaire d’actions en pseudo-code.

    doInit: function(component, event, helper) {
        // Load expenses from Salesforce
        let action = component.get("c.getExpenses");
        action.setCallback(
            // Here’s my number,
            // Call me maybe
        );
        $A.enqueueAction(action);
    },

Hum. Nous devrions peut-être expliquer les paramètres de action.setCallback() de manière plus détaillée. Dans le véritable code du gestionnaire d’actions, nous l’appelons ainsi :

        action.setCallback(this, function(response) { ... });

this est le périmètre dans lequel le rappel s’exécutera ; ici, this est la fonction du gestionnaire d’actions en elle-même. Considérez-le comme étant une adresse... ou peut-être un numéro. La fonction est ce qui est appelé lorsque la réponse du serveur est retournée. Donc :

        action.setCallback(scope, callbackFunction);

Here’s my number. Call me maybe.

Cela a globalement pour effet de créer la requête, d’encapsuler le code de manière à ce qu’il fasse ce qu’il a à faire une fois la demande exécutée et de l’envoyer pour exécution. À ce stade, l’exécution du gestionnaire d’actions en lui-même s’arrête.

Voici un autre moyen d’appréhender les choses. Imaginons que vous préparez votre enfant pour l’école, en lui donnant une liste de tâches à faire en rentrant à la maison. Vous le déposez à l’école puis allez travailler. Une fois au bureau, vous pouvez faire votre travail l’esprit tranquille, en sachant que votre enfant est obéissant et qu’il se chargera du travail que vous lui avez confié une fois rentré à la maison. Vous ne ferez pas ce travail vous-même et ne saurez pas exactement à quel moment il sera fait. Mais il le sera.

Voici un dernier moyen d’appréhender les choses, une fois encore en pseudo-code. Cette version « aplanit » la fonction de rappel pour laisser apparaître une version plus linéaire du gestionnaire d’actions.

    // Not real code! Do not cut-and-paste!
    doInit: function(component, event, helper) {
        // Create server request
        let action = component.get("c.getExpenses");
        // Send server request
        $A.enqueueAction(action);
        // ... time passes ...
        // ...
        // ... Jeopardy theme plays ...
        // ...
        // ... at some point in the indeterminate future ...
        // Handle server response
        let state = action.response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", action.response.getReturnValue());
        }
    },

Disons-le encore une fois. L’exécution asynchrone et les fonctions de rappel sont de rigueur pour les programmeurs JavaScript, mais si vous venez d’un autre langage, elles vous seront peut-être moins familières. Espérons que vous aurez bien compris leur principe à ce stade, car il est fondamental pour le développement d’applications avec des composants Lightning.

Gestion de la réponse du serveur

Maintenant que nous avons étudié la structure de la création d’une requête au serveur, examinons en détails la manière dont notre fonction de rappel gère véritablement la réponse. Voici la fonction de rappel à elle seule.

    function(response) {
        let state = response.getState();
        if (state === "SUCCESS") {
            component.set("v.expenses", response.getReturnValue());
        }
    }

Les fonctions de rappel prennent un seul paramètre, response, qui est un objet opaque fournissant les données retournées, le cas échéant, ainsi que divers détails concernant l’état de la requête.

Dans cette fonction de rappel, nous procédons aux opérations suivantes :

  1. nous obtenons l’état de la réponse ;
  2. si l’état est SUCCESS, c’est-à-dire que notre requête a été satisfaite comme prévu :
  3. nous paramétrons l’attribut expenses du composant sur la valeur des données de réponse.

Vous avez probablement quelques questions, par exemple :

  • Que se passe-t-il si l’état de la réponse n’est pas SUCCESS ?
  • Que se passe-t-il si aucune réponse n’est fournie (Call me maybe) ?
  • Comment pouvons-nous simplement affecter les données de réponse à notre attribut de composant ?

La réponse aux deux premières questions est que nous n’aborderons malheureusement pas ces possibilités dans ce module. Ce sont certainement des choses qu’il vous faudra savoir et prendre en compte dans vos applications du monde réel, mais nous ne disposons pas de suffisamment d’espace pour aborder ces points.

La dernière question est ici la plus pertinente, mais aussi celle à la réponse la plus simple. Nous avons défini un type de données pour l’attribut expenses.

<aura:attribute name="expenses" type="Expense__c[]"/>

Et notre action de contrôleur côté serveur a une signature de méthode définissant le type de données retourné.

public static List<Expense__c> getExpenses() { ... }

Les types correspondent, il nous suffit donc d’attribuer la valeur de l’un à l’autre. Les composants Aura gèrent tous les détails. Vous pouvez certainement procéder à votre propre traitement des résultats, en les transformant en d’autres données de votre application. Mais si vous concevez correctement vos actions côté serveur, vous n’êtes pas obligé de le faire.

Bon, nous avons examiné ces quelques lignes de code sous toutes les coutures. Mais voici la question : avez-vous déjà essayé votre version de notre application avec ? Car nous en avons terminé avec la partie Chargement des dépenses Salesforce. Rechargez l’application, et regardez si les dépenses que vous avez entrées dans Salesforce apparaissent !

Contrôleurs Apex pour les composants Aura

Avant de passer à l’étape suivante du développement de l’application, immergeons-nous un peu plus dans ces contrôleurs Apex. Voici un aperçu de la version suivante, dans laquelle il nous faudra gérer la création de nouveaux enregistrements, mais aussi la mise à jour de la case à cocher « Remboursé ? » dans les enregistrements existants.

public with sharing class ExpensesController {
    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Perform isAccessible() checking first, then
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }
    @AuraEnabled
    public static Expense__c saveExpense(Expense__c expense) {
        // Perform isUpdateable() checking first, then
        upsert expense;
        return expense;
    }
}

La version précédente nous en a donné un avant-goût, mais c’est maintenant vraiment le moment. Mais avant tout, concentrons-nous sur les détails de cette version minimaliste.

Nous avons commencé par ajouter une seule nouvelle méthode @AuraEnabled, saveExpense(). Cette méthode s’empare d’un objet Expense (Expense__c), puis procède à son insertion. Cela nous permet de l’utiliser à la fois pour créer de nouveaux enregistrements et pour mettre à jour les enregistrements existants.

Ensuite, notez que nous avons créé une classe comprenant les mots-clés with sharing. Elle appliquera automatiquement les règles de partage de votre organisation aux enregistrements disponibles via ces méthodes. Les utilisateurs ne verraient par exemple que leurs propres enregistrements de dépenses. Salesforce gère automatiquement toutes les règles SOQL compliquées en coulisses pour vous.

L’utilisation des mots-clés with sharing est l’une des mesures de sécurité essentielles que vous devez prendre lorsque vous développez le code du contrôleur côté serveur. Toutefois, cette mesure est nécessaire mais pas suffisante. Voyez-vous les commentaires concernant l’exécution des vérifications isAccessible() et isUpdateable() ? with sharing ne vous emmène que jusqu’ici. Vous devez en particulier implémenter vous-même la sécurité au niveau du champ ou de l’objet (souvent abrégée par FLS).

Voici par exemple une version de notre méthode getExpenses() avec cette sécurité minimale mise en œuvre.

    @AuraEnabled
    public static List<Expense__c> getExpenses() {
        // Check to make sure all fields are accessible to this user
        String[] fieldsToCheck = new String[] {
            'Id', 'Name', 'Amount__c', 'Client__c', 'Date__c',
            'Reimbursed__c', 'CreatedDate'
        };
        Map<String,Schema.SObjectField> fieldDescribeTokens =
            Schema.SObjectType.Expense__c.fields.getMap();
        for(String field : fieldsToCheck) {
            if( ! fieldDescribeTokens.get(field).getDescribe().isAccessible()) {
                throw new System.NoAccessException();
            }
        }
        // OK, they're cool, let 'em through
        return [SELECT Id, Name, Amount__c, Client__c, Date__c,
                       Reimbursed__c, CreatedDate
                FROM Expense__c];
    }

Il s’agit d’une relative expansion de notre code d’une ligne initial, mais elle est tout simplement adéquate. Par ailleurs, les appels à describe sont coûteux en termes de ressources. Si votre application appelle fréquemment cette méthode, vous devez trouver un moyen d’optimiser ou de mettre en cache vos vérifications d’accès par utilisateur.

Tout comme SLDS, nous ne disposons tout simplement pas de l’espace nécessaire pour vous enseigner tous les détails de l’écriture de code Apex sécurisé. Contrairement à SLDS, la sécurité du code que vous écrivez est nécessairement de votre responsabilité. Si vous n’avez pas lu les articles relatifs aux bonnes pratiques de sécurité dans les Ressources, mettez-les dans votre pile de choses à lire.

Parfait, </leçon-de-morale>.

Enregistrement des données dans Salesforce

Avant d’implémenter vraiment le formulaire Add Expense, nous ne prendrons pas de raccourci : il faudra d’abord découvrir en quoi la création d’un nouvel enregistrement est nettement différente de la lecture d’enregistrements existants. Avec doInit(), nous lisons simplement quelques données puis mettons à jour l’interface utilisateur de l’application. C’est clair, même si nous avons eu besoin de Carly Rae pour l’expliquer.

La création d’un nouvel enregistrement est plus complexe. Nous allons lire des valeurs du formulaire, créer un nouvel enregistrement expense localement et l’envoyer pour qu’il soit stocké sur le serveur. Puis, une fois que le serveur nous indiquera que l’enregistrement a réussi, nous mettrons à jour l’interface utilisateur, grâce à l’enregistrement retourné par le serveur.

Vous pensez que ce sera vraiment très compliqué ? Vous pensez qu’il va nous falloir un album complet des Rolling Stones pour nous aider dans la prochaine explication ?

Examinons un peu de code et vous verrez par vous-même.

Pour commencer, assurez-vous d’avoir bien enregistré la version mise à jour du contrôleur Apex, la version précédente comprenant la méthode saveExpense().

Vous vous rappelez de la gestion du formulaire de soumission ? Lorsque l'un des champs au moins n'est pas valide, un message d'erreur s'affiche et le formulaire n'est pas soumis. Le message d'erreur disparaît lorsque tous les champs sont valides.

Étant donné que nous avons placé tous les détails (et toutes les tricheries) de la création d’une nouvelle dépense dans la fonction d’assistance createExpense(), nous n’avons plus d’autre modification à apporter au contrôleur. Tout va bien jusqu’ici ?

Tout ce qui nous reste à faire, c’est modifier la fonction createExpense() dans l’assistance, afin qu’elle remplisse toutes les missions compliquées que nous avons évoquées plus haut. Voici le code en question :

    createExpense: function(component, expense) {
        let action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            let state = response.getState();
            if (state === "SUCCESS") {
                let expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
        $A.enqueueAction(action);
    },

Est-ce aussi compliqué que vous le craigniez ? Y a-t-il autant de lignes que vous le pensiez ? Espérons que non !

En réalité, il n’y a qu’un nouvel élément dans ce gestionnaire d’actions, et il est facile à comprendre. Parcourons le code.

Nous commençons par créer l’action, avec component.get("c.saveExpense") obtenant la nouvelle méthode de contrôleur Apex. C’est déjà connu.

Ensuite, nous joignons une charge utile de données à l’action. Voilà qui est nouveau. Il nous faut envoyer les données concernant la nouvelle dépense au serveur. Mais regardez à quel point c’est facile ! Il vous suffit d’utiliser action.setParams() et de fournir un objet de style JSON, avec des paires « nom de paramètre-valeur de paramètre ». Le seul truc, et c’est important, c’est que votre nom de paramètre doit correspondre au nom de paramètre utilisé dans votre déclaration de méthode Apex.

Ensuite, nous définissons le rappel pour la requête. Une fois encore, c’est ce qui se produira lorsque le serveur retournera une réponse. Si vous comparez cette fonction de rappel à notre fonction d’assistance createExpense d’origine, elles sont pratiquement identiques (exception faite du bidouillage dégoûtant).

Tout comme dans la version précédente, grâce à get(), nous obtenons l’attribut expenses. Ensuite, nous lui transmettons une valeur avec push(), puis la définissons avec set(). La seule véritable différence, c’est qu’au lieu de transmettre notre version locale de la nouvelle dépense au tableau avec push(), nous utilisons push() pour transmettre la réponse du serveur !

Pourquoi cela fonctionne-t-il ? Car la méthode côté serveur procède à une mise à jour de l’enregistrement (dans ce cas, du nouvel enregistrement), lui appose un ID puis retourne l’enregistrement obtenu. Une fois encore, les types de données côté client et côté serveur correspondent ; aucun travail supplémentaire n’est donc nécessaire.

Voilà. Pas besoin des Rolling Stones !

Points à surveiller

Bien que nous ayons abordé tous les points essentiels de la connexion de votre code de composant Aura côté client à votre code Apex côté serveur, quelques éléments méritent d’être soulignés avant de plonger dans le vif du sujet.

Ce premier point est la sensibilité à la casse, qui se résume de la manière suivante : Apex et Salesforce sont en général insensibles à la casse, alors que le JavaScript est sensible à la casse. C’est-à-dire que pour l’Apex, « Name » et « name » sont identiques, alors que ce n’est pas le cas pour le JavaScript.

Cela peut (et va) entraîner des bogues qui vous rendront complètement fous, car vous les aurez juste devant les yeux sans les voir. Tout particulièrement si vous avez travaillé avec du code de composants non Lightning sous Salesforce pendant un moment, vous ne penserez peut-être plus du tout à la casse des noms d’objet et de champ, des méthodes, etc.

Alors voici la meilleure pratique pour vous : Utilisez toujours le nom d’API exact de tous les objets, champs, types, classes, méthodes, entités, éléments, éléphants ou autres. Toujours, partout, même lorsque cela n’a aucune importance. De cette façon, vous n’aurez aucun problème. Ou en tout cas, pas ce problème.

L’autre point sur lequel nous aimerions attirer votre attention, c’est la nature de « required ». Nous ne pouvons pas résister à l’envie de répéter une célèbre citation : « Tu emploies toujours ce mot. Je ne sais pas s’il veut dire ce que tu penses. »

Dans le code que nous avons écrit jusqu’ici, nous avons vu au moins deux types différents de « required ». Dans le balisage du formulaire Add Expense, vous pouvez constater que le mot est employé de deux manières. Par exemple, pour le nom de champ expense.

<lightning:input aura:id="expenseform"
                 label="Expense Name"
                 name="expensename"
                 value="{!v.newExpense.Name}"
                 required="true"/> 

L’attribut required de la balise <lightning:input> est défini sur true. Ces deux cas n’illustrent qu’une seule signification de required, qui consiste à « configurer l’interface utilisateur de cet élément en lui indiquant que le champ est obligatoire ». En d’autres termes, ce n’est que « cosmétique ». Aucune protection de la qualité de vos données n’intervient ici.

Une autre signification du mot « required » est illustrée dans la logique de validation que nous avons développée pour le même champ.

let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
    // Displays error messages for invalid fields
    inputCmp.showHelpMessageIfInvalid();
    return validSoFar && inputCmp.get('v.validity').valid;
}, true);

Le mot « required » n’apparaît nulle part, mais la logique de validation l’applique. Vous devez définir une valeur pour le champ expense name.

Et jusqu’ici, tout va bien. Votre formulaire expense ne soumettra pas de nouvelle dépense en cas de nom vide. À moins bien sûr qu’il n’y ait un bogue. Ou encore, il est possible qu’un autre gadget utilise ce même contrôleur côté serveur, mais sans procéder à une validation de formulaire aussi minutieuse. et ainsi de suite. Cette mesure assure donc un certain niveau de protection de la qualité de vos données, mais ce n’est pas parfait.

Mais comment faire réellement respecter une règle d’intégrité des données - dans ce cas précis, pour expense name ? Vous le faites côté serveur. Et pas n’importe où côté serveur. Vous placez la règle dans la définition de champ, ou vous la codez dans un déclencheur. Mais si vous êtes du genre à appliquer un double principe de précaution, comme tout bon concepteur éclairé, vous ferez les deux.

Pour une vraie intégrité des données, lorsque « required » signifie vraiment obligatoire, appliquez-le au niveau le plus bas possible.