Search

Friday 18 November 2011

Serialize Batch Apex

In Force.com you cannot call a batch Apex class from another batch Apex class because batch Apex is a future call. However, you can use Database.Stateful and the finish() method to mimic serialization of batch processes.

After batch 1 is complete in the finish() method this calls startNewBatch() in GeneralUtils which fires the newbacth batch class


However, running this you will see an error
Database.executeBatch cannot be called from a batch or future method.


global class batch l implements Database.Batchable<sObject>, Database.Stateful{

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC,
List<sObject> scope){

}

global void finish(Database.BatchableContext BC){

     GeneralUtils.startNewBatch();
}

}

public class GeneralUtils{

 public static void startNewBatch(){
    newbacth batchable = new newbacth();
    Id
newbacthID = Database.executeBatch(batchable);
}

Tuesday 4 October 2011

This Is The Strangest Thing Ive Seen In Salesforce

So do we all agree that there's no problem if you want to insert a record on 1 object and after that has inserted correctly, no problem, you
immediately want to insert another record on a different object.

Wrong! You must be joking right. I do this all the time. Well there are certain times when this is not possible
I was making a unit test in a Sandbox and I was populating a custom setting with data. Then immediately after I was trying to insert
an Account record. Now I'd setup my test data I then performed my unit tests and ran the unit test in Eclipse. The unit test passed. Perfect.
Then later that day I was running All Tests in the same sandbox and to my surprise my new unit test failed. I knew there hadn't been any changes to code, meta data, data, or anything
on my sandbox. So after some investigation I came across this article

http://stackoverflow.com/questions/2387475/how-to-avoid-mixed-dml-operation-error-in-salesforce-tests-that-create-users

I also looked at
http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_dml_non_mix_sobjects.htm

From this information I wrapped System.runas around the insert statement that created the custom setting record to run this code as System Administrator, as shown below. The result was the unit test passed in both Eclipse and in the browser when I logged into Salesforce.

    Profile systProfileID = [Select id From Profile where Name Like 'System Administrator' LIMIT 1];
    list<User> us1 = [Select id From User where ProfileID=: systProfileID.id and IsActive=true limit 1];
   
    if (us1.size() > 0){
        System.runAs(us1[0]){
            //insert custom setting record
        }
    }

    //now you can safely insert any standard object record


This certainly qualifies as 1 of the most strangest bugs I've come across in Salesforce. Be aware!

Using The Builder Pattern

If you have a class which has specific methods, variables, properties which always need to be set and run and possibly not on the construction of the class, using the Builder Pattern is a very useful way to do this.
The Builder Pattern is a clearer and more efficient way of setting up the environment of a class.

If you were to set and run methods, variables, properties on construction of the class you might as well do this in the class's constructor method.
But if you wanted to set specific environment setting a bit later then builder methods are ideal

Here is the code of a class that uses the Builder Pattern
public class ThisClass{

private String HerName;
private String HerJob;
private String Herotherjob;

public ThisClass setName(String passName){
    HerName=passName;
    return this;
}

public ThisClass setJob(String passJob,String otherjob){
    HerJob=passJob;
    Herotherjob= otherjob;
    return this;
}

public ThisClass setJob(String passJob){
    HerJob=passJob;
    return this;
}

}


This invokes the Builder Pattern class above and uses the Builder Pattern on lines 2 and 3. As you can see all the work was done on just 1 line.
1. ThisClass cls = new ThisClass();

2. cls.setName('Liz').setJob('Secret Agent','Window Cleaner'); //This uses the 1st variety of setJob

3. cls.setName('Liz').setJob('Secret Agent'); //This uses the 2nd variety of setJob

For more information and a useful example on the  Builder Pattern see http://developer.force.com/cookbook/recipe/email-utility-class

Setting Up Salesforce To Salesforce

Note: not all records of objects can be shared using Salesforce To Salesforce

First of all lets get the basics out of the way. Follow the steps in

http://wiki.developerforce.com/index.php/An_Introduction_to_Salesforce_to_Salesforce

Now for some of the things that Salesforce don't tell you, or it is difficult to find information on.

Regarding the manual sharing records using  "Forward to connection" button. Well I've never personally found this button so if anyone does see this anywhere please tell me.


Sharing Accounts and Attachments Using Code
If you want to share attachments make sure that Setup > Security > 'HTML Documents and Attachments Settings' is not ticked otherwise you won't be able to share certain file types.

Attachments need to have a record associated with it, such as an Account record. You need first of all share the Account record with the other Salesforce orgs first before you can share any Attachments. So of course the donor needs to have Account set up in Connection > Published Objects

Here is some code which will allow you to share the Account and then the Attachment.


List<PartnerNetworkConnection> connMap = new List<PartnerNetworkConnection>();
connMap=[select Id, ConnectionStatus, ConnectionName from PartnerNetworkConnection   where ConnectionStatus = 'Accepted'] ;
Account acc =[Select id From Account where id = '<<Account ID>>'];
for(PartnerNetworkConnection network : connMap) {  
     PartnerNetworkRecordConnection newrecord = new PartnerNetworkRecordConnection();
    newrecord.ConnectionId = network.Id;
    newrecord.LocalRecordId = acc.id;
    newrecord.SendClosedTasks = true;
    newrecord.SendOpenTasks = true;
    newrecord.SendEmails = true;  
    insert newrecord;
}

Now that the Account has been shared you can share the Attachment.

Attachment att =[Select id,IsPartnerShared  From Attachment where Parentid='<<Account ID>>'];
att.IsPartnerShared =true;
update att;

If you want to share Attachments manually once the Account has been shared, click edit on the Attachment and a 'Share with Connections' tick box will now be available, tick this and the Attachment will now be shared.

You can setup the recipients to automatically accept shared records. If you do the records will become live automatically in the recipient org, otherwise you need to go to the Account tab, scroll down to  'Accounts from Connections' section, press Go will display any shared Accounts which you can now accept.

Thursday 1 September 2011

Stopping records being changed other than by Sys Admin Users

This as an example will prevent anyone other than System Administrators to update any Notes

trigger UpdateNotes on Note (before update) {

public class myException extends Exception{}

Profile profileID = [Select id From Profile where Name Like 'System Administrator' LIMIT 1];
list<User> thisUser = [Select id,ProfileID From User where ProfileID !=: profileID.id and IsActive=true and id =:userinfo.getUserId() limit 1];

 if (thisUser.size() > 0){
     //not sys admin
     throw new myException('Cannot update');
 }

}

Batch Apex Processing

This is based on a speech I made to the Salesforce London Group.
This is a brief introduction to batch processing


Batch Requirements

Every batch class requires 4 things. To implement Database.Batchable<sObject>, have a start(),execute() and finish() methods.

global class UpdatetFields implements Database.Batchable<sObject>{

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC,
List<sObject> scope){

}

global void finish(Database.BatchableContext BC){
}

}


Governor Limits

In total 50 million records can be processed by a batch class and 5 batches can be run simultaneously, giving a total of 250 million records that can be processed at 1 time.


Remember batch classes still need to adhere to governor limits, so if any soql’s are performed do remember that these soql’s need to adhere to governor limits

The following is likely to break governor limits because List<sObject> scope is likely to contain more than 200 records and so the soql in bold will run 200 times per batch that is processed.

global void execute(Database.BatchableContext BC,
List<sObject> scope){

ID thisCustID;
List<Customers__c> allsubs;

for (SObject sub: scope){
     thisCustID = (ID)sub.get(‘Customers__c');
     allsubs = [Select Name From Customers__c where id =: thisCustID];
}

}

Types of Batch Classes

Using Database.QueryLocator this passes an list of SObjects. This is the most common type used in batch processing. Or you can use Iterable which allows you to step through the records more easily, which is mainly used for more complex soql’s such as nested soql’s.

global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}

Or

global Iterable start(Database.BatchableContext BC){
return Database.getQueryLocator(query);
}


And


If your batch performs a callout you must implement Database.AllowsCallouts

global class SearchAndReplace implements Database.Batchable<sObject>,
Database.AllowsCallouts{
}


And


If you specify Database.Stateful in the class definition, you can maintain state across these transactions. This is can be used for example if you want to make some kind of summary, or total of the records processed, or could be used to summarise all the successful or failure transactions produced etc.

global class UpdatetFields implements Database.Batchable<sObject>, Database.Stateful{

}


Ensure Batches Don’t Try To Update The Same Records

Use AsyncApexJob to query when a batch has completed processing so you can run the next batch

ID batchprocessid = Database.executeBatch(reassign);
AsyncApexJob aaj = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors
FROM AsyncApexJob WHERE ID =: batchprocessid ];

If (aaj.Status == ‘Completed’){
      //run next batch
}
Else { }

Custom Schedule Classes

If you ever have to shceduled a class but provide a specific custom shcedule plan run a bit of code in the System Log, similar to

string CRON_EXP = '0 30 * ? * MON-FRI';
string jobid=System.Schedule('<<any name>>',CRON_EXP, new <<name of class>>());

This will run the class every 30 mins passed the hour from Mon-Fri

Changing the 1st line with
CRON_EXP = '0 0 * ? * MON-FRI';

will run the class on the hour 

More info for specific customisation at     http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_methods_system_system.htm


Tuesday 23 August 2011

The Future Of The Cloud

Yes the Cloud was a disruptive technology, but is it really a revolutionary concept or an evolutionary concept.
Many would believe it to be a revolutionary concept, but I differ, sorry Mr Benioff. It is evolutionary because really all that happens in the Cloud is that a computer has been picked up from 1 building and put in another. Yes, there is a little more to it than that, yes it provides SaaS and PaaS etc. But to consider it to be revolutionary it is not. It's not like moving from gas burning lights to electric ones, which is truly revolutionary.
Don't get me wrong I'm a massive advocate of the Cloud and wholly believe in it. Actually its massive success is partly due to it being an evolutionary concept rather than a revolutionary one, because it is easier to adopt evolutionary concepts.

But how will the Cloud develop. Well we are already seeing multiple types of Clouds springing up, Salesforce, Cordys, Azure, Sugar CRM, Google, Citrix, Redhat etc. Organisations are now not just licensing multiple instances of Clouds, such as 2 Salesforce Clouds dealing with 2 separate sides of a business ( Finance / Customers / Suppliers etc ), or Salesforce Clouds in partnered organisations connecting and sharing information, but also different types of Clouds, Salesforce, Sugar, Google etc communicating together in a thunderstorm of Clouds.

But I believe the real advantage of the Clouds like Salesforce and how it will leverage new technologies is the fact that Salesforce enforces a uniform foundation and language, so communication and crowd sourcing is far easier. Because of this although technologies like Salesforce is not OpenSource I believe cross-Cloud communication and crowd sourcing will exponentially increase bringing new and amazing capabilities to the Cloud.

Sunday 21 August 2011

Able To Run As Another User

There are times that you don't want to give permissions to specific users permanently, so it would be useful if you could temporarily turn on and off running code under a certain user.

Eg: to allow users to create data in Custom Settings requires to either turn on Customize Applications permissions or to give Delegated System Admin privileges to Non System Admin users.

The only problem with Customize Applications and Delegated System Admin privileges is that it opens the door to greater permissions for those users.

The answer will be to temporarily give permissions in code using System.runas() but this is only allowed in testmethods

If it was possible to run code like below in normal code

Profile prID = [Select id From Profile where Name Like 'System Administrator' LIMIT 1];
        list<User> usr = [Select id From User where ProfileID=:prID.id and IsActive=true limit 1];//take 1 user which will always exist
        if (usr.size() > 0){
            system.runas(usr[0]){
                object1__c new
object1 = new object1__c ();
            }
        }
        else{
            ...
        }

Create Drop Down List From Picklists

Sometimes its not possible to use <apex:inputfield /> or
            <apex:selectList size="1"  id="dependentField" required="false" value="{!aTask.1stfield}" .>                 
to display values from a picklist.

So I wondered how do I find the values in a picklist through code and display them on the page.
The code below shows a function getTaskStatus() which uses the functions getTheseSelectOptions() and getThePicklists() which find values from any picklist field and exclude any values which you want to exclude.


    <apex:selectList size="1" required="false" value="{!aTask.2ndfield}">
                        <apex:selectOptions value="{!TaskStatus}" />
    </apex:selectList>


   public Task aTask {get;set;}
   public List<SelectOption> getTaskStatus() {
            //gets the picklists
            List<SelectOption> options = new List<SelectOption>();
                String[] excluded = new String[]{''};
                options = getTheseSelectOptions('<<object>>','<<picklist field>>',excluded,false);
         
            return options;
    }

public static List<SelectOption> getTheseSelectOptions(String thisObject, String thisField, List<String> excluded, Boolean optionalBlank) {

        List<SelectOption> options = new List<SelectOption>();
        if (optionalBlank)
            options.add(new SelectOption('' ,'' ));
           
        try{
            List<Schema.PicklistEntry> returnPicks = getThePicklists(thisObject, thisField);
            if (returnPicks == null)
                return null;
             
            String thisPickValue;
            Boolean ExclusionExists;
            for (Schema.PicklistEntry EachVal: returnPicks){
                thisPickValue = String.valueof(EachVal.getValue());
             
                //search each Exclusion for this thisPickValue
                for (String EachExclusion: excluded){
                    if (thisPickValue != EachExclusion)
                        options.add(new SelectOption(thisPickValue,thisPickValue)); //add thisPickValue if it is not in the Exclusion
                }                 
            }
        }
        catch(Exception e){}
        return options;  
 
}


public static List<Schema.PicklistEntry> getThePicklists(String thisobject,String thisFieldName){ 

    List<Schema.PicklistEntry> picklists;
    try{
        Map<String, Schema.SObjectType> thisPickListMap = Schema.getGlobalDescribe();
        Schema.SObjectType myObjectType = thisPickListMap.get(thisobject) ;
        Schema.DescribeSObjectResult objResult = myObjectType.getDescribe() ;
        Map<String, Schema.SObjectField> fields = objResult.fields.getMap() ;

        Schema.SObjectField selectedObjectFld = fields.get(thisFieldName) ;
        Schema.DescribemyfldResult myfldResult = selectedObjectFld.getDescribe() ;
     
        picklists = myfldResult.getThePicklists();
     }
    catch(Exception e){}
    return picklists;
}


Wednesday 17 August 2011

How To SOQL On ActivityHistory

ActivityHistory is one of those rare objects that you cannot directly run a soql statement on.
 eg: Select WhatId,CreatedDate From ActivityHistory];  is not allowed

So the trick is to place the soql within a nested soql. The Account object has a direct lookup field in the Task object, so the outer part of the soql can query the Account object first.

But say for instance you are interested on information in the history for Tasks related to another object, such as the Opportunity object. To get this information the following code could be used to grab this information.

Set<Id> opportunityIDsSet = new Set<Id>();
opportunityIDsSet.add(<<populate set with Opportunity IDs);

Map<ID,Datetime> oppMap = new Map<ID,Datetime>();
for (Account eachtaskhist: [Select id, (Select WhatId,CreatedDate From ActivityHistories where WhatId In :opportunityIDsSet << YOU COULD SET MORE SPECIFIC WHERE CRITERION AS YOU REQUIRE >>) From Account])
                 oppMap.put(eachtaskhist.ActivityHistories[0].WhatId,eachtaskhist.ActivityHistories[0].
                 CreatedDate);


oppMap will contain a Map of (<<Opportunity IDs>>, <<Date of ActivityHistory Was Created>> )

Just a note on this however, there are certain objects in Salesforce that cannot be soqled on by users  other than by System Administrator users, ActivityHistory is one of those objects. So make sure you remove with Sharing in your classes.

Nice!!

Tuesday 16 August 2011

Correct Way For Creating Unit Tests

When you deploy code to live your unit tests may work because there is data in the live environment. But, lets say a load of data is archived away, or as the status’s of data can change from say Pending to Active for example etc, maybe your soql in your unit tests which normally would return data it now doesn’t. So if the data is not there and your unit tests don’t create data then your units tests will fail and you won’t be able to deploy anything to live until its fixed.
Also if you refresh a sandbox, something you should do frequently; not all the data from live will populate your sandbox. So there is likely to be missing data. When you then start working on an existing class and you want to see if the unit tests pass, but they fail, because there’s no data in the sandbox and your test classes haven’t created test data. So you either have to find which data needs to be inserted and add the data to the sandbox, which you will have to remember to do this every time you refresh your sandboxes, or change the unit tests so that they create test data, the latter being the best option.
So as you develop you must deploy your code to an empty Sandbox where there is no data and run all your unit tests to prove that your unit tests create test data correctly.
So your unit tests must search for existing data and if it doesn’t find the data it must create the test data. So you might have something like this:

Function void createdata(){
list<Testdata__c> defaultEmail = new list<Testdata__c>();
defaultEmail = [Select ID__c,DefaultValue__c From Testdata__c Where ID__c='Default'];
    
      //if soql returns nothing then create otherwise update
      if (defaultEmail.size() == 0){
//data doesn’t exist so create new data
Testdata__c newTestdata = new Testdata__c(ID__c='DefaultVal',DefaultValue__c='test@aol.com');
          
            insert newTestdata;   
      }
      else{
            //data exists so update it
            defaultEmail[0].DefaultValue__c = 'test@aol.com';
            update defaultEmail;
      }
}
    
      This code will not go in the unit test class, but in a separate class dedicated to creating test data so that this class can be used and shared by multiple test classes, lets call this class TestDatasCreation class. The only problem with this TestDatasCreation class is that it must also have unit test coverage. This is because the data will either exist or not exist in live, so without the TestDatasCreation  class having its own unit test you will only test 1 part of the if statement block in the code above, where data exists or doesn’t.
      To make sure that both if statement blocks are tested you could delete the data in the Testdata__c first to ensure that the 1st part of the if statement block is tested.

Delete defaultEmail; ( remove the data )
However in some objects deleting all the data in live this could break governor limits and also could cause your unit tests to take longer to run. Remember all unit tests are run when deploying code so delete statements like that will slow down deployments.
Instead if we change how the function is setup:

Function void createdata(String searchValue){
list<Testdata__c> defaultEmail = new list<Testdata__c>();
defaultEmail = [Select ID__c,DefaultValue__c From Testdata__c Where ID__c=: searchValue];

…..

}

Now if we assume that normally the 2nd part of the if statement block is tested by unit tests already created. To ensure that the TestDatasCreation class has also full unit test coverage:

      //delete data first
list<Testdata__c> defaultEmail = new list<Testdata__c>();
defaultEmail = [Select ID__c,DefaultValue__c From Testdata__c Where ID__c='Default'];

            Delete defaultEmail;

            //1st part if statement block runs
Createdata(‘default’);

defaultEmail = new list<Testdata__c>();
defaultEmail = [Select ID__c,DefaultValue__c From Testdata__c Where ID__c='Default' limit 1];
    
//test created data
system.assert(defaultEmail[0].ID__c,'DefaultVal');


Of course the unit test for the TestDatasCreation class could be created to test both parts of the if statement block.

This will now give 100% test coverage for the TestDatasCreation class.

Using ObjectType

You can use ObjectType to easily display certain information on VF pages

The general format is
{!$ObjectType.<<object>>.<<field>>.<<property>>}

Display the plural text of an object
{!$ObjectType.Task.LabelPlural}

Display the 1st part of an object records id
{!$ObjectType.Task.KeyPrefix}

Display the length of a field
{!$ObjectType.Task.Fields.Await_Call_Back_Time__c.Length}

Display the help text of a field
{!$ObjectType.Task.Fields.Await_Call_Back_Time__c.InlineHelpText}

Display the data type of a field
{!$ObjectType.Task.Fields.Await_Call_Back_Time__c.Type}

Display the label of a field
{!$ObjectType.Task.Fields.Await_Call_Back_Time__c.Label}

Also for field sets
http://www.salesforce.com/us/developer/docs/pages/Content/pages_dynamic_vf_field_sets.htm

Salesforce Dependent Picklists

Salesforce has forgot to allow dependent picklists to be interrogatible by Apex.

If you are wanting to display dependent picklists on a VF page using a standardcontroller it easy, just use inputFields:

<apex:page standardController="Account">
    <apex:form >
        <apex:pageBlock mode="edit">
            <apex:pageBlockButtons >
                <apex:commandButton action="{!save}" value="Save"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection title="Dependent Picklists" columns="2">
            <apex:inputField value="{!account.industry}"/>
            <apex:inputField value="{!account.subcategories__c}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
</apex:page>

However it becomes almost impossible when you use a custom controller

Options I considered was to use a javascript hack ( attached ), which although works on most pages the javascript reacts badly with the rendered visualforce elements especially when using rerenders etc
Code on VF page
    <apex:includeScript value="{!$Resource.Prototype}"/>  
    <apex:includeScript value="/soap/ajax/15.0/connection.js"/>
    <apex:includeScript value="{!$Resource.DependentPicklists}"/> 

                        <apex:outputLabel styleClass="RightBold">Status</apex:outputLabel>
                        <apex:inputField onclick="helper.handleControllerChange()" styleClass="LeftAlign"  id="controllingField" value="{!aTask.Status}"/>
                        <p></p>
      
                        <apex:outputLabel styleClass="RightBold">ControlledField</apex:outputLabel><br />
                        <apex:selectList id="dependentField" styleClass="LeftAlign" value="{!aTask.ControlledField__c}">
                            <apex:selectOption itemValue="None" />
                        </apex:selectList>

                        <script>
                             var dependentField = '{!$Component.dependentField}'; 
                             //alert(dependentField);
                            var controllingField = '{!$Component.controllingField}';             
                        </script>
                      
                        <script type="text/javascript">
                               var helper;                                 
                    
                               document.observe("dom:loaded", function(){
                                   helper = new DependentPicklistHelper(
                                       "{!$Api.Session_ID}","Task",
                                       "Status","ControlledField__c",
                                       controllingField ,dependentField
                                   );
                               }); 
                        </script>


I also looked at apex interrogating the dependent picklists to produce selectlists on the VF page, but Salesforce has just forgot to provide this capability. Hence, this is where the problem begins.

So I considered adding an apex iframe which embeds another VF page and the controller to this page sets static variables in another class. The controller of the page that has the embedded iframe then uses the same static variables
which would hold the same value already set. This option could well be a valid workaround in some scenarios.

However I opted for a slick solution using static resources. I upload a csv file containing the values for the dependent picklists. I created a function to read the static resource and create a Map of the values. So now I have a Map representing the
exact values of the dependent picklists. All I needed to do now is display the values on the VF page and make the selectlist for the controlling picklist and dependent picklist to be linked.

To see this in action have a look at the following functions
getTheseMappedProperties()
getTheseTaskStatus()
getControlFieldVals()

//Note you need to create a csv file with matrix for the dependent picklist values and upload to Static Resources


public List<SelectOption> ControlledFieldValues{get;set;}
public Task aTask {get;set;}

public static List<SelectOption> getMySelectOptions(String thisObject, String thisField, List<String> excluded, Boolean optionalBlank) {

        List<SelectOption> options = new List<SelectOption>();
        if (optionalBlank)
            options.add(new SelectOption('' ,'' ));
           
        try{
            List<Schema.PicklistEntry> returnPicks = getMyPickValues(thisObject, thisField);
            system.debug('returnPicks ' + returnPicks);
            if (returnPicks == null)
                return null;
              
            String PickVal;
            Boolean ExclusionExists;
            for (Schema.PicklistEntry EachVal: returnPicks){
                PickVal = String.valueof(EachVal.getValue());
              
                //search each Exclusion for this PickVal
                for (String EachExclusion: excluded){
                    system.debug('PickVal ' + PickVal + ' ' + EachExclusion);
                    if (PickVal != EachExclusion)
                        options.add(new SelectOption(PickVal,PickVal)); //add PickVal if it is not in the Exclusion
                }                  
            }
        }
        catch(Exception e){system.debug(' ' + e);}
        return options;   
  
}


public static List<Schema.PicklistEntry> getMyPickValues(String thisobject,String thisFieldName){  

    List<Schema.PicklistEntry> picklists;
    try{
        Map<String, Schema.SObjectType> thisPickListMp = Schema.getGlobalDescribe();
        Schema.SObjectType objType = thisPickListMp.get(thisobject) ;
        Schema.DescribeSObjectResult objResult = objType.getDescribe() ;
        Map<String, Schema.SObjectField> fields = objResult.fields.getMap() ;

        Schema.SObjectField selectedObjField = fields.get(thisFieldName) ;
        Schema.DescribeFieldResult fieldResult = selectedObjField.getDescribe() ;
      
        picklists = fieldResult.getMyPickValues();
     }
    catch(Exception e){system.debug(' ' + e);return null;}
    return picklists;
}


    public List<SelectOption> getTheseTaskStatus() {
            //gets the picklists
            List<SelectOption> options = new List<SelectOption>();
                String[] excluded = new String[]{''};
                options = getMySelectOptions('<<object>>','<<picklist field>>',excluded,false);
          
            return options;
    }


public List<StaticResource> thisStaticResource;

thisStaticResource = [Select Id, body, contentType, bodyLength, description,Name from StaticResource where Name=:fileName];

public Map<String, list<String>> getMapProperties() {
                                    Map<String, list<String>> propertiesMap;
                                    if(thisStaticResource == null || thisStaticResource.size() == 0) {
                ….
                                    }
                                                                      
                                    for(StaticResource sr : thisStaticResource) {
                                                Blob propBlob = sr.body;
                                                String bdy = propBlob.toString();
                                                List<String> propertyList = propBlob.toString().split('\r',0);

                                                propertiesMap = new Map<String, list<String>>();
                                                String KeyVal;
                                                for(String property : propertyList) {                                                 
                                                                        list<String> newLst = new list<String>();
                                                                        List<String> PicklistValues = property.split(',');

                                                                        KeyVal = PicklistValues[0];
                                                                        PicklistValues.remove(0);
                                                                        if(PicklistValues != null && PicklistValues.size() > 0)
                                                                                    propertiesMap.put(KeyVal,PicklistValues);
                                                }
                                    }
                                  
                                    //if it reaches here the value was not found
                                    return propertiesMap;
                                  
                        }


    public Pagereference getControlFieldVals() {
            List<SelectOption> options = new List<SelectOption>();              
            list<String> eachRow;
          
            if (mpResourceList != null){
                        if (aTask == null){
                                    eachRow = mpResourceList.get(<<status>>);
                                    for (String eachItem: eachRow){
                                                options.add(new SelectOption(eachItem,eachItem));
                                    }                                                        
                        }
                        else{
                                    eachRow = mpResourceList.get(aTask.Status);                                       
                                    for (String eachItem: eachRow){
                                                options.add(new SelectOption(eachItem,eachItem));
                                    }                                                        
                        }
            }
          
            ControlledFieldValues = options;
            return null;
    }
                      
}


<apex:outputPanel id="refreshControlledFieldResult">
            <apex:selectList size="1" required="false" value="{!aTask.Status}">
                        <apex:selectOptions value="{!TaskStatus}" />
                        <apex:actionsupport event="onchange" action="{!getControlFieldVals}" rerender="refreshControlledFieldResult"/>
            </apex:selectList>

            <apex:selectList size="1"  id="dependentField" required="false" value="{!aTask.2ndfield}">
                        <apex:selectOptions value="{!ControlledFieldValues}" />
            </apex:selectList>




I'm sure with a bit of intrepid versatility you will be able decipher the rest

Multiple Object Pagination on 1 VF Page

In Salesforce you can easily paginate records on a single object

<apex:page standardController="Account" recordSetvar = "accounts" >
  <apex:pageBlock title="ViewingAccounts">
   <apex:form id="theForm">
    <apex:pageBlockSection>
    <apex:dataList var="a" value="{!accounts}" type="1">
     {!a.name}
    </apex:dataList>
   </apex:pageBlockSection>
   <apex:panelGrid columns="2">
    <apex:commandLink action="{!previous}">Previous</apex:commandlink>
    <apex:commandLink action="{!next}">Next</apex:commandlink>
   </apex:panelGrid>
  </apex:form>
 </apex:pageBlock>
</apex:page>
I've highlighted the new bits in bold:

You can easily control the number of records displayed in each page (it defaults to 25), by writing an extension:
VF Page
<apex:page standardController="Account" extensions="tenPageSizeExt" recordSetvar
= "accounts" >

public class tenPageSizeExt {
  public tenPageSizeExt(ApexPages.StandardSetControllercontroller){
    controller.setPageSize(10);
  }
}


But the problem occurs when you want to include multiple objects on a single page and paginate each object independent of each other
To demonstrate this I've created a VF page called GeneralSearch and Controller called GeneralSearchController.

The key thing is to use multiple ApexPages.StandardSetController and to use Database.getQueryLocator to iterate through the records

String qry = 'select Id,Name from Account where ....';
ApexPages.StandardSetController  acct = new ApexPages.StandardSetController(Database.getQueryLocator(qry));

Here is some example code


public class EnumeratedValues {

public enum OpportunityEnum {Object1, Object2}
}

public with sharing class OppReporting {

      //iterators
      public ApexPages.StandardSetController object1{get; set;}
      public ApexPages.StandardSetController object2{get; set;}
    
      //the current page number for each object
      public integer object1Pg{get; set;}
      public integer object2Pg{get; set;}
    
      //display results
      public List<Opportunity> object1Return {get;set;}
      public List<Opportunity> object2Return {get;set;}

      //number of records to display comes from a custom setting
      public integer numberRecordsToDisplay{get;set;}
      public string setRecordsToDisplay{get;set;}
    
      //pages to display for Task
      public list<integer> object1StatusPagesToDisplay{get;set;}
      public integer selectedPageObject1{get;set;}
    
      //pages to display for object2 summary
      public list<integer> object2PagesToDisplay{get;set;}
      public integer selectedPageObject2{get;set;}

      private Set<Id> newIDsSet;
      public Map<ID,ID> object1IdToTaskID{get; set;}
      public ID selectedObject1{get; set;}
    
      //public Set<String> subjects;
      public list<Opportunity> opps{get; set;}
    
      //max pages to dislay
      public long numberOfPagesToDisplayObject1{get; set;}
      public long numberOfPagesToDisplayObject2{get; set;}
                
      public list<object1List> allObject1List{get;set;}{allObject1List = new list<object1List>();}
      public list<object2List> allObject2List{get;set;}{allObject2List = new list<object2List>();}
               
      public OppReporting(){    
            //get the number of records to display on each page
                  numberRecordsToDisplay = 10;          
      }

      //Task Navig
      public void moveToPageObject1() {
            object1.setPageNumber(selectedPageObject1);
            object1Pg = object1.getPageNumber();
            refreshOutput(EnumeratedValues.OpportunityEnum.Object1,object1);
      }

      //Object2 Summary Navig
      public void moveToPageObject2() {
            object2.setPageNumber(selectedPageObject2);
            object2Pg = object2.getPageNumber();
            refreshOutput(EnumeratedValues.OpportunityEnum.Object2,object2);
      }
    
      public PageReference doSearch() { 
            integer numberRecordsToDisplayTemp;//if this is not used numberRecordsToDisplay gets permanently set so user can only increase records per page not decrease
            if (setRecordsToDisplay != null && setRecordsToDisplay != '')
                  numberRecordsToDisplayTemp = integer.valueof(setRecordsToDisplay);
            else
                  numberRecordsToDisplayTemp = numberRecordsToDisplay;
                      
            //1st object
            String qry;     
            //NOTE - Product2__c will have to change to Product2
            integer rowlimit = limits.getLimitQueryRows();

            qry = 'select Id,Primary_Agency_Buyer__c,Account.Name,Name from Opportunity limit :rowlimit';
          
            //get number of records
            list<Opportunity> object1AllRecords = Database.query(qry);

            //get number of pages to display        
            decimal numberOfPagesToDisplay = decimal.valueof(object1AllRecords.size()) / decimal.valueof(numberRecordsToDisplayTemp);
                      
            //round up
            numberOfPagesToDisplayObject1 = numberOfPagesToDisplay.round(ROUNDMODE);
          
            object1StatusPagesToDisplay = new list<integer>();

            //produce list of pages to display
            for (integer i = 1;i<=numberOfPagesToDisplayObject1;i++){
                  object1StatusPagesToDisplay.add(i);
            }
                      
            //need to get into iterator so Prev and Next buttons work on same data
            object1 = new ApexPages.StandardSetController(Database.getQueryLocator(qry));
            object1.setPageSize(numberRecordsToDisplayTemp);
          
            //query the data to display
            refreshOutput(EnumeratedValues.OpportunityEnum.Object1,object1);
          

            //used to render correct section
            object1Pg=1;
          
            //2nd object
                              qry = 'select Id,Primary_Agency_Buyer__c,Primary_Agency_Buyer__r.Name,Name,Sum_Total_Price_del__c, Third_party_Stats_checked__c from Opportunity limit :rowlimit';
                
            //get number of records
            list<Opportunity> object2AllRecords = Database.query(qry);
          
            //get number of pages to display
            integer object2AllSize = object2AllRecords.size();
            decimal numberOfPagesToDisplay2 = decimal.valueof(object2AllSize) / decimal.valueof(numberRecordsToDisplayTemp);
                      
            //round up
            numberOfPagesToDisplayObject2 = numberOfPagesToDisplay2.round(ROUNDMODE);
            object2PagesToDisplay = new list<integer>();

            //produce list of pages to display
            for (integer i = 1;i<=numberOfPagesToDisplayObject2;i++){
                  object2PagesToDisplay.add(i);
            }
                      
            //need to get into iterator so Prev and Next buttons work on same data
            object2 = new ApexPages.StandardSetController(Database.getQueryLocator(qry));
            object2.setPageSize(numberRecordsToDisplayTemp);
          
            //query the data to display
            refreshOutput(EnumeratedValues.OpportunityEnum.Object2,object2);
          
                
            //used to render correct section
            object2Pg=1;    
                
            return null;
      }

      public void refreshOutput(EnumeratedValues.OpportunityEnum OpportunityEnumType,ApexPages.StandardSetController std){
            newIDsSet = new Set<Id>();
            for (sObject eachRec : (List<sObject>)std.getRecords())
                  newIDsSet.add((ID)eachRec.get('id'));
          
            system.debug('size refreshOutput ' + newIDsSet.size());
            Task[] allTks;
            OpportunityLineItem[] thisopplines;
            if (OpportunityEnumType ==  EnumeratedValues.OpportunityEnum.Object1){
                  object1Return = [select Id,Primary_Agency_Buyer__c,Account.Name,Name from Opportunity where Id In :newIDsSet];

            //Build list of object1List wrappers to easily display on page
            object1List newObject1List;
            allObject1List.clear();
            for (Opportunity eachopp: object1Return){
                  allTks=eachopp.Tasks;
                  thisopplines = eachopp.OpportunityLineItems;
                  for (OpportunityLineItem eachoppline: eachopp.OpportunityLineItems){
                        newObject1List = new object1List(<<Add each value to wrapper class>>);
                        allObject1List.add(newObject1List);
                  }
            }
            }else if (OpportunityEnumType ==  EnumeratedValues.OpportunityEnum.Object2){
                  object2Return = [select Id,Primary_Agency_Buyer__c,Account.Name,Name,Sum_Total_Price_del__c,Campaign_Start_Date__c,Campaign_End_Date__c,Complexity__c,Complexity_explanation__c, Third_party_Stats_checked__c from Opportunity where Id In :newIDsSet];

                                                                            
                        thisopplines = eachopp.OpportunityLineItems;
                        for (OpportunityLineItem eachoppline: eachopp.OpportunityLineItems){

                              newObject2List = new object2List((<<Add each value to wrapper class>>);
                              allObject2List.add(newObject2List);
                        }
                  }
    
            }
      }
    
    

    
      public class object1List{
            public object1List(String addID, …){
                  this.thisID=addID;
                  <<Add more>>
            }
          
            public String thisID{get;set;}
            <<Add more>>
      }
    
      public class object2List{
            public object2List(String addID, … ){
                  this.thisID=addID;
<<Add more>>          
}
          
          
            public String thisID{get;set;}
            <<Add more>>
      }   
}



<apex:page>
         <apex:outputPanel rendered="{!object1StatusDisplay}" >
              <p>&nbsp;</p>
               <apex:outputlabel styleclass="largeTitle">object1 Section</apex:outputlabel>
              <p>&nbsp;</p>
            
              <apex:outputPanel id="currentobject1Page" >
                <apex:outputlabel >Current Page {!object1Pg}</apex:outputlabel>
              </apex:outputPanel>
            
              <apex:pageBlockSection >
                    <apex:pageBlockTable var="object1Rec" value="{!allobject1List}" >
                        <apex:column headerValue="Agency">
                            <apex:commandLink target="_blank" action="/{!object1Rec.thisID}" value="{!object1Rec.thisAgency}"/>
                        </apex:column>
                    </apex:pageBlockTable>
                  
             </apex:pageBlockSection>

                   
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject1}" >First
                        <apex:param name="selectedPageobject1" assignTo="{!selectedPageobject1}" value="1" />
             </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject1}" >Previous
               <apex:param name="selectedPageobject1" assignTo="{!selectedPageobject1}" value="{!selectedPageobject1 - 1}" />
            </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject1}" >Next
               <apex:param name="selectedPageobject1" assignTo="{!selectedPageobject1}" value="{!selectedPageobject1 + 1}" />
            </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject1}" >Last
               <apex:param name="selectedPageobject1" assignTo="{!selectedPageobject1}" value="{!numberOfPagesToDisplayobject1}" />
            </apex:commandlink>
             <br />
             <apex:outputPanel layout="block" styleclass="scrollNavig">
                         <apex:repeat value="{!object1StatusPagesToDisplay}" var="pgs" >
                            <apex:commandLink styleclass="rightspace" id="object1Link" action="{!moveToPageobject1}" >{!pgs}
                                <apex:param name="selectedPageobject1" assignTo="{!selectedPageobject1}" value="{!pgs}" />
                            </apex:commandlink>
                         </apex:repeat>
             </apex:outputPanel>
        </apex:outputPanel>

        <apex:outputPanel rendered="{!object2Display}" >
             <p>&nbsp;</p>
             <apex:outputlabel styleclass="largeTitle">object2  Section</apex:outputlabel>
             <p>&nbsp;</p>
      
              <apex:outputPanel id="currentobject2Page" >
                <apex:outputlabel >Current Page {!object2Pg}</apex:outputlabel>
              </apex:outputPanel>
            
              <apex:pageBlockSection >
                    <apex:pageBlockTable var="object2Rec" value="{!allobject2List}" >
                        <apex:column headerValue="Agency">
                            <apex:commandLink target="_blank" action="/{!object2Rec.thisID}" value="{!object2Rec.thisAgency}"/>
                        </apex:column>
                        <apex:column headerValue="ID">
                            <apex:outputLabel value="{!object2Rec.thisID}"/>
                        </apex:column>
                    </apex:pageBlockTable>
                  
             </apex:pageBlockSection>
                   
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject2}" >First
                        <apex:param name="selectedPageobject2" assignTo="{!selectedPageobject2}" value="1" />
             </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject2}" >Previous
               <apex:param name="selectedPageobject2" assignTo="{!selectedPageobject2}" value="{!selectedPageobject2 - 1}" />
            </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject2}" >Next
               <apex:param name="selectedPageobject2" assignTo="{!selectedPageobject2}" value="{!selectedPageobject2 + 1}" />
             </apex:commandlink>
             <apex:commandLink styleclass="rightspace" action="{!moveToPageobject2}" >Last
               <apex:param name="selectedPageobject2" assignTo="{!selectedPageobject2}" value="{!numberOfPagesToDisplayobject2}" />
             </apex:commandlink>
             <br />
           
             <apex:outputPanel layout="block" styleclass="scrollNavig">
                         <apex:repeat value="{!object2PagesToDisplay}" var="pgs" >
                            <apex:commandLink styleclass="rightspace" action="{!moveToPageobject2}" >{!pgs}
                                <apex:param name="selectedPageobject2" assignTo="{!selectedPageobject2}" value="{!pgs}" />
                            </apex:commandlink>
                         </apex:repeat>
                     </apex:outputPanel>
        </apex:outputPanel>

    </apex:outputPanel>
</apex:form>
</apex:pageBlock>


</apex:page>