Sending an email notification to Admin whenever a field is created or deleted for a custom object in salesforce

Sending an email notification to Admin whenever a field is created or deleted for a custom object in salesforce


   global class FieldsValidator implements Database.Batchable<Sobject> {
    global Database.queryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('select name, fields_info__c, fields_count__c from ObjectsExistingFieldsInfo__c');
    }
    global void execute(Database.BatchableContext bc, List<SObject> sobjLst) {
        String[] types = new String[]{'Account','Contact'};
        String emailBody = '';
        // Make the describe call
        Schema.DescribeSobjectResult[] results = Schema.describeSObjects(types);
        Map<String,List<Field>> objFieldsDesMap = new Map<String,List<Field>>();
        // For each returned result, get some info
        for(Schema.DescribeSobjectResult res : results) {
            Map<String, Schema.SObjectField> objectFields = res.fields.getMap();
            for(Schema.SObjectField fDes : objectFields.values()) {
                if(fDes.getDescribe().isCustom()) {
                    system.debug(fDes.getDescribe().getName()+'@@@'+fDes.getDescribe().getType());
                    String fieldAPIName = String.valueOf(fDes.getDescribe().getName());
                    String fieldDataType = String.valueOf(fDes.getDescribe().getType());
                    if(objFieldsDesMap.containsKey(res.getName())) {
                        objFieldsDesMap.get(res.getName()).add(new Field(fieldAPIName,fieldDataType));
                    }
                    else {
                        objFieldsDesMap.put(res.getName(),new List<Field>{new Field(fieldAPIName,fieldDataType)});
                    }
                }      
            }
        }
        Map<String, ObjectsExistingFieldsInfo__c> objectInfoMap = new Map<String, ObjectsExistingFieldsInfo__c>();
        for(ObjectsExistingFieldsInfo__c objInfo : [select Field_API_Name__c , Fields_Count__c, Fields_Info__c from
        ObjectsExistingFieldsInfo__c where Field_API_Name__c in: types]) {
            objectInfoMap.put(objInfo.Field_API_Name__c,objInfo);
        }
        for(String type : types) {
            if(objectInfoMap.get(type).fields_count__c == null) {
                objectInfoMap.get(type).fields_count__c = objFieldsDesMap.get(type).size();
                objectInfoMap.get(type).fields_info__c = JSON.serialize(objFieldsDesMap.get(type));
            }
            else {
                System.debug('entering to else condition...');
                System.debug('entering to for loop..');
                List<Field> exstingFields = new List<Field>();
                List<Field> newFields = new List<Field>();
                Map<String,Field> bkupexstingFields = new Map<String,Field>();
                Map<String,Field> bkupnewFields = new Map<String,Field>();
                exstingFields = (List<Field>)JSON.deserialize(objectInfoMap.get(type).fields_info__c, List<Field>.class);
                System.debug('exstingFields: '+exstingFields);
                newFields.addAll(objFieldsDesMap.get(type));
                System.debug('objFieldsDesMap: '+objFieldsDesMap);
                System.debug('newFields: '+newFields);
                for(Field f : exstingFields) {
                    bkupexstingFields.put(f.apiName,f);
                }
                for(Field f : newFields) {
                    bkupnewFields.put(f.apiName,f);
                }
                for(Field f : exstingFields) {
                    bkupnewFields.remove(f.apiName);
                }
                for(Field f : newFields) {
                    bkupexstingFields.remove(f.apiName);
                }
                System.debug(bkupnewFields+'@@@'+bkupexstingFields);
                emailBody += type + ' : ';
                //for new fields
                for(Field f :bkupnewFields.values()) {
                    emailBody += f.apiName + ' with datatype '+f.dataType+' is newly created.';
                }
                objectInfoMap.get(type).fields_count__c = objFieldsDesMap.get(type).size();
                objectInfoMap.get(type).fields_info__c = JSON.serialize(objFieldsDesMap.get(type));
            }
        }
        /*** Sending Email ***/
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        mail.setToAddresses(new List<String>{system.label.Dev_Admin});
        mail.setSubject('Fields Update');
        mail.setPlainTextBody(emailBody);
        update objectInfoMap.values();
        Messaging.sendEmail(new Messaging.SingleEmailMessage[]{mail});
    }
    global void finish(Database.BatchableContext bc) {
    }
    public class Field {
        String apiName, dataType;
        public Field(String apiName, String dataType) {
            this.apiName = apiName;
            this.dataType = dataType;
        }
    }
}
 

Notes-

    We cannot create trigger on 'Object' or 'Field'.
    Alternative is creating a Batch Class and schedule it as per the requirement.
    To store the history of the fields we can user either list custom settings or custom object.
    List Custom Settings is not supporting Text Area Long.
    To store the history of the fields we need Text Area Long (255 character for text area data type is not sufficient.) field which is possible with the custom object.

Generic Pagination with Visualforce Component
Tuesday, February 10, 2015 8:14 PM
Creating Generic Pagination with Visualforce Component

GenericPaginationComponent
pop-uptext

    <!--
        Description
         *===========
         * VF Component for the Generic Pagination.
         * It accepts three parameters -
               1. Records (List of records belongs to any object)
               2. Fields (List of fields of a sobject to display in the table)
               3. Title (Title of the section)
       
    -->
    <apex:component controller="GenericPaginationComponentContrl">
     
      <!-- Attributes to accept the parameters -->
      <apex:attribute name="Records" Type="Sobject[]" assignTo="{!sObjLst}" required="true" description="Accepts list of records of any object and assign to a variable in controller class"/>
      <apex:attribute name="Fields" Type="String[]" required="true" description="Accepts list of field API names of a sobject in string format"/>
      <apex:attribute name="Title" Type="String" required="true" description="Accepts the title of the section"/>
     
      <!-- Table which displays records along with pagination -->
      <apex:form >
          <apex:pageBlock >
              <apex:pageBlockSection columns="1" title="{!Title}" id="pbSec">
                  <apex:pageBlockTable value="{!SobjRecords}" var="sObj">
                      <!-- Dispalys the multiple columns based on the user input -->
                      <apex:repeat value="{!Fields}" var="fld">
                          <apex:column value="{!sObj[fld]}"/>
                      </apex:repeat>
                  </apex:pageBlockTable>
                 
                  <!-- Dispalys pagination buttons -->
                  <apex:panelGrid columns="5">
                      <apex:commandButton value="First" action="{!con.first}" disabled="{!!con.hasPrevious}" status="pagStatus" reRender="pbSec"/>
                      <apex:commandButton value="Previous" action="{!con.previous}" disabled="{!!con.hasPrevious}" status="pagStatus" reRender="pbSec"/>
                      <apex:commandButton value="Next" action="{!con.next}" disabled="{!!con.hasNext}" status="pagStatus" reRender="pbSec"/>
                      <apex:commandButton value="Last" action="{!con.last}" disabled="{!!con.hasNext}" status="pagStatus" reRender="pbSec"/>
                      <apex:actionStatus startText="Fetching..." id="pagStatus"/>
                  </apex:panelGrid>
              </apex:pageBlockSection>
          </apex:pageBlock>
      </apex:form>
    </apex:component>

<!--
    Description
     *===========
     * VF Component for the Generic Pagination.
     * It accepts three parameters -
           1. Records (List of records belongs to any object)
           2. Fields (List of fields of a sobject to display in the table)
           3. Title (Title of the section)
       
-->
<apex:component controller="GenericPaginationComponentContrl">
 
  <!-- Attributes to accept the parameters -->
  <apex:attribute name="Records" Type="Sobject[]" assignTo="{!sObjLst}" required="true" description="Accepts list of records of any object and assign to a variable in controller class"/>
  <apex:attribute name="Fields" Type="String[]" required="true" description="Accepts list of field API names of a sobject in string format"/>
  <apex:attribute name="Title" Type="String" required="true" description="Accepts the title of the section"/>
 
  <!-- Table which displays records along with pagination -->
  <apex:form >
      <apex:pageBlock >
          <apex:pageBlockSection columns="1" title="{!Title}" id="pbSec">
              <apex:pageBlockTable value="{!SobjRecords}" var="sObj">
                  <!-- Dispalys the multiple columns based on the user input -->
                  <apex:repeat value="{!Fields}" var="fld">
                      <apex:column value="{!sObj[fld]}"/>
                  </apex:repeat>
              </apex:pageBlockTable>
             
              <!-- Dispalys pagination buttons -->
              <apex:panelGrid columns="5">
                  <apex:commandButton value="First" action="{!con.first}" disabled="{!!con.hasPrevious}" status="pagStatus" reRender="pbSec"/>
                  <apex:commandButton value="Previous" action="{!con.previous}" disabled="{!!con.hasPrevious}" status="pagStatus" reRender="pbSec"/>
                  <apex:commandButton value="Next" action="{!con.next}" disabled="{!!con.hasNext}" status="pagStatus" reRender="pbSec"/>
                  <apex:commandButton value="Last" action="{!con.last}" disabled="{!!con.hasNext}" status="pagStatus" reRender="pbSec"/>
                  <apex:actionStatus startText="Fetching..." id="pagStatus"/>
              </apex:panelGrid>
          </apex:pageBlockSection>
      </apex:pageBlock>
  </apex:form>
</apex:component>


GenericPaginationComponentContrl
pop-uptext

    /* Description:
       * ============
       * Class for the VF Component GenericPaginationComponent *
       */
     public class GenericPaginationComponentContrl {
     
      //Stores the records which are supplied to the 'Records' attribute.
      public Sobject[] sObjLst {get;set;}
     
      /*
       1. Implementing the pagination with ApexPages.StandardSetController.
       2. We can utilize the built in methods available for the ApexPages.StandardSetController to build the pagination.
       3. Following are the built in mehods we can utilize -
        a. first()
        b. previous()
        c. next()
        d. last()
        e. getHasPrevious() - returns boolean value.
        f. getHasNext() - returns boolean value.
        g. setPageSize(IntegerValue)
      */
      public ApexPages.StandardSetController con {
       get {
        //initializing con with the records.
        if(con == null)
         con = new ApexPages.StandardSetController(sObjLst);
       
        //Setting the pagination size
        con.setPageSize(5);
       
        return con;
       }
       set;
      }
     
      //Method which returns subset of records from the sObjLst.
      public List<sobject> getSobjRecords() {      
       //Type Casing the records and returning to display on the page.
       return (List<sobject>)con.getRecords();
      }
     }

/* Description:
   * ============
   * Class for the VF Component GenericPaginationComponent *
   */
 public class GenericPaginationComponentContrl {
 
  //Stores the records which are supplied to the 'Records' attribute.
  public Sobject[] sObjLst {get;set;}
 
  /*
   1. Implementing the pagination with ApexPages.StandardSetController.
   2. We can utilize the built in methods available for the ApexPages.StandardSetController to build the pagination.
   3. Following are the built in mehods we can utilize -
    a. first()
    b. previous()
    c. next()
    d. last()
    e. getHasPrevious() - returns boolean value.
    f. getHasNext() - returns boolean value.
    g. setPageSize(IntegerValue)
  */
  public ApexPages.StandardSetController con {
   get {
    //initializing con with the records.
    if(con == null)
     con = new ApexPages.StandardSetController(sObjLst);
   
    //Setting the pagination size
    con.setPageSize(5);
   
    return con;
   }
   set;
  }
 
  //Method which returns subset of records from the sObjLst.
  public List<sobject> getSobjRecords() {      
   //Type Casing the records and returning to display on the page.
   return (List<sobject>)con.getRecords();
  }
 }


*************
Using the above Generic Pagination from a Visualforce Page

GenericPaginationUsagePage
pop-uptext

    <!--
        Description
         *===========
         * VF Page for using 'GenericPaginationComponent' component.
         * Displaying three different object lists by reusing the logic of 'GenericPaginationComponent' component.
         
    -->
    <apex:page controller="GenericPaginationUsageContrl" tabStyle="Account">
     
      <!-- Default name space is : C ; Referring the Visualforce Component -->
      <c:GenericPaginationComponent records="{!accLst}" fields="{!accFieldLst}" title="Accounts"/>
      <c:GenericPaginationComponent records="{!conLst}" fields="{!conFieldLst}" title="Contacts"/>
      <c:GenericPaginationComponent records="{!oppLst}" fields="{!oppFieldLst}" title="Opportunities"/>
     
    </apex:page>

<!--
    Description
     *===========
     * VF Page for using 'GenericPaginationComponent' component.
     * Displaying three different object lists by reusing the logic of 'GenericPaginationComponent' component.
     
-->
<apex:page controller="GenericPaginationUsageContrl" tabStyle="Account">
 
  <!-- Default name space is : C ; Referring the Visualforce Component -->
  <c:GenericPaginationComponent records="{!accLst}" fields="{!accFieldLst}" title="Accounts"/>
  <c:GenericPaginationComponent records="{!conLst}" fields="{!conFieldLst}" title="Contacts"/>
  <c:GenericPaginationComponent records="{!oppLst}" fields="{!oppFieldLst}" title="Opportunities"/>
 
</apex:page>



GenericPaginationUsageContrl
pop-uptext

    /* Description:
         * ============
         * Class for the VF Page GenericPaginationUsagePage *
       
         */
    public class GenericPaginationUsageContrl {
       
        //Declaring variables to store list of records.
        public List<Account> accLst {get;set;}
        public List<Contact> conLst {get;set;}
        public List<Opportunity> oppLst {get;set;}
       
        //Declaring variables to store list of Field API names in string format.
        public List<String> accFieldLst {get;set;}
        public List<String> conFieldLst {get;set;}
        public List<String> oppFieldLst {get;set;}
       
        //Default Constructor  
        public GenericPaginationUsageContrl() {
           
            //Querying the records from the database.
            accLst = [select Name, AccountNumber, Fax, Phone, Industry from Account limit 100];
            conLst = [select Name, AccountId, Email, Phone from Contact limit 100];
            oppLst = [select Name, AccountId, Amount from Opportunity limit 100];
           
            //Preparing the list of fields to display in the table.
            accFieldLst = new List<String>{'Name', 'AccountNumber', 'Fax', 'Phone', 'Industry'};
            conFieldLst = new List<String>{'Name', 'AccountId', 'Email', 'Phone'};
            oppFieldLst = new List<String>{'Name', 'AccountId', 'Amount'};
        }
       
    }

/* Description:
     * ============
     * Class for the VF Page GenericPaginationUsagePage *
   
     */
public class GenericPaginationUsageContrl {
   
    //Declaring variables to store list of records.
    public List<Account> accLst {get;set;}
    public List<Contact> conLst {get;set;}
    public List<Opportunity> oppLst {get;set;}
   
    //Declaring variables to store list of Field API names in string format.
    public List<String> accFieldLst {get;set;}
    public List<String> conFieldLst {get;set;}
    public List<String> oppFieldLst {get;set;}
   
    //Default Constructor  
    public GenericPaginationUsageContrl() {
       
        //Querying the records from the database.
        accLst = [select Name, AccountNumber, Fax, Phone, Industry from Account limit 100];
        conLst = [select Name, AccountId, Email, Phone from Contact limit 100];
        oppLst = [select Name, AccountId, Amount from Opportunity limit 100];
       
        //Preparing the list of fields to display in the table.
        accFieldLst = new List<String>{'Name', 'AccountNumber', 'Fax', 'Phone', 'Industry'};
        conFieldLst = new List<String>{'Name', 'AccountId', 'Email', 'Phone'};
        oppFieldLst = new List<String>{'Name', 'AccountId', 'Amount'};
    }
   
}


Result

Live Demo
Fetching Record Type Id with Dynamic Apex

recordTypeInfo Utility Method


  public class MyUtility {
   /*
      @ Description -
      To fetch the RecordType Information of a certain Object.
      @Usage -
      Using this approach, we can avoid writing SOQL queries in apex class/test class to fetch the record type Id.
   */

   /*** recordTypeInfo Utility Method ***/
   //Below method will take sObject API as input.
   public static Map recordTypeInfo(String objectApiName){
   
    Map sObjectMap = Schema.getGlobalDescribe() ;
    Schema.SObjectType sObjType = sObjectMap.get(ObjectApiName) ;
    Schema.DescribeSObjectResult sObjTypeDescribe = sObjType.getDescribe() ;
   
    //returns all the record types info for a certain object
    return sObjTypeDescribe.getRecordTypeInfosByName();
   
   }
  }
  //---End of the Logic---Please ignore remaining lines---



recordTypeInfo Utility Method - Usage


  /*** recordTypeInfo Utility Method - Usage ***/

  //To retrieve the recordTypeId of a RecordType 'Sample Record Type' which belongs to 'Case' object.
  Id sampleRtId = MyUtility.recordTypeInfo('Case').get('Sample Record Type').getRecordTypeId();

  //Result: 'sampleRtId' will fetch the recordTypeId of 'Sample Record Type' record type.



Avoid duplicate string values in the collection 'Set' or 'Map Keys' while adding strings with different cases
Thursday, October 23, 2014 7:49 PM
When, Set allows duplicates?
Examples:

Set<String> nameSet = new Set<String>;
nameSet.add(mkr);
nameSet.add(MKR);
nameSet.add(Mkr);

Map<String,String> cityMap = new Map<String,String>;
cityMap .put('Newyork', USA);
cityMap .put('newyork',US);
cityMap .put('NEWYORK','America');

From the above examples, nameSet and cityMap allows all the three values because set and map key treat the values with different case as different values that means set string values are case-sensitive.

How to avoid?
Use the below method before adding string value to set or map key.

//Generic method to search a string from set of strings and which will ignore case-sensitive
public static Boolean isStrExists(Set<String> strSet,String searchStr) {
        Boolean isTrue = false;
        if(strSet != null && strSet.size() > 0) {
            for(String strVal : strSet) {
                if(strVal.equalsIgnoreCase(searchStr)) {
                    isTrue = true;
                    break;
                }
            }
        }
        return isTrue;
    }


How to use above method?

if(!isStrExists(nameSet,'GMKR'))
    nameSet.add('GMKR');


Avoid focus on date field
When will focus come to date field on a visualforce page load?
If the first field is the date field then whenever user try to open the visualforce page focus will be on date field.
Note: If you have pick list field as the first field then focus won't be on that field. Example if you have two pick list fields followed by date field then focus will be on date field not on pick list fields.

What to do?
Add below line of code to your visualforce page.


<script>function setFocusOnLoad() {}</script>
Post a Comment