Define Your Own Custom Editors

You can define your own Custom Editors. With them, you can create a VisualForce page to use in place of the standard Salesforce edit page. The steps for creating a Custom Editor for Quotes are detailed in the example below. Use the Quote example below as a model for creating your own Custom Editors.

To ensure the VisualForce page for custom editors renders fields in the same order as they appear in the FieldFX Back Office page layout, you must set the Custom FX Setting CustomEditorRenderPlaceHolder__c to TRUE and include the following code in the VisualForce page:

<apex:repeat value="{!LayoutSections}" var="s">
  <apex:pageBlockSection columns="{!s.columns}" title="{!s.heading}">
  <apex:repeat value="{!s.fields}" var="f">
    <apex:inputField value="{!EntityObj[f.fieldName]}" styleClass="fxdatafield {!f.fieldName}" required="{!f.required}" html-data-fieldname="{!f.fieldName}" rendered="{!AND(f.type =='Field', OR(AND(IsInsert, f.editableForNew), AND(NOT(IsInsert),f.editableForUpdate)))}" />
    <apex:outputField value="{!EntityObj[f.fieldName]}" styleClass="fxdatafield {!f.fieldName}" html-data-fieldname="{!f.fieldName}" rendered="{!AND(f.type=='Field', OR(AND(IsInsert, NOT(f.editableForNew)), AND(NOT(IsInsert), NOT(f.editableForUpdate))))}" />
  <apex:outputText styleClass="dataCol empty" value="" rendered="{!NOT(f.type=='Field')}" />
</apex:repeat>

Example: Define a Custom Editor for Quotes

In this example, we will show you how to define a Custom Editor for quotes.

Feature Impact

Once we set up this Custom Editor, these lookups on the Quotes tab work as follows.

Customer Lookup

Setting Value Impact

Lookup Filter

"RecordType.DeveloperName='Customer' AND {!FXNamespace}IsArchived__c=false"

Impact: Only customer accounts that aren’t marked archived display available for selection.

Fields for Available Records

Id, Name

The listed fields display for customers in search results.

Fields Analyzed for Keyword Searches

Name

Keyword searches analyze the names of customers.

Sort Order of Available Records

Name

Customers sort in alphabetical order by name.

Office Lookup

Setting Value Impact

Lookup Filter

"RecordType.DeveloperName='Office' AND {!FXNamespace}IsArchived__c=false"

Impact: Only office accounts that aren’t marked archived display available for selection.

Fields for Available Records

Id, Name

The listed fields display for offices in search results.

Fields Analyzed for Keyword Searches

Name

Keyword searches analyze the names of offices.

Sort Order of Available Records

Name

Offices sort in alphabetical order by name.

Price Book Lookup

Setting Value Impact

Lookup Filter

{!FXNamespace}Is_Active__c=true AND ({!FXNamespace}Account__c=NULL OR {!FXNamespace}Account__c='@@this.{!FXNamespace}CustomerAccount__c@@') AND ({!FXNamespace}Office__c=NULL OR {!FXNamespace}Office__c='@@this.{!FXNamespace}Office__c@@') AND ({!FXNamespace}Segment__c=NULL OR {!FXNamespace}Segment__c='@@this.{!FXNamespace}Segment__c@@')

Impact: Only active price books linked to the job’s customer, office, or segment display available for selection.

Fields for Available Records

Name,{!FXNamespace}Account__r.Name,{!FXNamespace}Office__r.Name,{!FXNamespace}Segment__c

The listed fields display for price books in search results.

Fields Analyzed for Keyword Searches

Name,{!FXNamespace}Account__r.Name,{!FXNamespace}Office__r.Name,{!FXNamespace}Segment__c

Keyword searches analyze the name, customer, office, and segment of price books.

Sort Order of Available Records

Name

Price books sort in alphabetical order by name.

Setup Instructions

Create a New FXEditorSearcher Visualforce Page

  1. From Setup, enter pages in the Quick Find box, then select Visualforce Pages.

  2. Click New.

  3. In Label, enter FXEditorSearcher.

  4. Copy the following code:

    Click to expand
    <apex:page controller="FX5.FXEditorSearchController" title="{!SearcherTitle}" showHeader="false" sideBar="false" id="pg" lightningstylesheets="true">
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
      <apex:sectionHeader title="{!SearcherTitle}" rendered="true"/>
      <apex:form >
        <apex:outputpanel id="page" layout="block" style="margin:5px;padding:5px;padding-top:2px;">
          <apex:actionregion >
            <apex:outputpanel id="top" layout="block" style="margin:5px;padding:5px;padding-top:2px;">
              <apex:outputlabel value="Keyword" style="font-weight:Bold;padding-right:5px;" for="txtSearch" />
              <apex:inputtext id="txtSearch" value="{!KeywordString}" />
              <span style="padding-left:5px"><apex:commandbutton id="btnGo" onclick="EnableMe('btnGo', false);EnableMe('txtSearch', false)" value="Go" status="searchstatus" action="{!Search}" rerender="searchResults" oncomplete="EnableMe('btnGo', true);EnableMe('txtSearch', true);setsearchfocus();"></apex:commandbutton></span>
              <apex:outputlabel style="padding-left:25px" value="Wildcard '*' search is not supported." />
            </apex:outputpanel>
            <div id="clearresults" style="display: none;">
              <apex:outputpanel id="clearsearch">
                &#60;
              <apex:commandlink value="Clear Search Results" rerender="searchResults" onclick="j$('[id$=\'txtSearch\']').val('');EnableMe('btnGo', false);EnableMe('txtSearch', false)" status="searchstatus" action="{!Search}" oncomplete="EnableMe('btnGo', true);EnableMe('txtSearch', true);setsearchfocus();" />
               <br />
               <br />
               <br />
               </apex:outputpanel>
            </div>
            <apex:outputpanel id="pnlSearchResults" style="margin:5px;height:350px;overflow-Y:auto;" layout="block">
              <apex:actionstatus id="searchstatus">
                <apex:facet name="start">
                  <apex:outputpanel >
                    <apex:image value="/img/loading32.gif" style="height: 15px" />
                  </apex:outputpanel>
                </apex:facet>
                <apex:facet name="stop">
                  <apex:pageblock id="searchResults">
                    <apex:pageblocktable value="{!results}" var="a" id="tblResults">
                      <apex:column >
                        <apex:facet name="header">
                          <apex:outputpanel >Name</apex:outputpanel>
                        </apex:facet>
                        <apex:outputlink value="javascript:top.window.opener.lookupPick2('{!FormTag}','{!TextBox}_lkid','{!TextBox}','{!a.Id}', &#39;{!a['Name']}&#39;, false)" rendered="{!NOT(ISNULL(a.Id))}">{!a["Name"]}</apex:outputlink>
                      </apex:column>
                      apex:repeat value="{!DisplayColumns}" var="f">
                       <apex:column >
                         <apex:facet name="header">
                           <apex:outputpanel >{!f.Label}</apex:outputpanel>
                         </apex:facet>
                         <apex:outputlabel value="{!a[f.FieldPath]}" rendered="{!NOT(ISNULL(a[f.FieldPath]))}" />
                       </apex:column>
                      /apex:repeat>
                    </apex:pageblocktable>
                  </apex:pageblock>
                </apex:facet>
              </apex:actionstatus>
            </apex:outputpanel>
          </apex:actionregion>
        </apex:outputpanel>
      </apex:form>
    
      <script type="text/javascript">
        var j$ = jQuery.noConflict();
          j$(document).ready(function() {
            j$("[id$='txtSearch']:first").focus();
            var defaultsearch = getUrlParameter('lksrch');
            if (defaultsearch != undefined) {
              if (j$("[id$='txtSearch']").val() != defaultsearch) {
                j$("[id$='txtSearch']").val(defaultsearch);
                j$("[id$='btnGo']").trigger("click");
              }
            }
          });
        j$("[id$='txtSearch']").keydown(function(e) {
          if (e.keyCode == 13) {
            e.preventDefault();
            j$("[id$='btnGo']").trigger("click");
          }
        });
       function getUrlParameter(sParam) {
         var sPageURL = decodeURIComponent(window.location.search.substring(1)),
         sURLVariables = sPageURL.split('&'),
         sParameterName, i;
         for (i = 0; i < sURLVariables.length; i++) {
           sParameterName = sURLVariables[i].split('=');
           if (sParameterName[0] === sParam) {
             return sParameterName[1] === undefined ? true : sParameterName[1];
           }
         }
       };
       function EnableMe(objid, enabled) {
         if (objid != undefined && enabled != undefined) {
           if (enabled == false) {
             j$('input[id$=' + objid + ']').attr('disabled', 'disabled');
             j$('input[id$=' + objid + ']').addClass('btnDisabled');
             j$("#clearresults").hide();
           } else {
             j$('input[id$=' + objid + ']').removeAttr('disabled', 'disabled');
             j$('input[id$=' + objid + ']').removeClass('btnDisabled');
           }
         }
        }
       function setsearchfocus() {
         j$('input[id$=txtSearch]').focus();
         if (j$("[id$='txtSearch']").val() != '') {
           j$("#clearresults").show();
         } else {
           j$("#clearresults").hide();
         }
       }
      </script>
    </apex:page>
  5. Replace the code on the Visualforce Markup tab by pasting over it with the code you copied.

  6. Click the Version Settings tab.

  7. Click -- Select to Add Installed Package -- and select "FieldFX Base Package".

  8. Click Save.

Add the FXQuoteEditor

  1. From Setup, enter pages in the Quick Find box, then select Visualforce Pages.

  2. Click New.

  3. In Label, enter FXQuoteEditor.

  4. Copy the following code:

    Click to expand
    <apex:page standardcontroller="FX5__Quote__c" extensions="FX5.FXEditorController,FX5.UtilityGetInstanceUrl" sidebar="false" tabstyle="FX5__Quote__c" lightningstylesheets="true">
      <apex:sectionheader title="Edit {!EntityTypeDisplayLabel}" rendered="{!NOT(IsInsert)}" />
        <apex:sectionheader title="New {!EntityTypeDisplayLabel}" rendered="{!IsInsert}" />
          <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
          <apex:includescript value="https://code.jquery.com/jquery-2.2.4.min.js" />
          <script src="https://cdn.fieldfx.com/{!$Api.Session_ID}/{!FXNamespaceWithoutUnderscores + '__'}/customeditor/LATEST/main.js?sitePrefix={!$Site.Prefix}&siteDomain={!$Site.Domain}&instanceUrl={!instanceUrl}" type="text/javascript"></script>
          <apex:form id="editorForm">
             <apex:pagemessages />
             <apex:pageblock mode="edit" id="pbMain">
                <apex:pageblockbuttons >
                  <apex:commandbutton action="{!save}" value="Save" id="cmdSave" />
                  <apex:commandbutton action="{!saveAndNew}" value="Save & New" id="cmdSaveAndNew" />
                  <apex:commandbutton action="{!doCancel}" value="Cancel" immediate="true" />
                </apex:pageblockbuttons>
                <apex:repeat value="{!LayoutSections}" var="s">
                  <apex:pageblocksection columns="{!s.columns}" title="{!s.heading}" collapsible="false">
                    <apex:repeat value="{!s.fields}" var="f">
                      <apex:inputfield value="{!EntityObj[f.fieldName]}" styleclass="fxdatafield {!f.fieldName}" required="{!f.required}" html-data-fieldname="{!f.fieldName}" />
                    </apex:repeat>
                  </apex:pageblocksection>
                </apex:repeat>
              </apex:pageblock>
             <apex:inputhidden value="{!LayoutJsonString}" />
          </apex:form>
          <script type="text/javascript">
             var customSearch = '{!FXNamespace}CustomerAccount__c,{!FXNamespace}Office__c,{!FXNamespace}Price_Book__c';
             var searchAttrs = `{
              "{!FXNamespace}CustomerAccount__c" : {
                  "QueryFilter" : "RecordType.DeveloperName='Customer' AND {!FXNamespace}IsArchived__c=false",
                  "QueryFields" : "Id,Name",
                  "KeywordSearchFields" : "Name",
                  "OrderBy" : "Name"
              },
              "{!FXNamespace}Office__c" : {
                 "QueryFilter" : "RecordType.DeveloperName='Office' AND {!FXNamespace}IsArchived__c=false",
                 "QueryFields" : "Id,Name",
                 "KeywordSearchFields" : "Name",
                 "OrderBy" : "Name"
              },
              "{!FXNamespace}Price_Book__c" : {
                "QueryFilter" : "{!FXNamespace}Is_Active__c=true AND ({!FXNamespace}Account__c='@@this.{!FXNamespace}CustomerAccount__c@@' OR {!FXNamespace}Global_Price_Book__c=true)",
                "QueryFields" : "Name,{!FXNamespace}Account__r.Name,{!FXNamespace}Office__r.Name,{!FXNamespace}Segment__c",
                "KeywordSearchFields" : "Name,{!FXNamespace}Account__r.Name,{!FXNamespace}Office__r.Name,{!FXNamespace}Segment__c",
                "OrderBy" : "Name"
              }
             }`;
            function openLookup(baseURL, width, modified, searchParam) {
              var j$ = jQuery.noConflict();
              var originalbaseURL = baseURL;
              var originalwidth = width;
              var originalmodified = modified;
              var originalsearchParam = searchParam;
              var entityType = '{!EntityType}';
              var entityId = '{!EntityId}';
              var originatorId = '{!ReturnUrlObjectId}';
              var lookupType = getParameterByName('lktp', baseURL);
              var lookupCustomFieldId = getParameterByName('lknm', baseURL);
              var ctrl = j$('[Id$="' + lookupCustomFieldId + '"]')[0];
              var lookupCustomField = j$(ctrl).data('fieldname');
              if (modified == '1') baseURL = baseURL + searchParam;
              var pIndex = baseURL.indexOf('?');
              var params = baseURL.substring(pIndex);
              var isCustomLookup = customSearch.indexOf(lookupCustomField) !== -1;
              console.log('isCustomLookup for ' + lookupCustomField + ' : ' + isCustomLookup);
              if (isCustomLookup == true) {
                  var urlArr = baseURL.split("&");
                  var txtId = '';
                  if (urlArr.length > 2) {
                    urlArr = urlArr[1].split('=');
                    txtId = urlArr[1];
                  }
                  params = params.replace('&lknm=' + encodeURIComponent(lookupCustomFieldId), '&lknm=' + lookupCustomField);
                  console.log('params: ' + params);
                  var jsonSubjectEntity = getContextEntity();
                  // Following is the url of Custom Lookup page. You need to change that accordingly
                  baseURL = "/apex/FXEditorSearcher" + params;
                  baseURL += "&txt=" + txtId;
                  baseURL += "&subjectType={!EntityType}&subjectId={!EntityId}&originatorId={!ReturnUrlObjectId}";
                  baseURL += "&subjectEntity=" + escapeUTF(jsonSubjectEntity);
                  // Following is the id of apex:form control "myForm". You need to change that accordingly
                  baseURL = baseURL + "&frm=" + escapeUTF("{!$Component.editorForm}");
                  if (modified == '1') {
                  baseURL = baseURL + "&lksearch=" + searchParam;
                }
                baseURL = baseURL + "&searchAttrs=" + searchAttrs;
                openPopup(baseURL, "lookup", 350, 480, "width=" + width + ",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
              } else {
                  if (modified == '1') originalbaseURL = originalbaseURL + originalsearchParam;
                  openPopup(originalbaseURL, "lookup", 350, 480, "width=" + originalwidth + ",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
              }
            }
          </script>
    </apex:page>
  5. Replace the code on the Visualforce Markup tab by pasting over it with the code you copied.

  6. Click the Version Settings tab.

  7. Click -- Select to Add Installed Package -- and select "FieldFX Base Package".

  8. Click Save.

  9. Continue with Configure the FXQuoteEditor.

Configure the FXQuoteEditor

  1. From Setup, enter object in the Quick Find box, then select Objects.

  2. Click Quote.

  3. Under Buttons, Links, and Actions,

    Features CrewPlanning Clone Edit Selection

    Even though this image is for the CrewPlanningEditor, the Edit option is located in the same location.

    1. Find these rows and complete these actions:

      • Clone

      • Edit

      • New

    2. In Override With, select "Visualforce", select "FXQuoteEditor".

    3. Click Save.

  4. Continue with Add a RESTEndPoint Remote Site Setting.

Add a RESTEndPoint Remote Site Setting

  1. Open the Quote tab.

  2. Open a quote.

  3. Click Edit.

    1. If the FXQuoteEditor displays, proceed to step grant permission to users.

    2. If an error message displays, continue with the next step.

  4. Copy the domain listed in the error message.

    Example 1. Sample domain to copy

    Use your customized my domain URL that is specific to your org instead of your instance specific URL. You can find your my domain here.

    Don’t use instanced URLs when logging in to Salesforce with code or as a user. When your org is moved to another Salesforce instance, code using the instanced URL breaks. If you find instanced URLs in your code, replace them with your My Domain login URL or the default Salesforce login URL. See Log In to Salesforce with Code for more information.

  5. From Setup, enter remote in the Quick Find box, then select Remote Site Settings

  6. Click New Remote Site.

  7. In Remote Site Name, enter RESTEndpoint.

  8. In Remote Site URL, enter the listed domain you copied earlier.

    The Remote Site URL field is case-sensitive. Make sure the URL you enter uses the correct capitalization.

  9. Click Active.

  10. Click Save.

  11. Continue with Grant Permission to Use the Added Visualforce Pages.

Grant Permission to Use the Added Visualforce Pages

  1. From Setup, enter permission in the Quick Find box, then select Permission Sets.

  2. Open a permission set for Visualforce pages.

  3. Under Apps, click Visualforce Page Access.

  4. Click Edit.

  5. Add "FXEditorSearcher" and "FXQuoteEditor" to Enabled Visualforce Pages.

  6. Click Save.

  7. Repeat these steps for other permission sets as needed.

Setup Other Custom Editors (if needed)