4

Salesforce Kahunas,

I need help understanding if the following is possible, and then help consolidating the code pieces into the appropriate spots.

I had a requirement to use a WebService for Address Validation on the standard Account BillingAddress fields. I wrote everything in a Visualforce page and placed it on the Account layout. Everything worked great. Check out screens here for a demo:

VF page embedded on layout. Click Edit Address to edit.

Editable Address Fields

Click Validate performs the WS call, and...

...returns results including compare flag

Click the link...

...to set the validated value to the field

Click all links if you want to

Save back to record, show R/O fields again

However, I missed the requirement that I needed to also have this validation on another object Address__c. So, I just copy pasted the code, and swapped out field names (e.g. City__c for BillingCity). But then I had the same code with small changes in 2 places. I didn't like it, but it was manageable.

Then surprise, surprise... Requirements change! I needed to also have this validation performed when entering new records for either object. Well, I found out you can't just drop in a Visualforce page into the "new record" layout, and you also can't use apex:detail to streamline it, so we will now be managing the new record "layouts" for both objects via VF pages. I would also be re-keying all the code... Meaning I would have the same code in 4 places! Not nice.

I realized that I should probably be using a component, which would keep all the same code in one location, and re-use it across the 2 different objects and their 2 page modes.

---------------------- EDIT: REMOVED CONTENT BETWEEN ----------------------

I am now struggling with understanding how to map the object fields to some kind of "generic" fields. Those generics I would pass into the callout and then update with the results. I imagine those would be the commandLinks, which I could then click to set the standard fields. Then, just use the StandardController QuickSave.

I know there's a lot of junk in here, so please let me know what to remove as I am still learning how this all works together.

THANK YOU!!!!

---------------------- EDIT: UPDATES BELOW ----------------------

Firstly, another enormous THANK YOU to SFDCFOX for the help already.

I am having trouble getting the callout to work now. It still works on my old VF pages, but not in the new one.

The ValidateAddress method runs, b/c I see the validationStatus set as the url for the ValidationService, and when I paste that in another tab, I get the JSON as normal. However, I don't think the callback method of processResponse is running. Any ideas?

Getting the response is the last blocker.

Much appreciated!

VF Page:

<apex:page standardController="Account" extensions="FOX_AddressValidationController">
    <apex:form >
        <c:FOX_addressValidationComponent addressValidationControl="{!extension}" />
    </apex:form>
</apex:page>

VF Component:

<apex:component allowDML="true" selfClosing="true">
    <apex:attribute name="addressValidationControl" type="FOX_AddressValidationController" description="..." required="true"/>

    <apex:pageBlock id="wrapper" mode="maindetail">
    tets: {!addressValidationControl.validatedAddressFields.street}

        <apex:pageBlockSection id="readonlyAddress" columns="2" rendered="{!NOT(addressValidationControl.showEditSection)}">
            <apex:pageBlockSectionItem >
                <apex:outputPanel layout="block">
                    <apex:outputLabel value="Main Address<br/>" for="mainAddress" style="white-space:pre;" escape="false"/>
                </apex:outputPanel>
                <apex:outputPanel layout="block">
                    <!--apex:image id="validityFlag" value="{!CASE(account.Address_Validity__c,'Valid','/img/samples/flag_green.gif','Invalid','/img/samples/flag_red.gif','/img/samples/flag_yellow.gif')}" style="margin: 0px 5px; vertical-align: sub;"/-->
                    <!--apex:outputText value="{!account.Address_Validity__c}"/-->
                    <apex:outputText value="<br/>{!addressValidationControl.record[addressValidationControl.addressFields.street]}<br/>{!addressValidationControl.record[addressValidationControl.addressFields.city]}, {!addressValidationControl.record[addressValidationControl.addressFields.state]} {!addressValidationControl.record[addressValidationControl.addressFields.postalCode]}<br/>{!addressValidationControl.record[addressValidationControl.addressFields.country]}<br/>" id="mainAddress" style="white-space:pre;" escape="false"/>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>

        <apex:pageBlockSection id="recordAddress" columns="2" rendered="{!addressValidationControl.showEditSection}">
            <apex:pageBlockSectionItem >
                <apex:outputLabel value="{!$ObjectType.Account.fields.BillingStreet.label}" for="Street"/>
                <apex:outputPanel layout="block" id="theStreet">
                    <apex:inputField value="{!addressValidationControl.record[addressValidationControl.addressFields.street]}" id="Street" onblur="checkValid();" taborderhint="1"/>
                    <apex:image id="streetIcon" rendered="{!addressValidationControl.showValidateButtons}" value="{!IF(ISBLANK(addressValidationControl.validatedAddressFields.street),'',IF(addressValidationControl.validatedAddressFields.street == addressValidationControl.record[addressValidationControl.addressFields.street],addressValidationControl.confirmIcon,addressValidationControl.errorIcon))}" style="margin: 0px 5px; vertical-align: sub;"/>
                    <apex:outputPanel rendered="{!NOT(isBlank(addressValidationControl.validatedAddressFields.street))}">
                        <apex:outputText value="[ "/>
                        <apex:commandLink value="{!addressValidationControl.validatedAddressFields.street}" action="{!addressValidationControl.useSuggested}" id="suggestedStreet" rerender="theStreet,saveButtons">
                            <apex:param name="suggested" value="Street"/>
                        </apex:commandLink>
                        <apex:outputText value=" ]"/>
                    </apex:outputPanel>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>


            <apex:pageBlockSectionItem >
                <apex:outputLabel value="{!$ObjectType.Account.fields.BillingCity.label}" for="City"/>
                <apex:outputPanel layout="block" id="theCity">
                    <apex:inputField value="{!addressValidationControl.record[addressValidationControl.addressFields.city]}" id="City" onblur="checkValid()" taborderhint="2"/>
                    <apex:image id="cityIcon" rendered="{!addressValidationControl.showValidateButtons}" value="{!IF(ISBLANK(addressValidationControl.validatedAddressFields.city),'',IF(addressValidationControl.validatedAddressFields.city == addressValidationControl.record[addressValidationControl.addressFields.city],addressValidationControl.confirmIcon,addressValidationControl.errorIcon))}" style="margin: 0px 5px; vertical-align: sub;"/>
                    <apex:outputPanel rendered="{!NOT(isBlank(addressValidationControl.validatedAddressFields.city))}">
                        <apex:outputText value="[ "/>
                        <apex:commandLink value="{!addressValidationControl.validatedAddressFields.city}" action="{!addressValidationControl.useSuggested}" id="suggestedCity" rerender="theCity,saveButtons">
                            <apex:param name="suggested" value="City"/>
                        </apex:commandLink>
                        <apex:outputText value=" ]"/>
                    </apex:outputPanel>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>

            <apex:pageBlockSectionItem >
                <apex:outputLabel value="{!$ObjectType.Account.fields.BillingState.label}" for="State"/>
                <apex:outputPanel layout="block" id="theState">
                    <apex:inputField value="{!addressValidationControl.record[addressValidationControl.addressFields.state]}" id="State" onblur="checkValid()" taborderhint="3"/>
                    <apex:image id="stateIcon" rendered="{!addressValidationControl.showValidateButtons}" value="{!IF(ISBLANK(addressValidationControl.validatedAddressFields.state),'',IF(addressValidationControl.validatedAddressFields.state == addressValidationControl.record[addressValidationControl.addressFields.state],addressValidationControl.confirmIcon,addressValidationControl.errorIcon))}" style="margin: 0px 5px; vertical-align: sub;"/>
                    <apex:outputPanel rendered="{!NOT(isBlank(addressValidationControl.validatedAddressFields.state))}">
                        <apex:outputText value="[ "/>
                        <apex:commandLink value="{!addressValidationControl.validatedAddressFields.state}" action="{!addressValidationControl.useSuggested}" id="suggestedState" rerender="theState,saveButtons">
                            <apex:param name="suggested" value="State"/>
                        </apex:commandLink>
                        <apex:outputText value=" ]"/>
                    </apex:outputPanel>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>

            <apex:pageBlockSectionItem >
                <apex:outputLabel value="{!$ObjectType.Account.fields.BillingPostalCode.label}" for="PostalCode"/>
                <apex:outputPanel layout="block" id="thePostalCode">
                    <apex:inputField value="{!addressValidationControl.record[addressValidationControl.addressFields.postalCode]}" id="PostalCode" onblur="checkValid()" taborderhint="4"/>
                    <apex:image id="postalIcon" rendered="{!addressValidationControl.showValidateButtons}" value="{!IF(ISBLANK(addressValidationControl.validatedAddressFields.postalCode),'',IF(addressValidationControl.validatedAddressFields.postalCode == addressValidationControl.record[addressValidationControl.addressFields.postalCode],addressValidationControl.confirmIcon,addressValidationControl.errorIcon))}" style="margin: 0px 5px; vertical-align: sub;"/>
                    <apex:outputPanel rendered="{!NOT(isBlank(addressValidationControl.validatedAddressFields.postalCode))}">
                        <apex:outputText value="[ "/>
                        <apex:commandLink value="{!addressValidationControl.validatedAddressFields.postalCode}" action="{!addressValidationControl.useSuggested}" id="suggestedPostalCode" rerender="thePostalCode,saveButtons">
                            <apex:param name="suggested" value="PostalCode"/>
                        </apex:commandLink>
                        <apex:outputText value=" ]"/>
                    </apex:outputPanel>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>






            <apex:pageBlockSectionItem >
                <apex:outputLabel value="{!$ObjectType.Account.fields.BillingCountry.label}" for="Country"/>
                <apex:outputPanel layout="block" id="theCountry">
                    <apex:inputField value="{!addressValidationControl.record[addressValidationControl.addressFields.country]}" id="Country" onblur="checkValid()" taborderhint="5"/>
                    <apex:image id="countryIcon" rendered="{!addressValidationControl.showValidateButtons}" value="{!IF(ISBLANK(addressValidationControl.validatedAddressFields.country),'',IF(addressValidationControl.validatedAddressFields.country == addressValidationControl.record[addressValidationControl.addressFields.country],addressValidationControl.confirmIcon,addressValidationControl.errorIcon))}" style="margin: 0px 5px; vertical-align: sub;"/>
                    <apex:outputPanel rendered="{!NOT(isBlank(addressValidationControl.validatedAddressFields.country))}">
                        <apex:outputText value="[ "/>
                        <apex:commandLink value="{!addressValidationControl.validatedAddressFields.country}" action="{!addressValidationControl.useSuggested}" id="suggestedCountry" rerender="theCountry,saveButtons">
                            <apex:param name="suggested" value="Country"/>
                        </apex:commandLink>
                        <apex:outputText value=" ]"/>
                    </apex:outputPanel>
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>


            <apex:pageBlockSectionItem >
                <apex:outputLabel value=""/>
                <apex:outputPanel layout="block">
                    <apex:commandButton id="validateAddressButton" action="{!addressValidationControl.validateAddress}" value="Validate Address" reRender="wrapper" status="pageStatus" tabindex="60"/>
                    <apex:actionStatus id="pageStatus">
                        <apex:facet name="start">
                            <apex:outputPanel >
                                <img alt="" title="" src="/img/loading32.gif" width="25" height="25" />
                                <apex:outputLabel value="Validating..."/>
                            </apex:outputPanel>            
                        </apex:facet>
                    </apex:actionStatus> 
                </apex:outputPanel>
            </apex:pageBlockSectionItem>
            <apex:pageBlockSectionItem >
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>

        <apex:pageBlockSection columns="1" rendered="{!addressValidationControl.showValidateButtons}">
            <apex:pageBlockSectionItem >
                <apex:outputLabel value="Validation Status" for="validationStatus"/>
                <apex:outputText id="validationStatus" value="{!addressValidationControl.validationStatus}" />
            </apex:pageBlockSectionItem>
        </apex:pageBlockSection>
        <apex:pageBlockButtons location="bottom" onmouseover="checkValid2()">
            <apex:commandButton action="{!editAddress}" value="Edit Address" id="editAddressButton" rendered="{!NOT(addressValidationControl.showEditSection)}" reRender="wrapper"/>
            <apex:commandButton action="{!save}" value="Save" id="saveButton"  rendered="{!addressValidationControl.showEditSection}" reRender="wrapper" tabindex="70"/>
            <apex:commandButton action="{!cancel}" value="Cancel" id="cancelButton" reRender="wrapper" rendered="{!addressValidationControl.showEditSection}" tabindex="80"/>
        </apex:pageBlockButtons>
    </apex:pageBlock>


</apex:component>

Apex Class:

public with sharing class FOX_AddressValidationController {
    public ApexPages.Standardcontroller controller { get; set; }
    public FOX_AddressValidationController extension { get { return this; } }
    public Sobject record { get; set; }
    public FieldMap addressFields { get; set; }
    public FieldMap validatedAddressFields { get; set; }

    public String suggestedStreet { get; set; }
    public String suggestedCity { get; set; }  
    public String suggestedState { get; set; }
    public String suggestedPostalCode { get; set; }
    public String suggestedCountry { get; set; }  

    public String confirmIcon{get;set;}
    public String errorIcon{get;set;}
    public String validationStatus{get;set;}
    public Boolean showEditSection { get; set; }
    public Boolean showValidateButtons { get; set; }

    public FOX_AddressValidationController () {
        // Provide zero-param constructor for component
    }
    public FOX_AddressValidationController (ApexPages.StandardController controller) {
        this.controller = controller;
        record = controller.getRecord();

        showEditSection = true;
        showValidateButtons = true;
        confirmIcon = '/img/msg_icons/confirm16.png'; 
        errorIcon = '/img/msg_icons/error16.png';
        // /img/msg_icons/warning16.png 
        // /img/func_icons/util/checkmark16.gif
        loadAddressData();
    }

    void loadAddressData() {
        addressFields = new FieldMap();
        validatedAddressFields = new FieldMap();
        if(record.Id != null) {
            if(record.getSObjectType() == Account.SObjectType) {
                Account thisRecord = (Account)record;
                Account dataSource = [SELECT BillingStreet, BillingCity, BillingState, BillingPostalCode, BillingCountry FROM Account WHERE Id = :record.Id];
                record.put('BillingStreet', dataSource.BillingStreet);
                record.put('BillingCity', dataSource.BillingCity);
                record.put('BillingState', dataSource.BillingState);
                record.put('BillingPostalCode', dataSource.BillingPostalCode);
                record.put('BillingCountry', dataSource.BillingCountry);
            }
            if(record.getSObjectType() == Address__c.SObjectType) {
                Address__c thisRecord = (Address__c)record;
                Address__c dataSource = [SELECT Line1__c, City__c, State__c, PostalCode__c, Country__c FROM Address__c WHERE Id = :record.Id];
                record.put('Line1__c', dataSource.Line1__c);
                record.put('City__c', dataSource.City__c);
                record.put('State__c', dataSource.State__c);
                record.put('PostalCode__c', dataSource.PostalCode__c);
                record.put('Country__c', dataSource.Country__c);
            }
        }
        if(record.getSObjectType() == Account.SObjectType) {
            addressFields.street = 'BillingStreet';
            addressFields.city = 'BillingCity';
            addressFields.state = 'BillingState';
            addressFields.postalCode = 'BillingPostalCode';
            addressFields.country = 'BillingCountry';
        }
        if(record.getSObjectType() == Address__c.SObjectType) {
            addressFields.street = 'Line1__c';
            addressFields.city = 'City__c';
            addressFields.state = 'State__c';
            addressFields.postalCode = 'PostalCode__c';
            addressFields.country = 'Country__c';
        }
        validatedAddressFields.street = '';
        validatedAddressFields.city = 'temp';
        validatedAddressFields.state = '';
        validatedAddressFields.postalCode = '';
        validatedAddressFields.country = '';
    }

    public PageReference editAddress(){
        //showReadonlySection = false;
        showEditSection = true;
        //showValidateButtons = false;
        return null;
    }

    // Unique label corresponding to the continuation
    public String RequestLabel;
    // Result of callout
    public String result {get;set;}
    // Callout endpoint as a named credential URL 
    // or, as shown here, as the long-running service URL
    private static String SERVICE_URL = 'callout:ValidationService/1.0/address/validate?';
    // Action method
    public Object validateAddress() {
        System.Debug('BEGIN validateAddress Method');
        validationStatus = 'clicked Validate';
        // Create continuation with a timeout
        Continuation con = new Continuation(40);
        // Set callback method
        con.continuationMethod='this.processResponse';
        // Create callout request
        HttpRequest req = new HttpRequest();
        List<String> parameters = new List<String>{};
        if(!String.isBlank((String)record.get(addressFields.street))){
            parameters.add('Line1='+EncodingUtil.urlEncode((String)record.get(addressFields.street), 'UTF-8'));
        }
        if(!String.isBlank((String)record.get(addressFields.city))){
            parameters.add('City='+EncodingUtil.urlEncode((String)record.get(addressFields.city), 'UTF-8'));
        }
        if(!String.isBlank((String)record.get(addressFields.state))){
            parameters.add('Region='+EncodingUtil.urlEncode((String)record.get(addressFields.state), 'UTF-8'));
        }
        if(!String.isBlank((String)record.get(addressFields.postalCode))){
            parameters.add('PostalCode='+EncodingUtil.urlEncode((String)record.get(addressFields.postalCode), 'UTF-8'));
        }
        SERVICE_URL += String.join(parameters, '&');
        req.setEndpoint(SERVICE_URL);
        req.setMethod('GET');
        // Add callout request to continuation
        this.RequestLabel = con.addHttpRequest(req);
        // Return the continuation
        validationStatus = SERVICE_URL;
        return con;  
    }


    // Callback method 
    public Object processResponse() { 
        System.Debug('BEGIN processResponse Method');
        validationStatus = 'processing Response';

        validatedAddressFields = new FieldMap();
        // Get the response by using the unique label
        HttpResponse response = Continuation.getResponse(this.RequestLabel);
        // Set the result variable that is displayed on the Visualforce page
        this.result = response.getBody();
        System.Debug(response.getBody());
        suggestedStreet = 'empty';
        suggestedCity = '';
        suggestedState = '';
        suggestedPostalCode = '';
        suggestedCountry = '';

        Map<String, Object> valMess = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
        if(valMess.get('ResultCode') == 'Success'){
            List<String> validationMess = new List<String>{};
            validationStatus = 'Validated. ';
            Map<String, Object> addr = (Map<String, Object>)valMess.get('Address');
            /*
            suggestedStreet = (String)addr.get('Line1');
            suggestedCity = (String)addr.get('City');
            suggestedState = (String)addr.get('Region');
            suggestedPostalCode = (String)addr.get('PostalCode');
            suggestedCountry = (String)addr.get('Country');
            */
            validatedAddressFields.street = (String)addr.get('Line1');
            validatedAddressFields.city = (String)addr.get('City');
            validatedAddressFields.state = (String)addr.get('Region');
            validatedAddressFields.postalCode = (String)addr.get('PostalCode');
            validatedAddressFields.country = (String)addr.get('Country');
            /*if((String)record.get(addressFields.street) != addr.get('Line1')){
                validationMess.add('Line 1');
            }
            if((String)record.get(addressFields.city) != addr.get('City')){
                validationMess.add('City');
            }
            if((String)record.get(addressFields.state) != addr.get('Region')){
                validationMess.add('State');
            }
            if((String)record.get(addressFields.postalCode) != addr.get('PostalCode')){
                validationMess.add('Postal Code');
            }               
            if((String)record.get(addressFields.country) != addr.get('Country')){
                validationMess.add('Country');
            }*/

            if(validationMess.size() > 0){
                String isAre = ' are';
                if(validationMess.size() > 2){
                    String lastMess = validationMess.remove(validationMess.size()-1);
                    validationStatus += String.join(validationMess, ', ');
                    validationStatus += ' and ' + lastMess;                    
                }else if(validationMess.size() > 1){
                    validationStatus += String.join(validationMess, ' and ');
                }else{
                    validationStatus += validationMess[0];
                    isAre = ' is';
                }
                validationStatus += isAre + ' different.';
            }
        } else if(valMess.get('ResultCode') == 'Error'){
            validationStatus = 'Error:';
            List<Object> messes = (List<Object>)valMess.get('Messages');
            for(Object a : messes){
                Map<String, Object> a2 = (Map<String, Object>)a;
                validationStatus += ' ' + a2.get('Summary');
                validationStatus += ' ' + a2.get('Details');      
            }
        } else {
            validationStatus = 'Failed to Validate.';
        }
        //showValidateButtons = true;
        checkValidity();
        // Return null to re-render the original Visualforce page
        return null;
    }

    public void checkValidity() {
        // Do your validation stuff here
        /*if(record[addressFields.street] == validatedAddressFields.street && 
           record[addressFields.city] == validatedAddressFields.city && 
           record[addressFields.state] == validatedAddressFields.state && 
           record[addressFields.postalCode] == validatedAddressFields.postalCode && 
           record[addressFields.country] == validatedAddressFields.country){*/
        if(true){
            validationStatus = validationStatus + 'Valid';
        } else {
            validationStatus = validationStatus + 'Invalid';
        }
    }
    public void useSuggested(){
        String suggested = Apexpages.currentPage().getParameters().get('suggested');
        Map<String, String> suggestionfields = new Map<String, String>{
            'Street' => addressFields.street,
            'City' => addressFields.city,
            'State' => addressFields.state,
            'PostalCode' => addressFields.postalCode,
            'Country' => addressFields.country
        };
        Map<String, String> suggestionvalues = new Map<String, String>{
            /*'Street' => suggestedStreet,
            'City' => suggestedCity,
            'State' => suggestedState,
            'PostalCode' => suggestedPostalCode,
            'Country' => suggestedCountry*/
            'Street' => validatedAddressFields.street,
            'City' => validatedAddressFields.city,
            'State' => validatedAddressFields.state,
            'PostalCode' => validatedAddressFields.postalCode,
            'Country' => validatedAddressFields.country
        };
        record.put(suggestionfields.get(suggested), suggestionvalues.get(suggested));
        checkValidity();
    }

    public PageReference save() {
        // You can do more here
        return controller.save();
    }

    public PageReference saveAndNew() {
        Insert record;
        PageReference newPage = new PageReference('/apex/FOX_New_Address');
        newPage.setRedirect(true);
        return newPage;
    }

    // Field mapping class. Keeps things organized.
    public class FieldMap {
        public String street { get; set; }
        public String city { get; set; }
        public String state { get; set; }
        public String postalCode { get; set; }
        public String country { get; set; }
    }
}
3

I actually wrote this once a few years ago. I don't have that code any more, as it is related to a prior employer I no longer have access to, but I can relate to you how I did it.

The trick is dynamic binding. You're just asking for trouble with all those hard-coded object names, fields, etc. Here's what your component controller should start off like:

public with sharing class AddressValidationController {
  public FieldMap addressFields { get; set; }
  public Sobject record { get; set; }
  public ApexPages.Standardcontroller controller { get; set; }
  public AddressValidationController extension { get { return this; } }
  public AddressValidationController () {
    // Provide zero-param constructor for component
  }
  public AddressValidationController (ApexPages.StandardController controller) {
    this.controller = controller;
    record = controller.getRecord();
    loadAddressData();
  }
  void loadAddressData() {
    addressFields = new FieldMap();
    if(record.Id != null) {
      if(record.getSObjectType() == Account.SObjectType) {
        Account thisRecord = (Account)record;
        Account dataSource = [SELECT BillingStreet, BillingCity... FROM Account WHERE Id = :record.Id];
        record.put('BillingStreet', dataSource.BillingStreet);
        ...
      }
      if(record.getSObjectType() == Address__c.SObjectType) {
        ...
      }
    }
    if(record.getSObjectType() == Account.SObjectType) {
      addressFields.street = 'BillingStreet';
      ...
    }
    if(record.getSObjectType() == Address__c.SObjectType) {
      ...
    }
  }
  public void checkValidity() {
    // Do your validation stuff here
  }
  public void useSuggested(){
    String suggested = Apexpages.currentPage().getParameters().get('suggested');
    Map<String, String> suggestionfields = new Map<String, String>{
      'Street' => addressFields.street,
      'City' => addressFields.city,
      ...
    },
    suggestionvalues = new map<String, String> {
      'Street' => suggestedStreet,
      'City' => suggestedCity,
       ...
    };
    record.put(suggestionmap.get(suggested), sugestionvalues.get(suggested));
    checkValidity();
  }
  public PageReference save() {
    // You can do more here
    return controller.save();
  }

  // Field mapping class. Keeps things organized.
  public class FieldMap {
     public String street { get; set; }
     public String city { get; set; }
     public String state { get; set; }
     public String postalCode { get; set; }
     public String country { get; set; }
   }
 }

In your component, pass in a reference to yourself:

<apex:component allowDML="true" selfClosing="true">
  <apex:attribute name="addressValidationControl" type="AddressValidationController" description="..." required="true" />

Then you can reference your data in the component:

<apex:inputField id="Street" label="Street" value="{!addressValidationControl.record[addressValidationControl.addressFields.street]}" />

And finally, the page you'd use will be nice and simple:

<apex:page standardController="Account" extensions="AddressValidationController">
   <apex:form>
     <c:addressValidation addressValidationControl="{!extension}" />
   </apex:form>
 </apex:page>

As you can see, ultimately, you'll end up with one shared apex class, one component, and the number of pages you need (four?). It's also pretty extensible. In our case, we did have two copies of some code for two objects, and they wanted to add a third... This design took a few days to really gel, so don't expect to get it all at once, but once you realize that you can do things dynamically, and pass the controller itself by reference, you'll realize that things are a lot easier than they first appear.

| improve this answer | |
  • Brian, WOW! Thank you so much for setting me in the right direction. A few Qs.... 1. The approach I was taking was if you needed this on another object, you just create a VF page and pass the fields as attributes. Everything else was generic. In your example, if I want this functionality on other objects, I must create VF pages and update the shared controller. I am sticking with your example, but am curious if I was wrong in my approach. 2. If I initially show the fields as read-only before "Edit Address" is clicked. Do I need to make the SOQL query? How would getRecord work in that setup? – chriscam85 Nov 30 '17 at 22:27
  • @chriscam85 (1) Passing in the fields kind of works, but setters are called after the constructor, so it makes things a bit messy. You'd have to engineer an apex:actionPoller or apex:actionFunction to call a separate initializer after rendering. You can do this (might even be fun), but it'll add time to your goals. I chose to do this in the constructor to simplify the code. (2) You could pass in the values, but you run into the same potential problem as (1), above. But if you want to, knock yourself out. The cool factor just doesn't seem to be worth it in the end. – sfdcfox Dec 1 '17 at 5:38
  • if you could help me with one last thing.... it seems that in the new setup, the continuation object is not running (i think), so I'm not processing the response from the WS. This is the last piece to get it all sorted out. Thank you. – chriscam85 Dec 5 '17 at 1:14
  • @chriscam85 just realized I missed this comment. For you get this sorted out? – sfdcfox Dec 21 '17 at 22:17
  • I still haven't been able to sort this out. I kinda moved on to other things for a bit, but I do need to get back to this soon. I didn't want to bug, since I felt like I needed to learn some more basics first (figuring out how to tell if the WS is actually being called or not). I edited the end of the original post with updated code if anything stands out to you. – chriscam85 Dec 22 '17 at 23:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.