Search

Sunday 20 March 2016

Salesforce PageBlockSections Styling Problems

When you want to render displays of parts of a PageBlockSection because PageBlockSections and PageBlockSectionItems do not take the attribute Rendered you have to use Outputpanels to turn on and off the display of the PageBlockSection.

The problem with this is that the Outputpanel interferes with the styling of the PageBlockSection and the PageBlockSection does not display properly.

This can be seen in this example
( you will need to create a controller class with the visualforce page ):

The aim of this visualforce page is to simply display a button and when the button is pressed the entire Outputpanel is displayed.



<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection title="" columns="1" collapsible="false">                   
                 <apex:outputPanel rendered="{!displaySelectedFrame}"  >
                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel >
                                          <apex:outputLabel value="A Label" />
                                          <h1>{! frameworkName}</h1>
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>

                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel >
                                          <apex:outputLabel value="Description" />
                                          {! description}
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>
                 </apex:outputPanel>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}" rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>





If you move the rendered to inside the PageBlockSectionItems it solves the styling issue but the entire section of the intended rendered area no longer collapses the PageBlockSectionItems so when the page starts there is a large white space created by the PageBlockSectionItems and so the styling is still not correct. This is shown in the following:



<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection title="" columns="1" collapsible="false">                   
                       
                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel rendered="{!displaySelectedFrame}">
                                          <apex:outputLabel value="A Label" />
                                          <h1>{! frameworkName}</h1>
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>

                              <apex:pageBlockSectionItem >
                                    <apex:outputPanel rendered="{!displaySelectedFrame}" >
                                          <apex:outputLabel value="Description" />
                                          {! description}
                                    </apex:outputPanel>
                              </apex:pageBlockSectionItem>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}"                                           rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>




  
To solve both situations use a PageBlockSection inside the Outputpanel that renders the section that we are wanting to turn on/off the display. By having 2 PageBlockSections the problem is solved.




<apex:form>
      <apex:pageBlock >
            <apex:outputPanel id="Results">
                  <apex:pageBlockSection columns="1" collapsible="false">                      
                        <apex:outputPanel layout="block" rendered="{!displaySelectedFrame}"  >
                      <apex:pageBlockSection columns="1" collapsible="false">
                                    <apex:pageBlockSectionItem >
                                          <apex:outputPanel >
                                                <h1><apex:outputLabel value="A Label" /></h1><br/>
                                                {! frameworkName}
                                          </apex:outputPanel>
                                    </apex:pageBlockSectionItem>

                                    <apex:pageBlockSectionItem >
                                          <apex:outputPanel >
                                                <h1><apex:outputLabel value="Description" /></h1><br/>
                                                {! description}
                                          </apex:outputPanel>
                                    </apex:pageBlockSectionItem>
                      </apex:pageBlockSection>
                        </apex:outputPanel>
                              <apex:pageBlockSectionItem >
                                    <apex:commandButton value="Display Section" action="{!doSomething}" rerender="Results"  />
                              </apex:pageBlockSectionItem>
                  </apex:pageBlockSection>
                  </apex:outputPanel>
            </apex:pageBlock>
      </apex:form>




                                       

Saturday 5 March 2016

Trigger Pattern Framework - session 2





In my last blog I introduced my Trigger Pattern Framework, now I will cover Trigger Control, which is one of the main improvements to other well documented frameworks.

DMLs are very processor intensive and conserving this precious resource in a multitenant environment is ever more so important.
So I built into my framework a capability using a Custom Setting to activate / deactivate per trigger for any User, Profile, or the entire Organization; or with a separate Custom Setting to be able to activate / deactivate ALL triggers for any User, Profile, or the entire Organization. This was great in situations where companies require migrating data from 1 system to another and you want to safeguard that no unwanted actions occur through the execution of code in the trigger.


However there are also many situations such as in unit tests where we create test data, to avoid running through all the code in triggers which is not necessary unless we are testing specifically the triggers to disable the triggers using the Custom Settings above will require running a DML and since we are trying to avoid DMLs because they are expensive to run we need another mechanism to bypass the code in the trigger, therefore we introduce a static variable to do this work. Here is a section in the TriggerFactory class that controls the execution using these methods and also the code from the calling classes that are used in this part of the framework.


boolean notriggerSetting;
                 boolean noTriggersPerObject;
                 try{
                      notriggerSetting = TriggerController.globalTriggerControlSetting();   
                      noTriggersPerObject = TriggerController.globalTriggerPerObjectControlSetting(objType);   
                 }
                 catch (Exception ex){system.debug('error in trigger controller ' + ex); }

                Type soType = Type.forName(objType);
               
    if (!notriggerSetting && !noTriggersPerObject && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_ALL)) {  
                               
                                if (Trigger.isBefore){
                                                if (Trigger.isUpdate && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UPDATE)){
                                                                                handler.beforeUpdate(Trigger.oldmap, Trigger.newmap);               
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UPDATE), true);                                
                                                }
                                                else if (Trigger.isDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_DELETE)){
                                                                                handler.beforeDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_DELETE), true);
                                                }
                                                else if (Trigger.isInsert && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_INSERT)){
                                                                                handler.beforeInsert(Trigger.newmap);                                                         
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_INSERT), true);
                                                }
                                                else if (Trigger.isUnDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UNDELETE)){
                                                                                handler.beforeUnDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UNDELETE), true);          
                                                }
                                                 
                                }
                                else{
                                               
                                                if (Trigger.isUpdate && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UPDATE)){
                                                                                handler.afterUpdate(Trigger.oldmap, Trigger.newmap);                                                               
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UPDATE), true);
                                                }
                                                else if (Trigger.isDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_DELETE)){
                                                                                handler.afterDelete(Trigger.oldmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_DELETE), true);
                                                }
                                                else if (Trigger.isInsert && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_INSERT)){
                                                                                handler.afterInsert(Trigger.newmap);                                                            
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_INSERT), true);
                                                }
                                                else if (Trigger.isUnDelete && !TriggerController.getTriggerControlValue(soType, TriggerController.TRIGGER_UNDELETE)){
                                                                                handler.afterUnDelete(Trigger.oldmap, Trigger.newmap);
                                                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(soType, TriggerController.TRIGGER_UNDELETE), true);
                                                }
                                }
                               
    }




public class TriggerController {

                public static map<TriggerControlKeyValue, boolean> triggerDisableMap = new map<TriggerControlKeyValue, boolean>();
                public static map<TriggerControlKeyValue, boolean> triggerSuccessMap = new map<TriggerControlKeyValue, boolean>();
               
                public static final String TRIGGER_ALL = 'ALL';
                public static final String TRIGGER_INSERT = 'INSERT';
                public static final String TRIGGER_UPDATE = 'UPDATE';
                public static final String TRIGGER_DELETE = 'DELETE';
                public static final String TRIGGER_UNDELETE = 'UNDELETE';
               
                public static Boolean getTriggerControlValue(System.Type objType, String triggerType){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                Boolean triggerDisable = false;
                                if (triggerDisableMap != null && triggerDisableMap.containskey(tkv))
                                                triggerDisable = triggerDisableMap.get(tkv);
                                               
                                return triggerDisable;
                }

                public static void setTriggerControlValue(System.Type objType, String triggerType, Boolean triggerDisable){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                                               
                                for (TriggerControlKeyValue eachtk : triggerDisableMap.keyset()){
                                                if (eachtk == tkv){
                                                                tkv = eachtk;                                                                       
                                                                break;
                                                }
                                }
                                triggerDisableMap.put(tkv, triggerDisable);                   
                }
               
                public static Boolean getTriggerSuccessValue(System.Type objType, String triggerType){
                                TriggerControlKeyValue tkv = new TriggerControlKeyValue(objType ,triggerType);
                                Boolean triggerSuccess = false;
                               
                                for (TriggerControlKeyValue eachtk : triggerSuccessMap.keyset()){
                                                if (eachtk == tkv){
                                                                triggerSuccess = triggerSuccessMap.get(eachtk);
                                                                break;
                                                }
                                }
                               
                                return triggerSuccess;
                }
               
               
public static boolean globalTriggerControlSetting(){
               
                return (((Triggers_Off__c.getOrgDefaults() != null) ? Triggers_Off__c.getOrgDefaults().value__c : false) || Triggers_Off__c.getInstance(UserInfo.getUserId()).value__c  || Triggers_Off__c.getInstance(UserInfo.getProfileId()).value__c) ;
}

public static boolean globalTriggerPerObjectControlSetting(String obj){
               
                if (obj != null && obj != '') {
                                if (!obj.endswith('__c')) obj += '__c';
                                                boolean s = false;
                                                if (Trigger_Per_Object__c.getOrgDefaults() != null) s =  (boolean)Trigger_Per_Object__c.getOrgDefaults().get(obj);

                                                boolean t = false;
                                                if (Trigger_Per_Object__c.getInstance(UserInfo.getUserId()) != null) t =  (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getUserId()).get(obj);

                                                boolean u = false;
                                                if (Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()) != null) u =  (boolean)Trigger_Per_Object__c.getInstance(UserInfo.getProfileId()).get(obj);

                                               
                                                if  (s == null) s = false;
                                                if  (t == null) t = false;
                                                if  (u == null) u = false;
                                               
                                                return (s || t || u);
                }else
                                return false;
}

}




public class TriggerControlKeyValue {
               
public system.type objectType;
public string triggerType;

                public TriggerControlKeyValue(system.type thisObjectType, string thisTriggerType) {
                                objectType = thisObjectType;
                                triggerType = thisTriggerType;
                }

               
                public boolean equals(object obj){
                               
                                if (obj instanceof TriggerControlKeyValue){
                                                TriggerControlKeyValue t = (TriggerControlKeyValue)obj;
                                                return (objectType.equals(t.objectType) && triggerType.equals(t.triggerType));
                                }
                                return false;
                }
               
                public integer hashCode(){
                                return system.hashCode(objectType) * system.hashCode(triggerType);
                }

}



We also need to be able to unit test the framework to test the trigger control has been built correctly and remains operational. To test this part of the framework we don’t want to test the outcomes from running each individual part of the trigger as the outcomes will be different per trigger, instead we just need to test that the code passed through the track of code we expect. For this purpose, another map is used.


@istest
public class TriggerControllerTest {
                public static TestDataCreation td = new TestDataCreation();
                public static Account acc;
                public static Triggers_Off__c trig;
                public static Trigger_Per_Object__c trigPerObject;
               
                static{
                                acc = td.insertAccount(null);
                }
               
                static testMethod void AccountTriggerGlobalCSTest() {
                                //test global CS on/off
                                trig = td.insertTriggersOff(null);
                                Test.startTest();
                                                //record should be inserted                                
                                                //system.assert([Select id From Account where Name=:defaultCusName].size() == 1);
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == true);
                                               
                                                //should change
                                                acc.Name = 'ChangeCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == true);

                                                //reset                                    
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                               
                                                trig.value__c = true;
                                                update trig;
                                               
                                                //should not change
                                                acc.Name = 'DefaultCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);
                                               
                                                //disable insert
                                               
                                test.stopTest();
                }

                static testMethod void AccountTriggerPerObjectCSStaticTest() {
                                //test trigger control using Per Object CS
                                trigPerObject = td.insertTriggersPerObject(null);
                                Test.startTest();
                                                //reset
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), true);
                                               
                                                //record should be inserted but shouldnt set Account_Insert_Succeeded
                                                trigPerObject.Account__c = true;
                                                update trigPerObject;
                                               
                                                //reset                                    
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), false);
                                               
                                                system.assert([Select id From Account].size() == 1);
                                               
                                                Account acc2 = td.insertAccount(null);
               
                                                system.assert([Select id From Account].size() == 2);
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == false);

                                test.stopTest();
                }             

                static testMethod void AccountTriggerGlobalStaticTest() {
                                //test trigger control using static variables
                                Test.startTest();
                                                //disable update
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_ALL), true);
                                               
                                                acc.Name = 'ChangeCusName';
                                                update acc;                                          
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);

                                                //reset
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_ALL), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), true);
                                               
                                                acc.Name = 'DefaultCusName';
                                                update acc;                                          
                                               
                                                //should not change
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == false);
                                               
                                                //update should run
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_UPDATE), false);
                                                acc.Name = 'DefaultCusName';
                                                update acc;
                                                system.debug('## TriggerController ' + TriggerController.triggerSuccessMap);                                       
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_UPDATE) == true);
                                               
                                                //test insert trigger code off
                                                TriggerController.triggerSuccessMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), false);
                                                TriggerController.triggerDisableMap.put(new TriggerControlKeyValue(Account.class, TriggerController.TRIGGER_INSERT), true);
                                                system.assert([Select id From Account].size() == 1);
                                               
                                                Account acc2 = td.insertAccount(null);
               
                                                system.assert([Select id From Account].size() == 2);
                                                //should not change
                                                system.assert(TriggerController.getTriggerSuccessValue(Account.class,TriggerController.TRIGGER_INSERT) == false);
                                               
                                test.stopTest();
                }

}