Pages

Showing posts with label lightning components. Show all posts
Showing posts with label lightning components. Show all posts

Thursday, October 18, 2018

Salesforce Custom Metadata vs. Custom Objects: Where Should I Put My Configuration and Validation Settings?

(Version française en dessous)

Yesterday, a colleague asked me the difference between "custom metadata" and "custom objects" for storing "configuration and validation" information in Salesforce.

My answer is:

  • Custom metadata if you can. Salesforce says so.
    And it survives sandbox refreshes!
  • Data tables (custom objects) if you truly need it to be part of your data
    (e.g. people will want to include it, hyperlinked to other data, in reports).

When I wrote an Apex trigger that determined which User should "own" each "Admissions Application" in our org, I ended up splitting the configuration data in two.

Here's why:

Custom Metadata

Used for a table that helps Apex code ask, "Who does this?"

  • key: the code for a graduate degree we offer
  • value: a username responsible for recruiting people to that graduate degree

Data (Custom Objects)

Used for a list of U.S. states, their full spellings, and fields with "lookup" links to the User table indicating who does what work for that state.

  • There was strong demand to generate native Salesforce reports per User showing all the states they're responsible for and the high schools in those states. It made sense to ensure that the "high school" table could have a "lookup" field to this "states" table.
  • Custom metadata can have "master-detail" and "lookup" relational links to other custom metadata, but it can't link to ordinary data.
    • This meant we needed to store the the "states" as data (custom objects), even though we would also be using it as configuration information for Apex triggers.

UI & Editability Considerations

I'll let you in on a dirty little secret about another reason I used data tables ("custom objects") for most of the Undergraduate Admissions counselor assignment configuration data.

Undergraduate Admissions tweaks their "recruiter assignment" business rules several times a year. The real-world configuration data for their business is a lot more complex than a simple "list of U.S. states."

I'll be honest: Salesforce's user interfaces for hand-editing, viewing, and bulk-editing data are a lot more end-user-friendly than their user interfaces for the same operations on custom metadata, and setting granular "edit" permissions is a lot more sysadmin-friendly for data. I wanted to make sure end users, not sysadmins, were the ones whose time was spent tweaking the configuration several times a year!

I was thoroughly scolded at Dreamforce. Actually, I stand by my decision to use "data" tables, because there truly is a business need to report on the configuration data alongside normal data. But ... building your own user interfaces (typically using Lightning Components) to help end users edit custom metadata was a big theme. You have been warned:

  1. Dan Appleman's "Build Awesome Configuration Pages with Lightning Components & Custom Metadata"
  2. Gustavo Melendez & Krystian Charubin's "Crafting Flexible APIs in Apex Using Custom Metadata"
  3. Beth Breisness & Randi Wilson's "Create Guided User Experiences for Managing ISV Custom Metadata"


🇫🇷 - en français

Une collègue, peu familière avec Salesforce, m'a demandĂ©e quelle Ă©tait la diffĂ©rence entre « mĂ©tadonnĂ©es personnalisĂ©es » et « objets personnalisĂ©s » pour stocker des donnĂ©es de configuration et de validation dans une organisation Salesforce.

J'ai répondu:

  • Selon Salesforce, s'il est possible, utilisez des mĂ©tadonnĂ©es personnalisĂ©es.
    (Elles survivent l'actualisation d'un environnement sandbox.)

  • Stockez vos informations aux tables de bases de donnĂ©es (objets personnalisĂ©s) s'il faut crĂ©er des relations aux autres objets de votre organisation.
    (Par exemple, s'il y a des utilisateurs qui vont générer des rapports sur ces données.)

Mon approche

Lorsque j'ai Ă©crit un dĂ©clencheur Apex pour attribuer les enregistrements de notre objet « demande d'admission » aux utilisateurs corrects, j'ai fini par utiliser les deux approches:

Métadonnées personnalisées

J'ai choisi une type de mĂ©tadonnĂ©es personnalisĂ©es pour stocker des donnĂ©es simples concernant « qui gère chaque spĂ©cialisation ? »

  • clĂ©: le code qui indique un diplĂ´me d'Ă©tudes supĂ©rieures que l'universitĂ© offre
  • valeur: un nom d'utilisateur chargĂ© de recruter des Ă©tudiants au diplĂ´me

Objets personnalisés

J'ai choisi un objet personalisé pour stocker une liste d'états américains (abréviation et nom), avec quelques champs de référence indiquant les utilisateurs qui y gèrent divers aspects du recrutement d'étudiants en license.

  • Nos utilisateurs ont besoin de gĂ©nĂ©rer des rapports, regroupĂ©s par utilisateur, avec les Ă©tats qu'ils gèrent et les lycĂ©es qui y sont situĂ©s. Donc il fallait pouvoir crĂ©er une relation de l'objet « lycĂ©e » Ă  l'objet « Ă©tat ».
  • On peut crĂ©er des relations entre mĂ©tadonnĂ©es personnalisĂ©es, mais pas entre les types de mĂ©tadonnĂ©es personnalisĂ©es et les objets personalisĂ©s.
    • Donc j'ai choisi un modèle « objet personnalisĂ© » pour stocker les informations Ă  propos des Ă©tats, mĂŞme si ces informations servent aussi comme donnĂ©es de configuration pour un dĂ©clencheur Apex.

Considérations au sujet de l'interface utilisateur et au contrôle d'accès

Je vais vous rĂ©vĂ©ler un secret : ce n'est pas qu‘Ă  propos des besoins relationnels que j'ai choisi le modèle « objet personnalisĂ© » pour les informations Ă  propos de la gestion du recrutement en licence.

Le service admission en license changent les règles de « qui gère quoi » plusieurs fois par an. Et les donnĂ©es de configuration sont, en rĂ©alitĂ©, beaucoup plus complexes qu'une seule liste d'Ă©tats.

Actuellement, les interfaces utilisateur de Salesforce pour voir et enregistrer des donnĂ©es aux objets personnalisĂ©s sont supĂ©rieures Ă  celles pour les mĂ©tadonnĂ©es personnalisĂ©es – surtout si l'on est utilisateur ordinaire. Le contrĂ´le d'accès, aussi, est plus facile Ă  gĂ©rer pour les administrateurs. Je voulais m'assurer que ça soit des utilisateurs ordinaires qui font plusieurs fois par an cette saisie de donnĂ©es, et non pas les administrateurs !

On m'a fait honte de cette position Ă  Dreamforce. Ben, je maintiens ma choix d'objet personnalisĂ© pour raisons de relations entre objets. Mais … on a beaucoup rĂ©pĂ©tĂ© qu'il est de bonne pratique de construire ses propres interfaces utilisateur pour autoriser des utilisateurs ordinaires Ă  modifier les mĂ©tadonnĂ©es personnalisĂ©es en toute sĂ©curitĂ©. Je vous aurai prĂ©venus !

Vidéos pertinentes de Dreamforce

  1. « Build Awesome Configuration Pages with Lightning Components & Custom Metadata » de Dan Appleman
  2. « Crafting Flexible APIs in Apex Using Custom Metadata » de Gustavo Melendez & Krystian Charubin
  3. « Create Guided User Experiences for Managing ISV Custom Metadata » de Beth Breisness & Randi Wilson

Wednesday, November 2, 2016

Visualforce vs. Lightning Components Side-By-Side 1

In my last post, I proposed converting an existing Visualforce page to use Lightning Components. I decided instead to start with a few simple "build the same thing in both environments and compare them" exercises.

Visualforce & Lightning Components are different coding environments for writing web applications, hosted in the user interface of a Salesforce database, that interact with data stored in that database.

Here is the first such exercise.


Apex Controller "BlogExample1ContactController.apxc" - used by both files:

public class BlogExample1ContactController {
    @AuraEnabled
    public static List<Contact> getCsFromServer() {
        // Do various proper security stuff and then...
        return [SELECT LastName, FirstName, Id, Email FROM Contact WHERE LastName LIKE 'LastXYZZY-%']; // <<-- DO NOT actually just do this.  Need proper security stuff.
    }
}

This server-side code, when executed, returns a list of "Contact"-typed records from the database (filtered to expose only records where LastName starts with "LastXYZZY-," and exposing only data from the LastName, FirstName, Id, & Email columns of those records).

 


The VisualForce page only takes 1 more file of code to get data displaying at https://MyCustomDomainName.na##.visual.force.com/apex/BlogExample1VFPage.

Visualforce Page "BlogExample1VFPage.vfp" - used in VisualForce:

<apex:page docType="html-5.0" controller="BlogExample1ContactController">
    <p>Hi</p>
    <apex:repeat value="{!CsFromServer}" var="cntct">
        <hr/>
        <p><apex:outputField value="{!cntct.LastName}"/></p>
        <p><apex:outputField value="{!cntct.FirstName}"/></p>
        <hr/>
    </apex:repeat>
    <p>Bye</p>
</apex:page>

 

Here's what visiting the VisualForce page looks like:


The Lightning Components app requires 4 more files of code (1 "app" + 1 "component" and 2 more JavaScript files as its "controller" & "helper" files) to get data displaying at https://MyCustomDomainName-dev-ed.lightning.force.com/c/BlogExample1LCApp.app.

Lightning Components App "BlogExample1LCApp.app":

<aura:application >
    <c:BlogExample1LCBasicComponent />
</aura:application>

This app's only job is to exist (lines 1 & 3) - it gets its own URL so I can actually browse to it and see it.

Oh, and it has to say, "go see the Component called 'BlogExample1LCBasicComponent'" (line 2).

"c" is a built-in variable that means "the code-space that is 'all Lightning Components.'"

 

Lightning Components App "BlogExample1LCBasicComponent.cmp":

<aura:component controller="BlogExample1ContactController">
    <aura:handler name="init" value="{!this}" action="{!c.initializationCode}" />
    <aura:attribute name="cs" type="Contact[]"/>
    <p>Hi</p>
    <aura:iteration items="{!v.cs}" var="con">
        <hr/>
        <p><ui:outputText value="{!con.LastName}"/></p>
        <p><ui:outputText value="{!con.FirstName}"/></p>
        <hr/>
    </aura:iteration>
    <p>Bye</p>
</aura:component>

My Lightning Components example is so simple that it's just got 1 component.

If we'd wanted fancier HTML representing each record of the database that we're displaying, we could have moved the contents of that "aura:iteration" tagset to their own component and put a reference to that component inside the tagset instead. (Although we'd have to make sure, when defining it, to give it an "attribute" that can hold a "Contact"-typed record and, when referencing the component, to "pass" that attribute the current value of the iteration's "con" variable.)

Anyway, this component's definition indicates that it's "controlled" by our Apex controller, much as you see in our Visualforce code.

Overall, this component looks a lot like our Visualforce page. The main difference is that we have to explicitly tell it to execute JavaScript code ("initializationCode(...)"). (Note: "initializationCode(...)" itself also has to be told to actually talk to the server-side Apex controller and fetch data ... we'll see that later.)

If we don't do that, there'll be no data between "Hi" & "Bye," even if there's data in the database that matches the query in our Apex controller.

The Visualforce line '<apex:repeat value="{!CsFromServer}" var="cntct">' knows to go talk to the server to fill in data summoned by the code in the "value" tag. (It also has the server loop through that data and generate HTML/CSS.)

The Lightning Components line '<aura:iteration items="{!v.cs}" var="con">' doesn't. It just says, "if this component's 'cs' variable (which lives in a web-surfer's browser) has any data in it, have the browser loop through it and generate HTML/CSS."

Line 3 establishes that the component has a variable named "cs" and that its data type is a list of "Contacts."

Line 2 ('<aura:handler name="init" value="{!this}" ... />') actually executes JavaScript that will go & talk to the server and that is responsible for setting the value of "cs" (in this case, upon page load).

 

Lightning Components JavaScript file "BlogExample1LCBasicComponentController.js":

({
 initializationCode : function(component, event, helper) {
        helper.getCons(component);
 }
})

Every JavaScript function intended to be summoned directly from "component" markup should be in the associated "Controller" file and should be defined to expect 3 parameters: a component, an event, and a helper (in that order).

Every JavaScript function intended to be summoned from other JavaScript, rather than from the "component" markup itself, should be in the associated "Helper" file.

In this case, our "initializationCode" doesn't do any real work - it just executes a JavaScript function found in the "Helper" file and passes it the value of the "component" passed to it (which would be the component from which we summoned it).

 

Lightning Components App "BlogExample1LCBasicComponentHelper.js":

({
    getCons : function(component) {
        var action = component.get("c.getCsFromServer");
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (component.isValid() && state === "SUCCESS") {
                component.set("v.cs", response.getReturnValue());
            }
        });
        $A.enqueueAction(action);
    }
})

This JavaScript function inspects the "component" it's been passed, notices that that "component" has an Apex "controller" class attached to it in its definition, and goes about talking to that code (calling its "getCsFromServer()" method).

If data actually comes back from that process, it assigns that data to the component's variable "cs."

("v," by the way, is included in the framework and is a variable that an instance of a component uses to refer to itself. It also appears in the component's code above.)

 

Here's what visiting the Lightning Components app looks like, when viewed on its own:

(Notice how "vanilla" it looks - what you code is what you get!)


Finally, I have a bunch of random ways of abbreviating the word "contact" or "contacts" scattered throughout the source code to make variable scope clear. Hope it helps, sorry if it confuses!

Sunday, October 23, 2016

Lightning Components vs. Visualforce

In my last post, I wrote that "there's a new, alternative style of coding available for building custom reporting/data-entry screens inside Salesforce ("Lightning Components" instead of "Visualforce"). Our web developers don't write either right now, but if they did, they'd be happy to know that the new option is more modern-web-development-ey than the old option.

I've been slowly reading the thick book about Lightning Components that I picked up at Dreamforce.

After getting through chapter 2 ("quick start"), I thought, "I should take an existing Visualforce page I've written and rewrite it in Lightning Components as a programming exercise.


Most Visualforce pages I've written are simply a way of displaying the results of an SOQL query to an end-user, so they weren't going to be very exciting.

The most "interactive" one I've written prompts the user for a date and plugs that into an SOQL query whose results are displayed to the end-user. Some of the fields in the display allow their values to be changed, and there are Save/Cancel buttons for all changes made so far on the page. This seemed like my best candidate for a conversion.


Unfortunately, even this isn't a great candidate for conversion, as far as I can tell. Here's why.

Step 1: Think of everything a user of your VF/LC app "does" to the page as a "browser event."
Here are all of mine:

  • User fills a date in the date-picker box
  • User clicks the "submit chosen date and refresh page with data from server, showing event attendees for that date" button**
  • User fills in an "attendance status" picklist with a given value
  • User clicks the "save changes to server and refresh page with latest data from server" button**
  • User clicks the "discard 'attendance status' picklist-value changes made in browser" button*

Step 2: For each "browser event," ask yourself two questions:

  1. Does it actually make any substantial changes happen, besides what the user expected?
    (If "user clicks a checkbox" is the "browser event", then "checkmark toggles inside checkbox" is NOT a "substantial change," but "text next to checkbox changes color" IS.)
  2. Does the "substantial change" depend upon the Salesforce.com server being contacted?
    ("...saves changes to server..." or "...gets data from server..." DO require the server to be contacted. "...Text next to checkbox changes color..." DOES NOT.)

In my case, the "user fills in" items don't do anything "substantial."

My "substantial changes" (marked with at least 1 asterisk & boldfaced) occur when the user clicks buttons.

Of those 3 "substantial changes," 2 are completely dependent upon the Salesforce.com server being contacted (marked with 2 asterisks).

Only the "discard" button's behavior could be done without contacting the server, if you had been saving "old values" in the browser's memory as the user made changes.
Even there, though, it's probably much simpler to just ask the server to provide clean data.


From what I can tell so far, Lightning Components makes "substantial changes that don't have to talk to the Salesforce.com server" easier to write.

However, "substantial changes have to talk to the Salesforce.com server" involve writing way more code in Lightning Components than they require in Visualforce.


Although it has a few "sections" to it, not all of which are always visible, overall, my page is merely a single "editable SOQL query" user-interface with a few extra controls.

More importantly, I want it to behave "synchronously" with the server.
That is, once a user has clicked a button, I don't want them messing around with the clickable parts of the page until the data-refresh from the server is complete. I want them to lose any work they try to do between button-click and page-refresh.

I have never written a Visualforce page that includes any "substantial" results of users' "browser events" (e.g. button-clicks) that are easily handled entirely inside their browser with JavaScript (e.g. "change the color of the text").

Therefore, from what I can tell, my conversion will inherently involve a lot more code in Lightning Components than it involves in Visualforce.
(And more memory usage in the user's browser.)
Please correct me if I'm wrong!


Visualforce has a very efficient one-line syntax for communicating with the Salesforce.com server and refreshing the Visualforce page in response.

Lightning Components, from what I can tell (please correct me if I'm wrong!), makes you:

  • Code a browser-event handler to fire LC-event-#1
  • Code a LC-event-#1 handler to talk to the server and to fire LC-event-#2 upon response
  • Register a listener for LC-event-#2
  • Code a LC-event-#2 handler to refresh the page with data returned from the server

I still plan to do the conversion as practice, but I wonder if there's a way to get the best of both worlds for my apps: Lightning-Components-looking responsive styling coded in a minimal amount of Visualforce.