Knockin’ On Kevin’s Door

Often times, even I don’t have all the answers…I know “shocking” right? In those times of need, we all need that one person we can go to, for me that person is @Kevin Swiggum (my boss at Radial Web, Inc.). I’m sure coming to my rescue can get pretty annoying at times, but he’s been doing this 4 times as long as I have and has seen numerous anomalies in numerous orgs.

For those that do not know, Kevin is a former MVP — and more than likely still would be, but running a business tends get in the way of keeping up with some of those requirements. (Not to mention, I’m working for him and I can keep anyone on their toes…)

So if you don’t already have your “Kevin” — find one, and this goes out to all of the “Kevin’s” out there…thanks for all your help!


Knockin’ On Kevin’s Door

P.S. I don’t normally perform the parodies I write. I have assisted writing a few Salesforce parodies over the past year or so, but since this one is really about my boss, I figured I’d have to own this one D.C al Coda.

P.P.S: for those that are interested, this was recorded on an iPhone 6 with a Voyage Air Travel guitar, on my deck…

:wq!

Streams of Lightning: Part 5

So last week I completely lost track of time and did not get part 5 out the door. For that I apologize, I’ll make it up to you somehow, someday, someway. For now however, you’ll have to settle for me being one week late with the finale to the Streams of Lightning series.

When we left off last time we finally had a usable lightning application. We could present a poll to our Salesforce1 users, and have them vote on their answers. (Yes we allow multiple votes, but I needed that for my demo anyway, otherwise it would have been very boring…).

Today we will make things a little more interesting by allowing our users to get real-time counts on the votes being cast. For that, we need to figure out how to tie in to the Salesforce Streaming API. We are going to need some external libraries and setup some push topics for our Poll Option object. We’ll then subscribe to that push topic in order to receive updates when a Poll Option has its vote count updated.

First, we need to get our external libraries. Head over Getting Started with the Streaming API to get a little background on how it all works and to download the libraries that will be needed. They have us create the Push Topic first, so let’s go ahead and do that now. Open the developer console and choose Debug->Open Execute Anonymous Window. Enter the following code and execute it:

PushTopic pushTopic = new PushTopic();
pushTopic.Name = 'PollOptionUpdates';
String open = 'Open';
pushTopic.Query = 'SELECT Id, Option_Name__c, Vote_Count__c FROM Poll_Option__c';
pushTopic.ApiVersion = 33.0;
pushTopic.NotifyForOperationCreate = false;
pushTopic.NotifyForOperationUpdate = true;
pushTopic.NotifyForOperationUndelete = false;
pushTopic.NotifyForOperationDelete = false;
pushTopic.NotifyForFields = 'Referenced';
insert pushTopic;

This will create a push topic that we can “subscribe” to. Whenever an update happens on our Poll Option, the streaming API will be notified and it will update our UI.

Next we need to obtain the necessary javascript libraries. Rather than plagiarize what is written in the docs, I am going to refer you to the Section titled: “Receiving Notifications in a Visualforce Page” at the following link (same as above link): CometD steps. You only need to follow as far along there as getting the appropriate files uploaded to your org as Static Resources. Once you’ve uploaded your files, you are done with that page.

Now lets modify our code. The first thing we need to do is authenticate to the Streaming API, for that we need a valid Session ID from Salesforce. (PLEASE NOTE: I’ve read in some places that this is not the “blessed way” to get a Session ID for lightning. However, there is no “blessed way” that has been given yet to my knowledge. Therefore, YMMV and until they give us a path forward, this is what I’ve done). Open your PollController apex class and add a method to return the current Session ID:

public class PollController {
    @AuraEnabled
    public static Poll_Question__c latestPoll() {
        Poll_Question__c poll = [SELECT Id
                                 , Name
                                 , Subject__c
                                 , Total_Votes__c
                                 , Highest_Votes__c
                                 , (SELECT Id, Vote_Count__c, Option_Name__c 
                                   FROM Poll_Options__r)
                                 FROM Poll_Question__c WHERE Status__c = 'Open' 
                                 ORDER BY CreatedDate LIMIT 1];
        System.debug('POLL:' + poll);
        return poll;
    }

    //new code
    @AuraEnabled
    public static String sessionId() {
        return UserInfo.getSessionId();
    }
}

Next open up the PollItemController.js (the client side controller for the parent component in our Poll app). Currently there is only one javascript function in there called “doInit” and that calls the “latestPoll” apex method to retrieve the poll object. We are going to add a second action to this function that will give us back the Session ID that we will need to authenticate to the Streaming API:

({
    doInit : function(component, event, helper) {
        var action = component.get("c.latestPoll");
        action.setCallback(this, function(response) {
            var state = response.getState();

            if(state === "SUCCESS") {
                component.set("v.pollQuestion", response.getReturnValue());
            }

        });
        $A.enqueueAction(action);
        
        //new code
        var sessionAction = component.get("c.sessionId");
        sessionAction.setCallback(this, function(response) {
            var state = response.getState();
                if(state  === "SUCCESS") {
                    component.set("v.sessionId", response.getReturnValue());
                }
            });
            $A.enqueueAction(sessionAction);
	}
})

We will also need a place to store this session ID for later use within our component so open up the PollItem component and add an attribute called “sessionId”:

<aura:component implements="flexipage:availableForAllPageTypes"  controller="BlogPollController" >

    <aura:attribute name="pollQuestion" type="Poll_Question__c"/>
    <aura:attribute name="options" type="Poll_Option__c[]"/>
    <aura:attribute name="title" type="String" />
    <!--new attribute here-->
    <aura:attribute name="sessionId" type="String" />
    <!--end new attribute-->
    {!v.title}</h1>
    <p style="padding:10px;">{!v.pollQuestion.Name}</p>
    <aura:iteration var="option" items="{!v.pollQuestion.Poll_Options__r}">
        <!-- modified code-->
        <c:BlogPollItemOption pollOption="{!option}" position="{!pos}" currentSessionId="{!v.sessionId}"/>
    </aura:iteration>

</aura:component>

We’ve created a new action to call up to our PollController.sessionId() method. We then take the returned session ID and we pass that back to our component {!v.sessionId} for safe keeping as our child component will then use this (currentSessionId=”{!v.sessionId}”) when it is time to subscribe to the push topic. (You may ask why would the child component be in charge of subscribing. The answer is, because I was learning this myself as I went along. I would like to refactor this to have the parent component handle everything but I haven’t tried yet and not sure it would even work. I invite you to mess with it and share your experience as I doubt I’m going to get that chance. Ahhh the life of a consultant, father, husband, musician…).

Now that our component has a handle on the session, we can wire up our subscription to the push topic. (There are some items here that I’d wish I’d refactored as well, but for sake of the demo and getting you something you can toy with, I’ve left it be — that’s very hard for me…I always want to fix things). We now have to simply load our external libraries and subscribe to the push topic. We will do this in our child component. (This is one of the items I’d like to fix at some point). Open up your PollItemOption component and tell it to load some external javascript:

<aura:component controller="PollOptionController"<
    <!-- require external JS here -->
    <ltng:require scripts="/resource/cometd,/resource/jquery151,/resource/json2,/resource/jquerycometd"
                  afterScriptsLoaded="{!c.afterScriptsLoaded}" /<
    
    <aura:attribute name="pollOption" type="Poll_Option__c"/<
    <aura:attribute name="pollOptionId" type="String" default="{!v.pollOption.Id}"/<
    <aura:attribute name="position" type="String" /<
    <!--new code-->
    <aura:attribute name="currentSessionId" type="String" /<
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" /<
    
    <div style="border:1px solid black;margin:5px;padding:10px;text-align:center;border-radius:5px"<
        <ui:button aura:id="voteButton" label="{!v.pollOption.Option_Name__c}" press="{!c.voteItUp}"/<
        <div style="float:right" class="findme" id="{!v.pollOption.Id}"<
            {!v.pollOption.Vote_Count__c}
        </div<
        <br/<
        <ui:outputText aura:id="sessionId" class="hideme" value=""/<
    </div<

</aura:component<

This tells our component that we want to use some external javascript libraries and makes them available for our use. We can also specify an action to take after our javascript libraries load by specifying the “afterScriptsLoaded” attribute. In this case, it will call the client side controller {!c.afterScriptsLoaded} function. So lets create that now. Open up your PollItemOption component and create an afterScriptsLoaded method (currently we just have the voteItUp function here):

({  
    
    voteItUp : function(component, event, helper) {
	var optionId = component.get("v.pollOptionId");
        var voteAction = component.get("c.incrementVote");
        voteAction.setParams({ optionId : optionId });
        
        voteAction.setCallback(this, function(response) {
           var state = response.getState();
            if(state === "SUCCESS") {
                document.getElementById(optionId).innerHTML = response.getReturnValue();
            }
        });
        $A.enqueueAction(voteAction);
        
        //new code
        $.cometd.subscribe('/topic/PollOptionUpdates', function(message) {
            var voteTarget = message.data.sobject.Id;
            document.getElementById(voteTarget).innerHTML = JSON.stringify(message.data.sobject.Vote_Count__c);
        });
    },
    
    //new code
    afterScriptsLoaded : function(component, event, helper) {  
        var authstring = "OAuth " + component.get("v.currentSessionId");

        //authenticate to the Streaming API
        $.cometd.init({
            url: window.location.protocol+'//'+window.location.hostname+'/cometd/29.0/',
            requestHeaders: { Authorization: authstring },
            appendMessageTypeToURL : false
        });
    }
})

After our javascript libraries load, we authenticate to the streaming API using CometD. That requires a session ID, in case you didn’t notice, we modified the call out to our child component to include the session ID. This is likely overkill and if refactored could/should be moved to our parent component but lets go with it. Lastly, we add the actual subscribe to the voteItUp method. I did this because I needed some sort of event to tell our component that we want to subscribe to the push topic. NOTE: Originally, I tried this in the afterScriptsLoaded method but that always failed and near as I can tell it was a timing issue with resources actually being available, etc. Once I moved the code to the vote method, it started working fine. I’ve not had a chance to mess around with it yet, but I suspect I could use some other built-in event or even a custom event to do this so that it doesn’t call the subscribe every time.

Finally, when our button is pressed, it will subscribe to the push topic (every time apparently, again I’d like to fix this). We will get a response from Salesforce that a poll option has been updated and it will update the appropriate vote count on each button. You’ll notice that I had to resort to document.getElementById to find the area of the component I wanted to update. I tried all manner of component.get/component.find but the count element never got found. I’m sure it was something simple, but again — I went with what worked for me.

There is still much I’d like to take the time to work on, but I’d like to think (barring any code issues posted here) that I’ve left you with something you can toy with and learn. I hope I may have cleared up some of the mud that many of us have been trudging through learning our way around lightning components. Its a major change from how we’ve been doing things, but in the right hands — it opens up a whole new world of possibilities.

For convenience, I have the original — complete with comments/bad code, etc examples on github: https://github.com/lifewithryan/LightningPoll

Thanks for reading, let me know if I’ve helped in some way or if you have any questions, etc.

:wq!