• Richard Hopkins 5150
  • NEWBIE
  • 0 Points
  • Member since 2016

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 2
    Questions
  • 2
    Replies
Hi everyone, I’m pretty much brand new to Apex and Salesforce development.

In our org, we need a way of having an inline editable visualforce page related lists on our opportunity layout for a custom object called Required Documents.

After scouring the internet, I found some code by Dave Helgerson on his blog: http://www.davehelgerson.com/in-line-editing-visualforce-component/

I tried to repurpose the code for my needs with no luck, emailed him, and he was nice enough to help me out. Thanks Dave! (Ps. Check out his blog, it is great)

It works perfectly in the sandbox and looks like this:

Related list visualforce page in sandbox working
He said the test class would be up to me.

I tried to deploy to production without a test class (my first time trying to deploying something) but it says:

Code Coverage Error

I’ve never wrote a test class. I’m going through the Apex testing trailhead now because we desperately need this in our production org.

Can anyone help me out with getting this deployed successfully in production. I would be forever grateful and appreciative! Any help or advice is appreciated, thank you!

Please let me know if you need any more information from me.

Here is the code:

MultiRecordComponent.component
 
<apex:component controller="MultiRecordComponentController" allowDML="true">
   <style>
.cmdLink {
   font-size: 89%;
   text-decoration: none;
   float: left;
}

.cmdLink:hover {
   text-decoration: underline;
}
</style>
   <apex:attribute name="aParentRecId" description="Parent Record Id" type="String" required="true" assignTo="{!ParentRecId}" />
   <apex:attribute name="aRelationField" description="Field that will be assigned the Parent's Record Id" type="String" required="true"
      assignTo="{!RelationField}" />
   <apex:attribute name="asObjectType" description="Type of child Object." type="String" required="true" assignTo="{!sObjectType}" />
   <apex:attribute name="aFieldList" description="List of fields to display." type="string[]" required="true" assignTo="{!FieldList}" />
   <apex:attribute name="aAllowAdd" description="Ability to add new records." type="Boolean" required="false" assignTo="{!AllowAdd}" />
   <apex:attribute name="aAllowEdit" description="Ability to edit records" type="Boolean" required="false" assignTo="{!AllowEdit}" />
   <apex:attribute name="aAllowDelete" description="Ability to delete records" type="Boolean" required="false" assignTo="{!AllowDelete}" />
   <apex:attribute name="aLabelOverrideFieldList" description="List of fields with overridden labels" type="String[]" required="false"
      assignTo="{!LabelOverrideFieldList}" />
   <apex:attribute name="aLabelOverrideTextList" description="List of text that overrides the field labels" type="String[]" required="false"
      assignTo="{!LabelOverrideTextList}" />
   <apex:attribute name="aDefaultValueFieldList" description="List of fields used to set default values on added records" type="String[]"
      required="false" assignTo="{!DefaultValueFieldList}" />
   <apex:attribute name="aDefaultValueTextList" description="List of text used to set default values on added records" type="String[]"
      required="false" assignTo="{!DefaultValueTextList}" />
   <apex:attribute name="aBlockTitle" description="Page block title text" type="String" required="false" />


   <apex:actionFunction name="DoDeleteJS" action="{!DoDelete}" rerender="pbContainer,msgs" immediate="true">
      <apex:param name="ActionId" assignto="{!ActionId}" value="" />
   </apex:actionFunction>
   <apex:actionFunction name="DoRemoveJS" action="{!DoRemove}" rerender="pbContainer,msgs" immediate="true">
      <apex:param name="ActionRowNumber" assignto="{!ActionRowNumber}" value="" />
   </apex:actionFunction>

   <apex:pageMessages id="msgs" />

   <apex:pageBlock id="pbContainer" title="{!aBlockTitle}">


      <apex:pageBlockButtons location="both">

         <apex:actionStatus id="ButtonStatus">
            <apex:facet name="stop">
               <apex:outputPanel >
                  <apex:commandButton rerender="pbContainer,msgs" status="ButtonStatus" value="Add" action="{!DoAdd}" immediate="true"
                     rendered="{!AllowAdd}" />
                  <apex:commandButton rerender="pbContainer,msgs" status="ButtonStatus" value="Save" action="{!DoSave}"
                     rendered="{!OR(AllowEdit,AllowAdd)}" disabled="{!DisableSave}" />
                  <apex:commandButton rerender="pbContainer,msgs" status="ButtonStatus" value="Cancel" action="{!DoCancel}" immediate="true"
                     rendered="{!OR(AllowEdit,AllowAdd)}" disabled="{!DisableCancel}" />
               </apex:outputPanel>
            </apex:facet>
            <apex:facet name="start">
               <apex:outputPanel >
                  <apex:commandButton value="Processing..." disabled="true" rendered="{!AllowAdd}" />
                  <apex:commandButton value="Processing..." disabled="true" rendered="{!OR(AllowEdit,AllowAdd)}" />
                  <apex:commandButton value="Processing..." disabled="true" rendered="{!OR(AllowEdit,AllowAdd)}" />
               </apex:outputPanel>
            </apex:facet>
         </apex:actionStatus>

      </apex:pageBlockButtons>

      <apex:pageBlockTable id="pbTable" value="{!ObjectList}" var="ow">
         <apex:column headerValue="Action" width="71px" rendered="{!OR(AllowEdit,AllowDelete,AllowAdd)}">
            <apex:outputPanel rendered="{!ISBLANK(ow.obj.Id)}">
               <apex:outputLink style="color: #015BA7;" styleClass="cmdLink" value="javascript:DoRemoveJS('{!JSENCODE(ow.AddedRowNumber)}');">Remove</apex:outputLink>
            </apex:outputPanel>
            <apex:outputPanel rendered="{!!ISBLANK(ow.obj.Id)}">
               <apex:commandLink style="color: #015BA7;" styleClass="cmdLink" value="Edit" action="{!DoEdit}" immediate="true"
                  rendered="{!AllowEdit}" rerender="pbContainer,msgs">
                  <apex:param name="RecId" value="{!ow.obj.Id}" assignTo="{!ActionId}" />
               </apex:commandLink>
               <apex:outputPanel style="display:inline;float: left; margin: 0 2px 0 2px" rendered="{!AND(AllowEdit,AllowDelete)}"> | </apex:outputPanel>
               <apex:outputLink style="color: #015BA7;" styleClass="cmdLink"
                  value="javascript:if (window.confirm('Are you sure?')) DoDeleteJS('{!JSENCODE(ow.obj.Id)}');" rendered="{!AllowDelete}">Del</apex:outputLink>
            </apex:outputPanel>
         </apex:column>

         <apex:repeat value="{!ColumnList}" var="cf">
            <apex:column width="200">
               <apex:facet name="header">
                  <span>{!cf.FieldLabel}</span>
               </apex:facet>
               <apex:outputPanel rendered="{!OR(!ISBLANK(ow.obj['Id']), AND(ISBLANK(ow.obj['Id']),cf.IsObjField))}">
                  <apex:outputField value="{!ow.obj[cf.FieldName]}" rendered="{!!AND(ow.IsEditMode,cf.IsEditable)}" />

                  <apex:InputField value="{!ow.obj[cf.FieldName]}" rendered="{!AND(ow.IsEditMode,cf.IsEditable)}" />
               </apex:outputPanel>
            </apex:column>
         </apex:repeat>
      </apex:pageBlockTable>

   </apex:pageBlock>
</apex:component>

MultiRecordComponentController.cls
 
public with sharing class MultiRecordComponentController {
    public Boolean AllowAdd {get; set;}
    public Boolean AllowEdit {get; set;}
    public Boolean AllowDelete {get; set;}
    public String ParentRecId {get; set;}
    public String RelationField {get; set;}
    public String OrderByField {get; set;}
    public list<String> FieldList {get; set;}
    public String sObjectType {get; set;}
    public list<String> LabelOverrideFieldList {get; set;}
    public list<String> LabelOverrideTextList {get; set;}
    private map<String, String> LabelOverrideMap;
    public list<String> DefaultValueFieldList {get; set;}
    public list<String> DefaultValueTextList {get; set;}
    private map<String, String> DefaultValueMap;
    public Boolean DisableSave {get; set;}
    public Boolean DisableCancel {get; set;}
    public String ActionId {get; set;}
    public list<ObjectWrapper> ObjectList {get; set;}
    private list<ColumnWrapper> ColumnWrapList;
    public String ActionRowNumber {get; set;}
    private Integer AddedRowCount;
    
    public MultiRecordComponentController() {
        DisableSave = true;
        DisableCancel = true;
        AddedRowCount = 0;
    }

    /***
    * ColumnList - get/set methods. get initializes columns and list entries on first load
    ***/
    public list<ColumnWrapper> ColumnList {
        get {
            if (ColumnWrapList == null) {
                InitValues();
                // load fields for table columns
                ColumnWrapList = LoadColumnList(sObjectType, FieldList, LabelOverrideMap);
                // load records in the table
                ObjectList = LoadObjectList(ParentRecId, sObjectType, FieldList, RelationField, OrderByField);
            }
            return ColumnWrapList;
        }
        set;
    }

    /***
    * InitValues - initialize maps with list data
    ***/
    public void InitValues() {
        // convert field label override lists to a map for easier lookup
        // Salesforce apex:attribute of type map doesn't current work properly.
        // this can updated to a map when/if SF fixes the attribute for maps
        LabelOverrideMap = new map<String, String>();
        if (LabelOverrideFieldList != null && LabelOverrideTextList != null) {
            system.debug(LabelOverrideFieldList + ':::' + LabelOverrideTextList);
            for (Integer i=0; i < LabelOverrideFieldList.size(); i++) {
                if (i < LabelOverrideTextList.size()) {
                    LabelOverrideMap.put(LabelOverrideFieldList[i], LabelOverrideTextList[i]);
                }
            }
        }
        system.debug('LabelOverrideMap' + LabelOverrideMap);
        
        DefaultValueMap = new map<String, String>();
        if (DefaultValueFieldList != null && DefaultValueTextList != null) {
            system.debug(DefaultValueFieldList + ':::' + DefaultValueTextList);
            for (Integer i=0; i < DefaultValueFieldList.size(); i++) {
                if (i < DefaultValueTextList.size()) {
                    DefaultValueMap.put(DefaultValueFieldList[i], DefaultValueTextList[i]);
                }
            }
        }
        system.debug('DefaultValueMap' + DefaultValueMap);
    }
    
    /***
    * DoAdd - add a record to the list
    ***/
    public void DoAdd() {
        DisableSave = false;
        DisableCancel = false;
        ObjectWrapper TmpObjWrap = new ObjectWrapper(Schema.getGlobalDescribe().get(sObjectType).newSObject(), true);
        TmpObjWrap.obj.put(RelationField, ParentRecId);
        for (String s : DefaultValueMap.keySet()) {
            TmpObjWrap.obj.put(s, DefaultValueMap.get(s));
        }
        AddedRowCount += 1;
        TmpObjWrap.AddedRowNumber = String.valueOf(AddedRowCount);
        ObjectList.add( TmpObjWrap );
    }

    /***
    * DoCancel - remove added lines and change lines back to display mode
    ***/
    public void DoCancel() {
        DisableSave = true;
        DisableCancel = true;
        for (Integer i=0; i < ObjectList.size(); i++) {
            // remove added lines that were not saved
            if (ObjectList[i].obj.Id == null) {
                ObjectList.remove(i);
                i--;
                continue;
            }
            // change to display mode
            ObjectList[i].IsEditMode = false;
        }
    }
    
    /***
    * DoSave - Save edited and added records. then refresh/requery the list
    ***/
    public void DoSave() {
        DisableSave = true;
        DisableCancel = true;
        list<sObject> UpdateList = new list<sObject>();
        list<sObject> InsertList = new list<sObject>();
        for (ObjectWrapper o : ObjectList) {
            if (o.IsEditMode == true) {
                if (o.obj.Id == null) {
                    InsertList.add(o.obj);
                } else {
                    UpdateList.add(o.obj);
                }
            }
        }
        System.Savepoint sp1 = Database.setSavepoint();
        try {
            system.debug('UpdateList: ' + UpdateList);
            system.debug('InsertList: ' + InsertList);
            if (UpdateList.size() > 0) {
                update UpdateList;
            }
            if (InsertList.size() > 0) {
                insert InsertList;
            }
        } catch (System.DmlException e) {
            system.debug('error: ' + e);
            Database.rollback(sp1);
            for (Integer i=0; i < e.getNumDml(); i++) {
                ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getDmlMessage(i)));
            }
            return;
        } catch (exception e) {
            system.debug('error: ' + e);
            Database.rollback(sp1);
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, 'An error updating the records: ' + e.getMessage()));
            return;
        }
        // requery in case field list contains fields referencing related objects
        if (UpdateList.size() > 0 || InsertList.size() > 0) {
            ObjectList.clear();
            ObjectList = LoadObjectList(ParentRecId, sObjectType, FieldList, RelationField, OrderByField);
        }
    }
    
    /***
    * DoDelete - delete the selected record
    ***/
    public void DoDelete() {
        if (ActionId == null || ActionId.trim().length() == 0) {
            return;
        }
        try {
            database.delete(ActionId);
            for (Integer i=0; i < ObjectList.size(); i++) {
                if (ActionId == ObjectList[i].obj.Id) {
                    ObjectList.remove(i);
                    break;
                }
            }
        } catch (exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error, e.getMessage()));
        }
        ActionId = null;
        return;
    }

    /***
    * DoRemove - remove usaved added rows of the list
    ***/
    public void DoRemove() {
        if (ActionRowNumber == null || ActionRowNumber.trim().length() == 0) {
            return;
        }
        for (Integer i=0; i < ObjectList.size(); i++) {
            if (ActionRowNumber == ObjectList[i].AddedRowNumber) {
                ObjectList.remove(i);
                break;
            }
        }
        ActionRowNumber = null;
        return;
    }
    
    /***
    * DoEdit - dispaly a record with editable fields
    ***/
    public void DoEdit() {
        if (ActionId == null || ActionId.trim().length() == 0) {
            return;
        }
        DisableSave = false;
        DisableCancel = false;
        for (ObjectWrapper o : ObjectList) {
            if (o.obj.Id != null && ActionId == o.obj.Id) {
                o.IsEditMode = true;
                break;
            }
        }
        ActionId = null;
        return;
    }
    
    /***
    * LoadObjectList - query the object, and load results into the object wrapper list
    ***/
    public static list<ObjectWrapper> LoadObjectList(String InitRecId, String InitSObj, list<String> InitFieldList, String InitRelField, String InitOrderByField) {
        list<ObjectWrapper> ObjWrapList = new list<ObjectWrapper>();
        list<String> QueryFieldList = new list<String>();
        set<String> QueryFieldSet = new set<String>();
        
        // add id to field
        QueryFieldList.addAll(InitFieldList);
        QueryFieldSet.addAll(InitFieldList);
        if (QueryFieldSet.contains('id')) {
            QueryFieldList.add('id');
        }
        
        if (InitOrderByField == null || InitOrderByField.trim().length() == 0) {
            InitOrderByField = 'CreatedDate';
        }
        String TmpQuery;
        TmpQuery = 'Select ' + String.escapeSingleQuotes( String.join(QueryFieldList,', ') )+ 
                    ' From ' + String.escapeSingleQuotes( InitSObj ) + 
                    ' Where ' + String.escapeSingleQuotes( InitRelField ) + '=\'' + String.escapeSingleQuotes( InitRecId ) + '\'' + 
                    ' Order by ' + String.escapeSingleQuotes( InitOrderByField ) +
                    ' limit 1000';
        system.debug('Query: ' + TmpQuery);
                
        list<sObject> TmpObjectList = database.query(TmpQuery);
        for (sObject o : TmpObjectList) {
            ObjWrapList.add(new ObjectWrapper(o, false));
        }
        return ObjWrapList;
    }
    
    /***
    * LoadColumnList - load properties for columns to display into a list
    ***/
    public static list<ColumnWrapper> LoadColumnList(String InitSObj, list<String> InitFieldList, map<String, String> LabelOverrideMap) {
        list<ColumnWrapper> TmpColumnList = new list<ColumnWrapper>();
        system.debug('sObj:' + InitSObj);
        // map of fields for the object
        map<String, Schema.sObjectField> FieldMap = Schema.getGlobalDescribe().get(InitSObj).getDescribe().fields.getMap();
        
        for (String s : InitFieldList) {
            Schema.sObjectField FieldObj;
            Schema.DescribeFieldResult DescField;
            String TmpLabel;
            Boolean TmpIsEditable;
            Boolean TmpIsObjField;
            
            // check override label
            
            // check read only ************************************************************ 
            // defaults
            TmpIsEditable = false;
            TmpIsObjField = false;
            TmpLabel = s;

            // fields of the object retrieve label and permissions, related object fields do not
            FieldObj = FieldMap.get(s);
            if (FieldObj != null) {
                DescField = FieldObj.getDescribe();
                if (DescField != null) {
                    if (DescField.isAccessible() == false) {
                        system.debug('Field: ' + s + ' is not accessable for the user. Field ignored.');
                        continue;
                    }
                
                    if (DescField.isUpdateable() == true && DescField.isCreateable() == true) {
                        TmpIsEditable = true;
                    }
                    TmpLabel = FieldObj.getDescribe().getLabel();
                    TmpIsObjField = true;
                }
            }
            
            // use override label when found
            if (LabelOverrideMap.containsKey(s) == true) {
                    TmpLabel = LabelOverrideMap.get(s);
            }
                
            TmpColumnList.add(new ColumnWrapper(s, TmpLabel, TmpIsEditable, TmpIsObjField));
        }
        system.debug('ColumnList: ' + TmpColumnList);
        return TmpColumnList;
    }
    
    /***
    * ColumnWrapper - subclass for field properties of columns that will be displayed in the list
    ***/
    public class ColumnWrapper { 
        public String FieldName {get; set;}
        public String FieldLabel {get; set;}
        public Boolean IsEditable {get; set;}
        public Boolean IsObjField {get; set;}
        
        public ColumnWrapper(String FieldName, String FieldLabel, Boolean IsEditable, Boolean IsObjField) {
            this.FieldName = FieldName; 
            this.FieldLabel = FieldLabel;
            this.IsEditable = IsEditable;
            this.IsObjField = IsObjField;
        }
    }
    
    /***
    * ObjectWrapper - subclass for the sObject record with additional properties
    ***/
    public class ObjectWrapper { 
        public sObject obj {get; set;}
        public Boolean IsEditMode {get; set;}
        public String AddedRowNumber {get; set;}
        
        public ObjectWrapper(sObject obj, Boolean IsEditMode) {
            this.obj = obj;
            this.IsEditMode = IsEditMode;
        }
    }
}

ExampleOppPage1.page
 
<apex:page extensions="ExampleOppPage1Controller" standardController="Opportunity">
   <apex:form >
      <c:MultiRecordComponent aParentRecId="{!MyRecId}" asObjectType="{!MysObj}" aFieldList="{!MyFieldList}"
         aRelationField="{!MyRelationField}" aLabelOverrideFieldList="{!MyLabelOverrideFieldList}"
         aLabelOverrideTextList="{!MyLabelOverrideTextList}" aDefaultValueFieldList="{!MyDefaultValueFieldList}" 
         aDefaultValueTextList="{!MyDefaultValueTextList}" aBlockTitle="{!MyBlockTitle}" aAllowAdd="{!MyAllowAdd}"
         aAllowEdit="{!MyAllowEdit}" aAllowDelete="{!MyAllowDelete}" />
   </apex:form>
</apex:page>

ExampleOppPage1Controller
 
public with sharing class ExampleOppPage1Controller {
    public Boolean MyAllowAdd {get; set;}
    public Boolean MyAllowEdit {get; set;}
    public Boolean MyAllowDelete {get; set;}
    public String MysObj {get; set;}
    public String MyRecId {get; set;}
    public String MyRelationField {get; set;}
    public list<String> MyFieldList {get; set;}
    public list<String> MyLabelOverrideFieldList {get; set;}
    public list<String> MyLabelOverrideTextList {get; set;}
    public list<String> MyDefaultValueFieldList {get; set;}
    public list<String> MyDefaultValueTextList {get; set;}
    public String MyBlockTitle {get; set;}
	private Opportunity myOpportunity;

    
	public ExampleOppPage1Controller(ApexPages.StandardController stdController) {
		myOpportunity = (Opportunity)stdController.getRecord();
        MyAllowAdd = true;
        MyAllowEdit = true;
        MyAllowDelete = true;
        MyBlockTitle = 'Documents Required Tracking';
        MysObj = 'Required_Documents__c';
        MyRecId = myOpportunity.Id;  // fill in your record Id here
        MyRelationField = 'Opportunity__c';
        MyFieldList = new list<String> {'Name',
                                        'Status__c',
                                        'Client_Facing_Note__c',
                                        'Internal_Note__c'};
        MyLabelOverrideFieldList = new list<String> { 'Opportunity.Name'};
        MyLabelOverrideTextList = new list<String> {'Opportunity Name'};
        MyDefaultValueFieldList = new list<String> {'Name'};
        MyDefaultValueTextList = new list<String> {'<default value>'};
            }
}

Thanks for your time!
I have a flow with a series of screens. Each screen has one radio button choice field. There are decision elements to determine which screen users see next depending on their choices. This is a public facing needs analysis questionaire for our customers.

Right now users must select their choice, and then click the next button to proceed to the next screen.

I would like for it to instead advance to the next screen immediately upon clicking the radio button choice.

This will avoid the extra click per page and allow customers to speadily adance through our needs analysis questionaire.

Is this something that can be done using javascript/jQuery?

Thanks!
I have a flow with a series of screens. Each screen has one radio button choice field. There are decision elements to determine which screen users see next depending on their choices. This is a public facing needs analysis questionaire for our customers.

Right now users must select their choice, and then click the next button to proceed to the next screen.

I would like for it to instead advance to the next screen immediately upon clicking the radio button choice.

This will avoid the extra click per page and allow customers to speadily adance through our needs analysis questionaire.

Is this something that can be done using javascript/jQuery?

Thanks!