Pages

Showing posts with label visualforce. Show all posts
Showing posts with label visualforce. Show all posts

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.

Wednesday, October 12, 2016

Dreamforce 2016 Lessons Learned

Lucky me, I got to attend Dreamforce. I had colleagues going to plain-English "what you can do with Salesforce" talks, so I filled my days with "how to program Salesforce" lessons. Here's a summary of what I learned.

(Also, I plan to put out a "how to install WinPython on Windows" walk-through but haven't had time to capture screenshots of the version I found that works well without administrator rights. It'll happen, though!)

  1. I got to go to a "gripe to the people who maintain Salesforce" session and gripe about certain "relational databases have been doing this for decades--why doesn't Salesforce?" things (e.g. the fact that data-cleansing "trigger" code can't be written against certain data tables in Salesforce). I learned some interesting history about why such things are broken. (For the triggers, the fact that the tables having data in them had about a 3-year head start on triggers existing at all. Which means you have to rebuild the tables or something, and apparently that's expensive in Salesforce-employee-time for not-many-customers, lots-of-rows tables, so money to pay someone to do it has to be budgeted from the top at Salesforce.) Ultimately, as I suspected, the almighty dollar is an issue. Fixing such basic functionality doesn't easily compete with adding other features that are better at bringing in new multi-million-dollar contracts to Salesforce, so the problem gets picked at slowly as limited budgets allow. But it was fun to get to tell someone whose job it is to fix these issues how annoying these issues are to his face and get sympathetically nodded to!
  2. Salesforce is pushing a new "artificial intelligence" product they just acquired pretty hard, but it seems to me that it's for companies whose business models mean they can be "fuzzy" about how they handle customer data. (One of the brag-stories was something along the lines of, "We didn't like our 'unsubscribe rate,' so we used AI to predict who was likely to unsubscribe and to preemptively unsubscribe them!") That said, I didn't actually go to any sessions about Einstein - it just had a display in the way of my coding classes. A marketing colleague was way more interested (e.g. mining Facebook/LinkedIn data).
  3. 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.
  4. There's an option to turn on a new look & feel for the user interface in Salesforce ("Lightning Experience"). It's been pretty hideous (a bunch of icons instead of words), but it's getting better (they brought the words back). Not sure if it's worth the work. The new & old user-interfaces have different features + some overlap, so it's not just flipping a switch. I learned some ways to find out what's around & what isn't + to preview what our org would look like in Lightning Experience. Overall, I think our users tend to want to be able to see lots of data at a time, and font sizes and whitespace seem smaller in the "Classic" UI, so I think "Classic" is better for us.
  5. I learned some things I can do to make sure that my administrator rights for a web application I have hosted externally are secure (e.g. lock down my admin account with two-factor authentication).
  6. I thought I was writing one type of test ("unit tests") against code I was writing in Salesforce, but it turns out it's actually closer to another ("integration tests"). Probably OK because "integration tests" tend to be closer to answering the question, "does this do what the end-user asked for?" Not sure if writing true "unit tests" is actually necessary for what I'm writing, but good to know the difference and some new things to learn ("mocking") if I ever want to do so.
  7. I learned some code necessary to ensure that code I write in Salesforce respects normal user-access settings (it goes beyond "with sharing"). That said, most of what I've written purposely "plays God" & takes care of stuff a user can't. But perhaps some of it doesn't have to "play God," so this is a nice tool to have in my toolbelt. I learned it last year but went for a refresher this year and plan to start using it more.
  8. Database triggers (code that executes when people save data in Salesforce) can make "asynchronous" calls to external services that take a while to return results (e.g. "Hey Google, what's the driving time between these two addresses?"). However, you want to make sure you write such code only for database-data-change conditions that are likely not to happen too often (e.g. less than 20 times per 10 minutes, or less than a few hundred times per day), because only 50 such calls can stack up in Salesforce at a time, and the 51st+ just kind of fail, possibly irrecoverably).
  9. I learned that, with Apex, I can write my own REST API that interacts with my Salesforce org, rather than relying on the Salesforce REST API. This can potentially let me reduce the number of "API calls" I have to make when, say, interacting with my Salesforce org from Python running on my hard drive. Potentially. Doesn't yet pass the XKCD automation test for my work.

Wednesday, April 6, 2016

Define and Initialize a Map, List, and Set in Apex

I use Dave Helgerson's blog post "Define and Initialize a Map, List, and Set in Apex" pretty much every time I code. Keep it in your bookmarks and you can essentially type "map" or "list" or "set" in your browser URL and have a quick-reference whenever you need it.

Monday, April 20, 2015

My First VisualForce: A Table To Simulate A List View

So, a month or two ago, our implementation partner wrote a trigger to keep a field on the Opportunity called "Related Application Record" populated with a Lookup to the Admission Application record it most closely corresponded to.


(Don't forget our old friend the double-sided garden rake!)

Those Admission-Application "rake tines" are actually miniature rakes in and of themselves. They have "Checklist Records" hanging off of them, indicating what documents a Contact has turned in along with their application form.

We wanted to put a Related List at the bottom of the Opportunity page layout to show any "Checklist Records" from the Admission Application record indicated in "Related Application Record."

Only SalesForce wouldn't let us.

So I wrote my first VisualForce page, which allowed me to put a component into the Opportunity page layout that looks close enough to a Related List.

So here's the code

(Please let me know if you have any ideas for making "chkls" in my wrapper class private - it seems like bad design to leave it public. Please also let me know if you see any design or security flaws in this code. I'm still a beginner and appreciate pro tips.)

(Note: Please let me know if my code doesn't seem to flow. I did some manual Find&Replace to obfuscate my org's internal structure just a wee bit, and I might have missed something.)

The VisualForce Page

<apex:page standardController="Opportunity" extensions="OppApplicationChecklistClass">
 <apex:pageBlock >
  <apex:pageBlockTable value="{!app_chkl}" var="a" id="table">
   <apex:column value="{!a.chkls.Requirement__c}"></apex:column>
   <apex:column value="{!a.chkls.Received_Date__c}"></apex:column>
   apex:column value="{!a.chkls.Comment__c}"></apex:column>
  </apex:pageBlockTable>
 </apex:pageBlock>
</apex:page>

The Apex Class (A "Standard Opportunity Controller Extension")

(Ignore the close-tags in line 52 - either Blogger or my new code formatter doesn't seem to like Apex collections and insists on closing them as if they were HTML.

public with sharing class OppApplicationChecklistClass {
 
    // Attributes possessed by all objects made out of this class
    private final Opportunity opp;
    private List chklRecords;
    
    // Constructor for this class
    public OppApplicationChecklistClass(ApexPages.StandardController stdController) {
        this.opp = (Opportunity)stdController.getRecord();
    }
 
    // Methods possessed by all objects made out of this class
    
    public Opportunity getOpportunity() {
        return opp;
    }
    
    public List getchecklist() {
  
  if (chklRecords == null) {
   
   chklRecords = new List();
    
   List tempQueryResult = [SELECT (SELECT Id FROM Opportunities__r),
    (SELECT Applicant_Last_Name__c, Requirement__c, Received_Date__c, 
    Comment__c FROM Checklist__r) FROM ApplicationRecord__c
    WHERE Id IN (SELECT Related_Application_Record__c FROM Opportunity WHERE Id = :opp.id)
    AND Id IN (SELECT RelatedApplication__c FROM Checklist__c)];

    if(tempQueryResult.size() == 1) {
    for (Checklist__c a : (tempQueryResult.get(0)).Checklist__r) {
     chklRecords.add(new wChkl(a));
    }
   }
  
  }
  
  return chklRecords;
  
    }
    
    // Another attribute possessed by all objects made out of this class...
    // ...the wrapper class wChkl surrounding a single Checklist__c object
    public with sharing class wChkl {
        public Checklist__c chkls {get; set;} // I don't seem to be able to find a way to privatize this.
        public wChkl(Checklist__c a) {
            chkls = a;
        }
    }
 
}

My awful test class

(I swear I mean to come back to it and make it meaningful...)

@isTest
private class OppApplicationChecklistTest {
  static testMethod void test() {
        
        Opportunity setupOpp = new Opportunity();
        ApexPages.StandardController sc = new ApexPages.standardController(setupOpp);
        
        // Create an instance of the page controller to test
        OppApplicationChecklistHandler testPageCon = new OppApplicationChecklistHandler(sc);
        
        // Try calling methods/properties of the controller in all possible scenarios to get the best coverage.
        Opportunity testOpp = testPageCon.getOpportunity();
        
        // OppApplicationChecklistHandler works with a blank Opportunity if it can't find a real one from the page it's on.
        System.assertEquals(null, testOpp.Id);
        // Working with a blank Opportunity, the list of Checklist records would also be blank.
        System.assertEquals(0, (testPageCon.getchecklist()).size());
    }
}

Thursday, April 2, 2015

VisualForce!

It's off to vacation for me, but boy do I have some fun code to share once things settle down. I created my first VisualForce component! It's a Related-List-like block that can be inserted into an Opportunity Page Layout, and it shows a list of values from an object that ACTUALLY hangs off of "Admission" in the underlying schema.

Update: Posted here