Pages

Wednesday, March 29, 2017

Simple way to generate XML, parse and retrieve values from XML

Use Case

We usually get a requirement to store Salesforce records in XML format. Later, we may need to parse the XML and retrieve the data from XML and store them into a Map for future use.

Solution

I have created this class to generate some hard coded data. Later through usable parseXML method that has been parsed.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class XMLParserClass
{
    public Map<String, String> xmlDataMap = new Map<String,String>();
    
    public String generateXML()
    {
        Dom.Document doc = new Dom.Document();
        Dom.Xmlnode rootNode = doc.createRootElement('TestReport', null, null);
        
        Dom.Xmlnode headerNode = rootNode.addChildElement('header', null, null);
        //assign header attributes
        headerNode.setAttribute('id', 'TEST1'); 
        
        Dom.Xmlnode childNode = headerNode.addChildElement('detail', null, null);               
        childNode.setAttribute('id','CHILD1');              
        childNode.setAttribute('Amount','1000');  
        
        String xmlString = doc.toXmlString();
        System.debug('xmlString =' + xmlString);
        return xmlString;
    }
    
 //generated xml
 /*
    <?xml version="1.0" encoding="UTF-8"?>
    <TestReport>
    <header id="TEST1">
    <detail id="CHILD1" Amount="1000" />
    </header>
    </TestReport>
    */
 
 
    public void parserXML(String toParse)
    {
        xmlDataMap = new Map<String,String>();
        DOM.Document doc = new DOM.Document();
        try{
            doc.load(toParse);
            DOM.XMLNode root = doc.getRootElement();
            traverseThroughXML(root);
        }catch(Exception ex){
            ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, ex.getMessage());
            ApexPages.addMessage(msg);
       }  
    }
    
    /**
     * traverseThroughXML
     * This method traverse through the xml, read the data from XML.
     * @param
     * @return void.
     */
    private void traverseThroughXML(DOM.XMLNode node) 
    {
        if (node.getNodeType() == DOM.XMLNodeType.ELEMENT) 
        {
            System.debug('node.getName()=' + node.getName());
            if(node.getName().equalsIgnoreCase('detail'))
            {
                if (node.getAttributeCount() > 0) 
                { 
                  xmlDataMap.put(node.getAttributeValue(node.getAttributeKeyAt(0), node.getAttributeKeyNsAt(0))
                                ,node.getAttributeValue(node.getAttributeKeyAt(1), node.getAttributeKeyNsAt(1)));
                }
            }
            for (Dom.XMLNode child: node.getChildElements()) 
            {
              traverseThroughXML(child);
            }
        } 
    }
}

Usage

1
2
3
4
XMLParserClass cls = new XMLParserClass();
String xmlStr = cls.generateXML();
cls.parserXML(xmlStr);
System.debug(cls.xmlDataMap);

Outcome in Debug Log


Sunday, March 26, 2017

Pass Collection to Visual Workflow, parse and save case records

Use Case

Business has a requirement to update multiple cases from List view. User will save multiple field values taking an input from screen and finally save those records.


User will click on "Update Field Values" button and following fields will be displayed on the screen.



User will enter Due Date and Status and finally save collection of records.

Solution

If the user wants to update single field then that can be achievable by inline editing. But if there are more than one fields to update then that can be achieved by using flow.

Create a List View Custom button and use this code to pass parameters to the flow.
vSelectedCaseIds which contains case ids collection which can be found from {!GETRECORDIDS($ObjectType.Case)}.
vCaseCount which contains number of selected case id count.
retURL where it will be landed after completion.




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{!RequireScript("/soap/ajax/29.0/connection.js")}

var caseObj = new sforce.SObject("Case");
var selectedCases = {!GETRECORDIDS($ObjectType.Case)}; //chosen records from list view checkboxes

//check at-least one record is selected
if (selectedCases[0] == null) {
   alert("You must select at least one record");
} else 
{
    var serverUrl = '{!$Api.Partner_Server_URL_260}';
    var position = {!FIND( '/services', $Api.Partner_Server_URL_260)};
    var base = serverUrl.substring(0,position-1);

    var url = base +encodeURI('/flow/Update_Field_Values?vSelectedCaseIds=' + selectedCases +  '&vCaseCount=' + selectedCases.length +  '&retURL=/500/o');

    window.open(url, '_self');
}

The complete flow will look like this:




Step by step implementation:

1. Screen


2. Decision - check counter size


3. Assignment - Retrieve Single Case Id from param


caseIdFormula:

LEFT({!vRemainingCaseIds}, 15)



4. Assignment - Assign values to Case Object



Where varSObjectCase is SObject input and output variable.

5. Assignment - Add All Case Objects



Where All_Case_Sobjects is SObject Collection variable


6. Assignment - Retrieve Remaining CaseIds



Remaining_CaseIds - Formula


TRIM(
RIGHT({!vRemainingCaseIds}, (LEN({!vRemainingCaseIds})-16))
)

7. Decision - If Counter less than vCaseCount


8. Fast Update



9. Success






Conclusion

I have tried to put a solution through flow instead of using visualforce page.

Since this type of complete implement is not readily available at google, that's why I have tried my best put it in a proper way.

Have fun and if it is helpful then share with your friends.

Sunday, March 19, 2017

Efficient coding using Group By Rollup

I have come across this use case recently during answering a question in Salesforce stackexchange.

Use Case

Family records are being captured as follows:


Requirement is to show repetitive summations of Weight and Workout_hours both at GrandParent and Parent level.

Expected result should look like this:

Solutions

I could think of preparing the data using GROUP BY ROLLUP.

SOQL will look like this:


SELECT GrandParent__c, Parent__c, Child__c, SUM(Weight__c), SUM(Workout_Hours__c) ,
GROUPING (GrandParent__c) grpGrantPt, GROUPING(Parent__c) grpPT
FROM Family__c
GROUP BY ROLLUP(GrandParent__c, Parent__c,Child__c)
ORDER BY GrandParent__c, Parent__c, Child__c

Query Result

The above query returns expected result, only thing I need to eliminate first record which is giving total summation. and two columns GROUPING (GrandParent__c) grpGrantPt, GROUPING(Parent__c) grpPT which are not needed to show in visualforce.



Controller


Now it is time to prepare the data so that it can be displayed into visualforce in a most efficient way. For this I have taken a help of Wrapper class (FamilyData)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class FamilyClass
{
    public List<FamilyData> lstFamilyRecord {get;set;}

    public PageReference displayData()
    {
        Boolean firstRecord = true;

        lstFamilyRecord = new List<FamilyData>();
         AggregateResult[] lstFamily = [SELECT GrandParent__c, Parent__c, Child__c, SUM(Weight__c) weightTotal, SUM(Workout_Hours__c) whTotal ,
                                        GROUPING (GrandParent__c) grpGrantPt, GROUPING(Parent__c) grpPT
                                        FROM Family__c
                                        GROUP BY ROLLUP(GrandParent__c, Parent__c,Child__c)
                                        ORDER BY GrandParent__c, Parent__c, Child__c];

        for(AggregateResult familyObj:lstFamily)
        {
            if(!firstRecord)
            {
                FamilyData wrapper = new FamilyData();
                wrapper.GrantParent = (String) familyObj.get('GrandParent__c');
                wrapper.Parent = (String) familyObj.get('Parent__c');
                wrapper.Child = (String) familyObj.get('Child__c');

                if(familyObj.get('GrandParent__c') !=null && familyObj.get('Parent__c') !=null && familyObj.get('Child__c') !=null)
                {
                    wrapper.isChildRecord = true;
                }
                else if (familyObj.get('GrandParent__c') !=null && familyObj.get('Parent__c') !=null && familyObj.get('Child__c') ==null)
                {
                    wrapper.isParentRecord = true;
                }
                else if(familyObj.get('GrandParent__c') !=null && familyObj.get('Parent__c') ==null && familyObj.get('Child__c') ==null)
                {
                    wrapper.isGrantParentRecord = true;
                }
                wrapper.Weight = familyObj.get('weightTotal')!=null? ((Decimal)familyObj.get('weightTotal')).intValue():null;
                wrapper.WorkoutHours = familyObj.get('whTotal')!=null? ((Decimal)familyObj.get('whTotal')).intValue() :null;
                lstFamilyRecord.add(wrapper);
            }
            firstRecord = false;
        }

        return null;
    }

    public class FamilyData
    {
        public String GrantParent {get;set;}
        public String Parent {get;set;}
        public String Child {get;set;}
        public Integer Weight {get;set;}
        public Integer WorkoutHours {get;set;}
        public Boolean isGrantParentRecord {get;set;}
        public Boolean isParentRecord {get;set;}
        public Boolean isChildRecord {get;set;}
    }

}

Visualforce



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<apex:page id="familyPage" controller="FamilyClass" action="{!displayData}" showheader="true" sidebar="false"> 
    <table id="familytable" border="1"> 
        <tr>
            <th>Parents</th>
            <th>Weight</th>
            <th>Work Hours</th>
        </tr>             
        <apex:repeat id="myRepeat" value="{!lstFamilyRecord}" var="key"> 
            <tr>
                <td>
                    <apex:outputText style="color: red;" value="{!key.GrantParent}" rendered="{!key.isGrantParentRecord}"/>
                    <apex:outputText style="color: blue;" value="{!key.Parent}" rendered="{!key.isParentRecord}"/>
                    <apex:outputText value="{!key.Child}" rendered="{!key.isChildRecord}"/>
                </td>
                <td>
                    <apex:outputText value="{!key.Weight}"/>
                </td>
                <td>
                    <apex:outputText value="{!key.WorkoutHours}"/>
                </td>
            </tr>                       
        </apex:repeat>
    </table>          
</apex:page>

Final Output

Conclusion

Usually, looking at the requirements we think of preparing the data and logic in apex or visualforce in a complex way. But if we can leverage SOQL logic efficiently like this we may end up nice implementation.

Sharing with you, if it helps!

Link to stackExchange question:
http://salesforce.stackexchange.com/questions/164382/how-to-iterate-repetitive-rows-in-table-to-calculate-count-and-show-the-sum/164394#164394