Reid Carlberg

Connected Devices, salesforce.com & Other Adventures

Tag Archives: apex

Refactoring Visualforce Controllers for Lightning Components

1

You might remember Mo Tester, the package that helps you Experiment Faster on Force.com. I’m tweaking it for some Lightning Component stuff (spoiler alert) and ran into this.  (Posting to the Lightning Newbie Notes too.)

The following code won’t compile. You’ll get a handy Return type does not support AuraEnabled error because — wait for it — the @AuraEnabled annotation doesn’t support PageReference return types.

@AuraEnabled
public PageReference createMoTester1() {
    SmartFactory.FillAllFields = true;
    LAB_Mo_Tester_1__c t = (LAB_Mo_Tester_1__c) 
           SmartFactory.createSObject('LAB_Mo_Tester_1__c');
    insert t;
    return new PageReference('/'+t.id);
}

You need to refactor your Visualforce controller to have two methods, one for your existing code, one for your new Lightning Component.  Also, the @AuraEnabled method needs to be static.

public PageReference createMoTester1() {
    Id myMo = LAB_CreateDataHelper.getNewMoTester1Id();
    return new PageReference('/'+myMo);        
}    

@AuraEnabled
public static Id getNewMoTester1Id() {
    SmartFactory.FillAllFields = true;
    LAB_Mo_Tester_1__c t = (LAB_Mo_Tester_1__c) 
           SmartFactory.createSObject('LAB_Mo_Tester_1__c');
    insert t;  
    return t.id;                
}

Note that in this case, based on the way my test classes are structured, this didn’t affect my code coverage. It was 100% before, it’s 100% after.

Incidentally, not supporting PageReference return types is perfectly logical when you think about it.  PageReferences are a Visualforce thing after all.

Also note that, yes, I’m bewildered by my choice of method names here, too.  Also, the original package doesn’t seem to use this method.  And the test coverage in the package didn’t include coverage for the StandardController and StandardSetController dependent constructors. What was I thinking???

Looking at old (ish) code is fun.

Update 2/23/15 — I did use the button, on the list view.  I just didn’t inspect hard enough.  Phew & Sheesh!mo-tester-button-found

 

Also the method has to be static.  Whoops!

AngularJS & Remote Actions: The Simplest Possible Visualforce Page

3

The other day, spurred on by Mr. Ward’s Building Salesforce1 Mobile Apps with Visualforce, AngularJS, and Bootstrap, I became curious about whether or not I could mix Remote Actions in with his code, and it turns out I can.  And more than that — it turns out to be pretty sweet!

I’ve included a video demo at the bottom of the post and you can install these pages into your developer edition from this unmanaged package.  Note that once you install these into your org, you’ll want to add them to your Mobile Navigation in order to see the Salesforce1 #awesomesauce.  Code is also on github.

The foundations of Remote Actions are simple.  You create an Apex class that performs the operation you want, and then use a @RemoteAction annotation to expose a particular method to the JavaScript Remoting framework.

global class SampleRemoteActionPageController {
    @RemoteAction
    global static List myContacts() {
        return [select id, name, email from Contact Order By LastModifiedDate DESC LIMIT 200];
    }
}

You then create a simple page that calls that remote action.

<apex:page controller="SampleRemoteActionPageController" docType="html-5.0">
 <apex:stylesheet value="//cdn.jsdelivr.net/webjars/bootstrap-sf1/0.1.0-beta.6/css/bootstrap-namespaced.css"/>
 <apex:includeScript value="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.11/angular.min.js"/>
 
 <script>
 var app = angular.module("ngApp", []); 
 app.controller("ContactCtrl", ["$scope", function($scope) {
   $scope.contacts = [];
 
   $scope.getContacts = function() {
     Visualforce.remoting.Manager.invokeAction(
     '{!$RemoteAction.SampleRemoteActionPageController.myContacts}', 
     function(result, event) {
       $scope.contacts = result;
       $scope.$apply();
     }); 
     }
   }]);
 </script>
 <div class="bootstrap" ng-app="ngApp" ng-controller="ContactCtrl" >
 
 <h1 align="center">Click The Button</h1>
 <button ng-click="getContacts()" class="btn btn-lg btn-default btn-block">Get Contacts</button> 
 
 <p>
 <ul>
 <li id="{{current.Id}}" ng-repeat="current in contacts" ng-class-even="'rowEven'">{{current.Name}}</li>
 </ul>
 </p>
</div>
</apex:page>

I love the simplicity and flexibility I’m seeing here.

Just for fun, I also created a demo using the new Remote Objects functionality that’s still in developer preview.  It also works well in both the desktop browser and Salesforce1 mobile app.  Checkout the video below!

 

Salesforce Apex RSS Reader Featuring XMLStreamReader

0

For reasons I won’t go into (but which are probably obvious when you look at the code) I needed a simple way of importing RSS feeds into Salesforce. Although I have seen others, I decided to create a new one using XMLStreamReader that includes test coverage and which is installable via an unmanaged package.

Why XMLStreamReader instead of the oft used XMLNode? Well, in the case of RSS, XMLStreamReader is great because it’s a read-only interface, which is all I want to do. This keeps everything super fast and efficient, and pretty easy to test.

The heavy lifting is done in the BlogLog_RssReader class. As a developer, you simply send the raw text representation of the RSS XML into the read method, and it returns a List of Blog_Entry__c objects.

Here’s the meat:

    public List<Blog_Entry__c> read(String document) {
        List<Blog_Entry__c> ret = new List<Blog_Entry__c>();
        boolean isSafeToGetNextXmlElement = true;

        XmlStreamReader reader = new XmlStreamReader(document);

        while(isSafeToGetNextXmlElement) {
            if (reader.getEventType() == XmlTag.START_ELEMENT) {
                System.debug('^^^^' + reader.getLocalName());
                if ('item' == reader.getLocalName()) {
                    Blog_Entry__c item = parseItem(reader);
                    ret.add(item);
                }
            }
            // Always use hasNext() before calling next() to confirm 
            // that we have not reached the end of the stream
            if (reader.hasNext()) {
                reader.next();
            } else {
                isSafeToGetNextXmlElement = false;
                break;
            }
        }        

        return ret;
    }

    Blog_Entry__c parseItem(XmlStreamReader reader) {

        Blog_Entry__c ret = new Blog_Entry__c();
        boolean isSafeToGetNextXmlElement = true;

        while(isSafeToGetNextXmlElement) {
            if (reader.getEventType() == XmlTag.END_ELEMENT &&
               'item' == reader.getLocalName()) {
                isSafeToGetNextXmlElement = false;  
                   break;
            }
            if (reader.getEventType() == XmlTag.START_ELEMENT) {
                System.debug('****' + reader.getLocalName() + '~~~~' + reader.getNamespace());
                if ('title' == reader.getLocalName() && reader.getNamespace() == null) {
                        String title = parseString(reader);
                        ret.Title__c = title;
                }
                if ('link' == reader.getLocalName() && reader.getNamespace() == null) {
                        String link = parseString(reader);
                        ret.Link__c = link;
                }
                if ('origLink' == reader.getLocalName()) {
                        String link = parseString(reader);
                        ret.Link__c = link;
                }
                if ('creator' == reader.getLocalName() ) {
                        String author = parseString(reader);
                        ret.Author__c = author;
                } 
                if ('category' == reader.getLocalName() ) {
                        String category = parseString(reader);
                    if (ret.Category__c != null) {
                        ret.Category__c = ret.Category__c + ', ' + category;
                    } else {
                        ret.Category__c = category;
                    }
                    if (ret.Category__c.length() > 250) {
                        ret.Category__c = ret.Category__c.substring(0,249);
                    }
                }  
                if ('pubDate' == reader.getLocalName() ) {
                        String pubDate = parseString(reader);
                        ret.Published__c = convertRSSDateStringToDate(pubDate);
                } 
                if ('description' == reader.getLocalName() ) {
                        String description = parseString(reader);
                        ret.Lead_Copy__c = description;
                }                 

            }
            // Always use hasNext() before calling next() to confirm 
            // that we have not reached the end of the stream
            if (reader.hasNext()) {
                reader.next();
            } else {
                isSafeToGetNextXmlElement = false;
                break;
            }
        }        

        return ret;

    }

    String parseString(XmlStreamReader reader) {
        String ret = '';

        boolean isSafeToGetNextXmlElement = true;
        while(isSafeToGetNextXmlElement) {
            System.debug('****EVENTTYPE' + reader.getEventType());
            if (reader.getEventType() == XmlTag.END_ELEMENT) {
                break;
            } else if (reader.getEventType() == XmlTag.CHARACTERS) {
                System.debug('****Characters |' + reader.getText() + '|');
                ret = ret + reader.getText();
            } else if (reader.getEventType() == XmlTag.CDATA) {
                System.debug('****CDATA');
                ret = reader.getText();
            }
            // Always use hasNext() before calling next() to confirm 
            // that we have not reached the end of the stream
            if (reader.hasNext()) {
                reader.next();
            } else {
                isSafeToGetNextXmlElement = false;
                break;
            }
        }
        return ret.trim();
    }

You get the idea. Read method takes the whole document and looks for an item. ParseItem looks for the individual fields I care about, and ParseString gets the code out of those fields. Good times.

Code is on Github. As always, you can get a free Developer Edition with a few quick clicks.

Improvements? Comments? Let me know.

%d bloggers like this: