Wednesday, 5 September 2012

Inner Wrapper Class in Apex Class

Hi,

I have seen a lot of questions regarding inner class in a apex class in developer community and issues that can be solved by use of inner class. So here I would like to show a problem which can also be solved by inner class.

Syntax of inner class :

  1. public class myOuterClass {  
  2.    // Additional myOuterClass code here    
  3.       
  4.    class myInnerClass {  
  5.      // myInnerClass code here    
  6.       
  7.    }  
  8. }  

Problem : 
http://boards.developerforce.com/t5/General-Development/When-i-click-on-add-button-i-need-to-display-one-more-textbox/td-p/286261

When i click on add button i need to display one more textbox 

Visual Force Page :
  1. <apex:page controller="addTextrBox">  
  2.   <apex:Form>  
  3.        <apex:commandButton Value="Add Text Box" action="{!addTextBox}"/>  
  4.          
  5. </br>  
  6.        <apex:repeat value="{!listvalueOfTextBox}" var="item" rendered="{!IF(listvalueOfTextBox.size > 0 , true , false)}" >  
  7.            <apex:outPutLabel value="{!item.textBoxLabel}">  
  8.            </apex:outPutLabel>  
  9.            <apex:inputText value="{!item.textBoxValue}"/>  
  10.              
  11. </br>  
  12.        </apex:repeat>  
  13.    </apex:Form>  
  14. </apex:page>  

Apex Controller Class

  1. public class addTextrBox   
  2. {  
  3.   
  4.     public List<textBoxClass> listValueOfTextBox  
  5.         {   
  6.           get;   
  7.           set;   
  8.         }      
  9.     public addTextrBox ()  
  10.         {  
  11.             listvalueOfTextBox = new List<textBoxClass>();  
  12.         }  
  13.     public PageReference addTextBox()   
  14.         {  
  15.             try  
  16.                 {  
  17.                     listvalueOfTextBox.add(new textBoxClass('TextBox' + (listvalueOfTextBox.size() +  1)));  
  18.                 }  
  19.             catch(Exception e)  
  20.                 {  
  21.                     ApexPages.addMessages(e);  
  22.                 }  
  23.             return ApexPages.currentPage();  
  24.         }  
  25.   
  26.      public class textBoxClass  
  27.          {  
  28.              public string textBoxLabel{get;set;}  
  29.              public string textBoxValue{get;set;}  
  30.                
  31.              public textBoxClass(String textBoxLabel)  
  32.                  {  
  33.                      this.textBoxLabel = textBoxLabel;  
  34.                  }  
  35.          }  
  36. }  

Here textBoxClass is the inner class

In above problem we used iner class now any new text box is binded with a new item in the list listvalueOfTextBox which is of type textBoxClass.

inner class acan be intancitiated like this
outerClass.innerclass instanceOfinnerClass = new outerClass.innerclass();

in above case it will be like
addTextrBox.textBoxClass instaceOftextBoxClass = new addTextrBox.textBoxClass('Test text Box');

Regards,
Ashok.

Examples of Workflow Rules in Salesforce


Examples of Workflow Rules

Available in: EnterpriseUnlimitedDeveloper, and Database.com Editions

User Permissions Needed
To create or change workflow rules, email alerts, tasks, field updates, and outbound messages:


“Customize Application”
     You Want to WorkFlow Rules Example  Click Here

Tuesday, 4 September 2012

Uploading Multiple Attachments via Visualforce

I've lost count of the number of times I've implemented an attachments page in Visualforce, sometimes for a single file, sometimes for a finite number and once or twice for an indeterminate number.  Following the principles of DRY, I decided to create a custom component that would allow me to attach files to any specified parent sobject with Add More functionality that allowed me to upload several files at once.  As its easy to forget what attachments you have already uploaded, I also wanted the existing attachments to be listed. Below is a screenshot of the completed component:



The component has a mandatory attribute named "sobjId" - this is the id of the sobject that the uploaded files will be attached to. Note that you can't use the name "id" for the attribute, as this is a standard attribute that allows the component to be identified by other components in the page.

The Attachment sobjects backing the input file elements are stored in a list in the controller that is initialised in the constructor to contain a single element.  Clicking the Add More button causes another five Attachment sobjects to be appended to the list.  As the user can choose to fill in the attachments in any order (e.g. just populating the last two elements), we can't simply insert the attachments when the save button is clicked, as that will attempt to insert the empty attachments that the user hasn't supplied details for.  Thus we iterate the list and only insert those Attachments that have the Body field populated. 

Note that there is no field for the user to input the name of the Attachment - I'm using the filename for the this, as the filename extension gives a browser a good chance of automatically opening the attachment when it is downloaded. 

The Done button simply takes the user back to the view page for the object matching the supplied id attribute.

There's a couple of wrinkles when dealing with file uploads:

  • When the page initially renders with one file chooser, if you choose a file and then click the Add More button, the chooser will not remember the file that you chose.  This is browser behaviour, as it is not possible to prefill a file chooser under any circumstances - otherwise malicious pages could upload files from your hard disk without your consent.
  • The body of the uploaded file is stored as a Blob.  To unit test code that handles file uploading, you can create a Blob from a String using the valueof method:

    ?
    1
    Blob bodyBlob=Blob.valueOf('Unit Test Attachment Body');
The code (including unit tests!) and Visualforce can be downloaded here.  Simply unzip into the src directory of your org in the Force.com IDE.

Note that when using the example page, you'll need to supply the id of an existing sobject (Account, Opportunity etc).

Thanks,
Ashok.

Monday, 3 September 2012

Automatic Dashboard Refresh

This weeks post concerns a piece of functionality that I've been struggling with for ages, and I'm quite pleased with myself to have it working.  It's a feature that is requested time and time again by clients - a dashboard that refreshes itself.

The sticking point for this has always been that a Visualforce component is required to add logic to a dashboard, but as Visualforce pages are contained in an iframe served from a different host to the dashboard page, it isn't possible to navigate to the Refresh button and click it programmatically - the browser blocks this as a cross-site scripting attack.  There is another technique to embed Javascript into a Salesforce page via a sidebar component, but dashboard pages don't have a sidebar so that's out too.

Thus I needed to take an alternative approach to this and started to investigate effecting the refresh server side from Apex code.

The first thing that I decided I needed to get at was the ID of the dashboard itself.  This is encoded in the dashboard URL, an example from my dev org is : https://na6.salesforce.com/01Z80000000lf7nEAA, where 01Z80000000lf7nEAA is the ID.  Once again though, it isn't possible to interrogate the URL of the main window from a Visualforce component due to Cross Site Scripting.  Thinking back to my web development days at Olive Systems Limited, it struck me that the HTTP request headers for the Visualforce component might provide some useful information.  I therefore created a Visualforce page with a controller that traversed the HTTP headers and output the details to the System log:

?
1
2
3
4
5
6
Map<string, string=""> headers=ApexPages.currentPage().getHeaders();
for (String key : headers.keySet())
{
   System.debug('### ' + key + '=' + headers.get(key));
}
</string,>

I then added this page to my dashboard which gave the following output in the system log:

14:50:34.055 (55491000)|USER_DEBUG|[20]|DEBUG|### Accept=application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
14:50:34.055 (55645000)|USER_DEBUG|[20]|DEBUG|### Accept-Charset=ISO-8859-1,utf-8;q=0.7,*;q=0.3
14:50:34.055 (55790000)|USER_DEBUG|[20]|DEBUG|### Accept-Encoding=gzip,deflate,sdch
14:50:34.055 (55936000)|USER_DEBUG|[20]|DEBUG|### Accept-Language=en-GB,en-US;q=0.8,en;q=0.6
14:50:34.056 (56092000)|USER_DEBUG|[20]|DEBUG|### CipherSuite=RC4-MD5 TLSv1 128-bits
14:50:34.056 (56239000)|USER_DEBUG|[20]|DEBUG|### Connection=keep-alive
14:50:34.056 (56388000)|USER_DEBUG|[20]|DEBUG|### Host=kab-tutorial.na6.visual.force.com
14:50:34.056 (56532000)|USER_DEBUG|[20]|DEBUG|### Referer=https://na6.salesforce.com/01Z80000000lf7nEAA
14:50:34.056 (56677000)|USER_DEBUG|[20]|DEBUG|### User-Agent=Mozilla/5.0 (X11; Jolicloud Linux i686) AppleWebKit/534.24 (KHTML, like Gecko) Joli OS/1.2 Chromium/11.0.696.14 Chrome/11.0.696.14 Safari/534.24
14:50:34.056 (56823000)|USER_DEBUG|[20]|DEBUG|### X-Salesforce-Forwarded-To=na6.salesforce.com
14:50:34.056 (56967000)|USER_DEBUG|[20]|DEBUG|### X-Salesforce-SIP=80.176.152.54

and I have a result - the Referer header shows the full URL of the dashboard that my Visualforce component has been added to.

Referer=https://na6.salesforce.com/01Z80000000lf7nEAA

The next hurdle was how to programmatically trigger a dashboard refresh.  Inspecting the Refresh button didn't help much - it was tied to a bunch of Javascript that wouldn't help me server side.   Using the Fiddler web debugger, I clicked the Refresh button and monitored the requests that were submitted to the Salesforce server with fingers crossed that it would be a GET rather than a POST request.  Once again I had a result and could see that one of the requests was an HTTP GET on:


https://na6.salesforce.com/dash/dashboardRefresh.apexp?id=01Z80000000lf7nEAA.

I copied this URL and submitted it directly via the browser. The first attempt returned a minimal page body that the refresh had been submitted - not quite what I had in mind. Upon requesting the page again, I received another small page, but this time telling me that the dashboard had been refreshed. Opening the dashboard page in another tab showed that the refresh had taken place successfully. 

At this point I started to think that this might be possible after all!

Next up I added code to my controller to retrieve the referrer and extract the id.  Upon adding this to my dashboard it threw an error.  Not quite what I had in mind.  The reason for this turned out to be simple - the URL for a dashboard edit page is in a different format to the view.  A similar issue was waiting when the dashboard was embedded in a Home page - in this case the Referer header wasn't present.  Luckily these were straightforward to detect and put out a message that the dashboard wouldn't refresh.

I then created an action method that would carry out the HTTP GET, by creating a PageReference to the  refresh page identified above, adding the id to the parameters and executing the getContent() method.  This code is inside a loop, which will make a maximum of 10 attempts to refresh before giving up.  An actionFunction on the page in association with a Javascript setTimeout() invoked the refresh action method 60 seconds after the page was loaded.   Finally, using a technique outlined in an earlier post from this blog, I was able to automatically reload the dashboard after the refresh. 

Refreshing after 60 seconds seemed far too arduous for the Salesforce servers, so I upped the timeout to one hour.  This introduced the next issue - session timeout.  The next version reduced the timeout to 60 seconds, but for the first 59 times the timeout expired, the page simply refreshed.  Only on the 60th did the dashboard actually refresh.  This nicely circumvented the session timeout, as the page was submitted every minute.

Finally, I wanted to give the users a visual indication that the dashboard would automatically refresh, resulting in a recurring timeout that fired every second and counted down the seconds until refresh.

While this is probably starting to sound like quite a lot of code, there isn't actually that much to it.

Controller:

?
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
74
75
76
77
78
79
80
81
////////////////////////////////////////////////////////////
//
// Custom controller for dashboard refresh Visualforce page
//
// Author: Keir Bowden
//
////////////////////////////////////////////////////////////
public with sharing class DashboardRefreshController
{
 public Boolean needsRefresh {get; set;}
 public Boolean canRefresh {get; set;}
 public Id dbIdent {get; set;}
 public Integer minutes {get; set;}
  
 public DashboardRefreshController()
 {
  needsRefresh=true;
  setup();
  minutes=59;
 }
  
 public void setup()
 {
  Map<String, String> headers=ApexPages.currentPage().getHeaders();
  String referrer=headers.get('Referer');
   
  if (null==referrer)
  {
   canRefresh=false;
  }
  else
  {
   Integer lastSlashPos=referrer.lastIndexOf('/');
   lastSlashPos++;
   Integer paramPos=referrer.indexOf('?', lastSlashPos);
   
   
   String result='';
   if (-1!=paramPos)
   {
    result=referrer.substring(lastSlashPos, paramPos);
   }
   else
   {
    result=referrer.substring(lastSlashPos);
   }
    
   try
   {
    dbIdent=result;
    canRefresh=true;
   }
   catch (Exception e)
   {
    canRefresh=false;
   }
  }
 }
  
 public PageReference refreshDashboard()
 {
  minutes--;
  if (-1==minutes)
  {
   needsRefresh=false;
   String refUrlStr='/dash/dashboardRefresh.apexp?id='+dbIdent;
   Boolean refreshed=false;
   Integer idx=0;
   while ( (!refreshed) && (idx<10) )
   {
    PageReference pr=new PageReference(refUrlStr);
    Blob body=pr.getContent();
    String bodyStr=body.toString();
    refreshed=(-1!=bodyStr.indexOf('Last refreshed'));
    idx++;
   }
  }
    
  return null;
 }
}

Page:


?
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
<apex:page sidebar="false" showheader="false" standardstylesheets="false" controller="DashboardRefreshController">
 
<apex:outputPanel rendered="{!canRefresh}">
 <apex:form >
     <apex:actionFunction name="doRefresh" action="{!refreshDashboard}" />
     <apex:outputPanel id="detail">
   <div id="countDown"></div>
     </apex:outputPanel>
 </apex:form>
 
 <apex:outputPanel id="scriptPanel">
     <apex:outputPanel rendered="{!needsRefresh}">
   <script>
    window.onload = function()
    {
     startCountDown(59, 1000, doRefresh);
    }
 
    function startCountDown(i, p, f)
    {
     var pause = p;
     var fn = f;
     
     var countDownObj = document.getElementById("countDown");
     if (countDownObj == null)
     {
      alert("div not found, check your id");
      return;
     }
     
     countDownObj.count = function(i)
     {
      countDownObj.innerHTML = 'Refreshing in {!minutes} minutes ' + i + ' seconds';
      if (i == 0)
      {
       fn();
       return;
      }
      setTimeout(function()
       {
        countDownObj.count(i - 1);
       },
       pause
      );
     }
   
     countDownObj.count(i);
    }
   </script>
     </apex:outputPanel>
      
  <apex:outputPanel rendered="{!NOT(needsRefresh)}">
   <script>
       window.top.location='/{!dbIdent}';
   </script>
     </apex:outputPanel>
 </apex:outputPanel>
 
</apex:outputPanel>
<apex:outputPanel rendered="{!NOT(canRefresh)}">
   Edit mode/home page - refresh disabled
</apex:outputPanel>
 
 
</apex:page>

Below is a screenshot of the page embedded in a dashboard:


Once the countdown reaches 0 minutes and zero seconds, the page is automatically refreshed and reloaded to show the updated details.

A couple of words of caution:


  • The page that I'm hitting to refresh isn't documented by Salesforce, which most likely means it is unsupported.  Thus a future release could easily break this functionality.
  • I haven't tried this on a hugely complex dashboard, so I don't know what would happen if the dashboard took significant time to refresh.
  • This is using the browser to trigger the refresh, so if you close the browser session, the refresh won't happen.
  • Refreshing dashboards pulls in information from a number of sources, so refreshing with a short time interval will put additional strain on the Salesforce servers.  
  • I've only tested this with google chrome, so it may not work with other browsers.