Monday, 15 February 2016

The way of the Lightning Component

This is a quick walk through of how I wrote my first Lightning Component in Salesforce, to give myself a feel for the process, and develop my own skills in this up and coming technology.

But what to do? I didn't want to actually produce a piece of serious functionality, thats what I do at work all week anyway, but I also didn't want to create a piece of HTML that wrote "Hello World!" in a totally separate and useless app.... and then I had a thought, why don't I write an integration to the Internet Chuck Norris Database, providing a contacts name, and then display a "Chuck Norris" style quote on the contact page...? 

(For those of you who didn't know, the ICNDB provides quotes about Hollywoods most bad ass actor, Chuck Norris, and it of course also provides a RESTful API - to which you can provide alternative first and last names, to receive some JSON with a quote tailored to that person)

So this was a fairly simple example, but it included a web call out, which is always interesting, and also meant I needed to embed the component on a standard view page, and pass information about that record to the component. I felt this was just the right amount of complexity. 

First off then: The component

It seemed logical to start with the component. I did all this work through the Developer Console, so some boiler plate was put in place with me as soon as I selected to build a new Lightning Component from the File menu.

All I needed to do was put some HTML in the aura:component tag, and putting some activity code into the client controller. So some gotchas - you can't make a web callout from the client controller in Javascript, logical I suppose, it is a bit wild - security wise; and a performance liability. I also had trouble getting the function to run from the "init" tag, so I put it behind a button for a while, but eventually it just started working under Init as well. I think perhaps I was just impatient!


<aura:component controller="ChuckController" implements="force:appHostable,flexipage:availableForAllPageTypes">
    <aura:attribute name="firstN" type="string" default="Chuck" />
    <aura:attribute name="secondN" type="string" default="Norris" />
    <aura:handler name="init" value="{!this}" action="{!c.requestQuote}" />
    <ui:outputText value="{!v.quote}"/>

In here we have a couple attributes, that bind to the data sent in from the Contact page (details below in the Visualforce section) and then an Aura:handler instructed to execute on init and the output text from the standard UI library for the quote, quite simple in the end.

The "Implements" section uses some standard Component libraries to tell Salesforce this component needs to work on all pages (Visualforce, standard view etc.etc.) and is "Hostable" - which is also something to do with placing it on Visualforce.

The action in the init tag calls the component method requestQuote - as described here:


 requestQuote : function(component, event, helper) {
        var initAction = component.get("c.getChucked");
        initAction.setParams({ firstN : component.get("v.firstN"), secondN: component.get("v.secondN") });
                if (response.getState() === "SUCCESS"){
                    component.set("v.quote", response.getReturnValue());

Just one simple function, which sets up a method call to our Apex controller called getChucked. I think all methods coming from the Apex need to start "get", and I don't know why the c. prefix is required. This had to be like this though because I couldn't call ICNDB from Javascript, so I use standard javascript to pull off an Asynchronous callout,  I can set the parameters by GETTING the values from the attributes on the page, and passing them in with "setParams".. and then in the callback function I use the Component.set method to update the quote in the UI output text element.

Once I had this component ready, I needed to house it in a Lightning Application, which I did so by making a new Application from the developer console, and simply entering:

<aura:application access="GLOBAL" extends="ltng:outApp" >
    <aura:dependency resource="c:ContactChucker" />

Here I just needed to tell Salesforce the app was available to anything that wanted it, and that it would be output on Visualforce pages (the outApp base controller). That is really it.. In this form, the preview button comes up blank, because the app doesn't actually make a component, just depends on it... but to test it, instead of that being a dependancy, you can actually just include the c:contactchucker tag to initiate the component right in the app.

A little Apex

So I now had a component, but it was trying to use a controller to make a call out that didn't exist.. so we better fix that. Here is the simple controller method to make the call out, as an @AuraEnabled method, and some clever innerclasses to facilitate the JSON deserialiser.


public class ChuckController {

    private class JsonJoke {
        public String type { get;set; }
        public JokeValue value { get;set; }   

    private class JokeValue {
        public String joke { get;set; }

    public static String getChucked(String firstN, String secondN) {
     HttpRequest req = new HttpRequest();
     req.setEndpoint('' + firstN + '&lastName=' + secondN);
     Http http = new Http();
     HTTPResponse res = http.send(req);
     JsonJoke jsjk = (JsonJoke)JSON.deserialize(res.getBody(), JsonJoke.class);
     return jsjk.value.joke;

I hear what you are thinking though, with Apex, we need tests to be able to package or promote this work. Well, don't freak out, there is a really simple test to assert this behaviour that utilises the Apex testing MockHttp library:


global class ChuckControllerTest {

    global class NeverMockChuckService implements HttpCalloutMock {
        global HTTPResponse respond(HTTPRequest req) {
            HTTPResponse res = new HTTPResponse();
            ChuckController.JokeValue jv = new ChuckController.JokeValue();
            jv.joke = 'MS Dynamics';
            ChuckController.JsonJoke jj = new ChuckController.JsonJoke();
            jj.type = 'joke';
            jj.value = jv;
            return res;

    public static void auraComponent_HttpMocked_ReturnsResult() {
        Test.setMock(HttpCalloutMock.class, new NeverMockChuckService());
        String quote = ChuckController.getChucked('Simon','Lawrence');
        System.assertEquals('MS Dynamics', quote);

There's 100% coverage in your pocket right there.

We have a bunch of code then, and basically a working component, but we have a little more to do to get it on the page and operating. First we need to get it into a chunk of Visualforce, which means two things: Making a Visualforce page (that uses the Contact controller, so it can be put on the details page) that actually renders the app and component, and then passing the Contacts name down into the app so it can be sent to ICNDB... This is what the Visualforce page ended up being:


<apex:page standardController="Contact">
    <apex:includeLightning />
    <div id="chuckLine" />
        $Lightning.use("c:ContactChuckerApp", function() {
          { firstN: "{!Contact.firstName}",
            secondN: "{!Contact.lastName}" },
          function(cmp) {}

This is a standard (and apparently famous) snippet for embedding a Lightning Component on a Visualforce page. Include the Javascript, set up an empty div container, and then use that Script tag to setup an instance of your app, and then inject the dependant component... (don't get too bogged down in namespaces here, it's all just "c" it turns out). The second parameter of the createComponent is a Javascript object that is bound to the attributes of the Component above.. gotcha: do not use reserved words or field names here, like 'contactName' as an attribute, or it will never work! I spent ages wondering why I couldn't pass the contact name in, until I switched over to firstN and lastN as my attributes, contactName simply would not be set.

The last two things to do now then is to put this piece of Visualforce on the standard Contact page layout, and ... have you realised the last thing? You need to make the domain a REMOTE SITE in remote site settings, or Salesforce won't be allowed to talk to it.

That is it though! you now have your own bad-ass Chuck Norris quotes about your Contacts in Salesforce, curtesy of Lightning Components. Here are some personal favourites...

1 comment: