Pages

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!