Managing form dropdown options at the LP level

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

This post is an experiment: can a cool technical hack be communicated without extra prose... that is, can I shut up for once? If it doesn’t make sense, ask in the comments! 😉

 

image-8[1].png

In Form Editor, just add the field with type Select — options don’t need to be listed there.

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-0" default="Select...|">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-1" default="Choice 1">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-2" default="Choice 2">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-3" default="Choice 3">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-4" default="">
    <meta class="mktoString" mktoName="Job Title Option" id="Title-5" default="">
    <title>Test Page</title>
  </head>
  <body>
    <div class="mktoForm" id="exampleForm" mktoName="Example Form">
    </div>
    <datalist id="mktoFormSelectOptions">
      <option label="Title">${Title-0}</option>
      <option label="Title">${Title-1}</option>
      <option label="Title">${Title-2}</option>
      <option label="Title">${Title-3}</option>
      <option label="Title">${Title-4}</option>
      <option label="Title">${Title-5}</option>
    </datalist>
  </body>
</html>

An example Marketo LP template. Note the use of a <datalist> element, which is the perfect choice for storing offline options, while — importantly — correctly encoding values.

 

image-7[1].png

Customize mktoVariables at the page level to set options. Empty values are skipped. <friendly name>|<server value> syntax (like in Form Editor) is supported. 

 

 

/**
 * Dynamic <select> options from <datalist>
 * @author Sanford Whiteman
 * @version v1.0 2022-08-21
 * @copyright © 2022 Sanford Whiteman
 * @license Hippocratic 3.0: This license must appear with all reproductions of this software.
 *
 * Prereq: <datalist> structured like:
 *   <datalist id="mktoFormSelectOptions">
 *   <option label="fieldName">Select Fruit...|</option>
 *   <option label="fieldName">Apple|apple</option>
 *   <option label="fieldName">Orange|orange</option>
 *   <option label="fieldName">Pear|pear</option>
 *   <option label="anotherFieldName">Select Sport...|</option>
 *   <option label="anotherFieldName">Basketball|bball</option>
 * etc.
 */
{
  const options = {
    renderOnlyOnce : true // set to false to pair with VRs
  }

  MktoForms2.whenRendered(function(mktoForm){    
    const arrayify = getSelection.call.bind([].slice);
   
    let formEl = mktoForm.getFormElem()[0];

    let allDynamicSelectOptions = document.querySelector("#mktoFormSelectOptions");
   
    let optionsByField = arrayify(allDynamicSelectOptions.options)
    .reduce(function(acc,option){
       if( !(option.label in acc )){
         acc[option.label] = [];       
       }
       if( option.textContent ) {
         acc[option.label].push(option.textContent);
       }
       return acc;
    }, {});
      
    Object.keys(optionsByField)
    .forEach(function(fieldName){       
       let mktoVariableOptions = optionsByField[fieldName];             
       let selectEl = formEl.querySelector("select[name='" + fieldName + "']");
       
       if( (!options.renderOnlyOnce || selectEl.getAttribute("data-options-managed") != "true") && mktoVariableOptions.length > 0 ){
          selectEl.setAttribute("data-options-managed","true");
          
          arrayify(selectEl.options)
          .forEach(function(originalOption){
             selectEl.removeChild(originalOption);
          });    

          mktoVariableOptions
          .map(function(mktoVariableOption){
             let newOption = document.createElement("option");
             let optionDescriptor = mktoVariableOption.split("|");

             newOption.textContent = optionDescriptor[0];
             newOption.value = optionDescriptor[optionDescriptor.length == 1 ? 0 : 1];

             return newOption;
          })
          .forEach(function(newOption){
             selectEl.appendChild(newOption);
          });
       }
    })
  });
}

The Forms 2.0 JS to add to your template (recommend a separate <script> tag). Note you don’t need to list any fields in the JS, they’re picked up automatically from the <datalist>.

 

2022-08-21-15_32_24-CodePen---MktoForms2-__-Dynamic-Options-from-mktoVariables---Mozilla-Firefox[1].png

Voilà! Different form variations per LP.

2247
8
8 Comments
JayW
Level 2

Deleted the previous comment. Figured out my issue. 
Amazing solution as always @SanfordWhiteman 

JayW
Level 2

Hi @SanfordWhiteman 

How to update the script to take the Option Value different to Display Value.

 

Current set up

Dynamic Value 1 | Dynamic Value 1

Dynamic Value 2 | Dynamic Value 2

Dynamic Value 3 | Dynamic Value 3

I want to create

Dynamic Value 1 | User Selected Option1

Dynamic Value 2 | User Selected Option2

Dynamic Value 3 | User Selected Option3

This way I can use the predefined "User Selected Option..." in my flow logic.

 

SanfordWhiteman
Level 10 - Community Moderator

I think you figured this out already but you can use Friendly Name|Server Value format in the mktoVariable value.

 

If there’s no pipe | character then the value is used as both the Friendly Name and the Server Value.

 

JayW
Level 2

Hi @SanfordWhiteman ,

Back again with a question to take this step forward.

 

What if I have a select field that I want to keep hidden and only show if another field changes.

i.e. When "Country" field = United Kingdom then show the job titles drop down. Else keep hidden.

But in this visibility scenario "Job Titles" are to be picked dynamically from the datalist. 

 

Based on the forms api:

.onFormRender(callback) Adds a callback that will be called every time any form on the page renders. Forms are rendered when initially created, then again every time that visibility rules alter the structure of the form.
.whenRendered(callback)

Like onFormRender, this adds a callback that will be called every time a form is rendered. Additionally, this will also call the callback immediately for all forms that have already been rendered.

 

Does that means if I replace .whenRendered in the script with .onFormRender it should take the datalist values and rework the select list every time it's visibility rules change?

I tried but didn't work.

JayW_0-1670853816495.png

JayW_1-1670853837516.png

 

SanfordWhiteman
Level 10 - Community Moderator

onFormRender won't matter (do not make that change). It's already set to run on every render. But it only checks the dropdown list once for performance reasons.

 

I added the option to ignore if the dropdown was already populated. Set options.renderOnlyOnce to false to have it render every time.

JayW
Level 2

Hi @SanfordWhiteman ,

Here's what I added to the page but I don't still see my options in the drop down list when we change the visibility. Any idea why?

 

 

 

<datalist id="mktoFormGiftSelectOptions">
  <option label="giftOption">Your Gift Option|</option>
  <option label="giftOption">${GOption1}</option>
  <option label="giftOption">${GOption2}</option>
  <option label="giftOption">${GOption3}</option>
</datalist>



<!-- Marketo Form Dynamic Select Options -->

<script>
{
  const options = {
    renderOnlyOnce : false // set to false to pair with VRs
  }


  MktoForms2.whenRendered(function(mktoForm){
    const arrayify = getSelection.call.bind([].slice);

    let formEl = mktoForm.getFormElem()[0];

    let allDynamicSelectOptions = document.querySelector("#mktoFormGiftSelectOptions");

    let optionsByField = arrayify(allDynamicSelectOptions.options)
    .reduce(function(acc,option){
      if( !(option.label in acc )){
        acc[option.label] = [];
      }
      if( option.textContent ) {
        acc[option.label].push(option.textContent);
      }
      return acc;
    }, {});

  Object.keys(optionsByField)
  .forEach(function(fieldName){
     let mktoVariableOptions = optionsByField[fieldName];
     let selectEl = formEl.querySelector("select[name='" + fieldName + "']");

     if(selectEl.getAttribute("data-options-managed") != "true" && 
     mktoVariableOptions.length > 0){
       selectEl.setAttribute("data-options-managed","true");

     arrayify(selectEl.options)
.forEach(function(originalOption){
selectEl.removeChild(originalOption);
});

mktoVariableOptions
.map(function(mktoVariableOption){
let newOption = document.createElement("option");
let optionDescriptor = mktoVariableOption.split("|");

newOption.textContent = optionDescriptor[0];
newOption.value = optionDescriptor[optionDescriptor.length == 1 ? 0 : 1];

return newOption;
})
.forEach(function(newOption){
selectEl.appendChild(newOption);
});
}
})
});
}
</script>

 

 

SanfordWhiteman
Level 10 - Community Moderator

Can you highlight your code with the syntax highlighter, please?

JayW
Level 2

Apologies @SanfordWhiteman - I updated the code in the comment. It was a code you've shared in the past, to dynamically populate the dropdown with meta values in a guided marketo LP template. I'm trying to taking one step forward by making that drop down conditionally visible. 
When the condition isn't there, the form loads as expected with the datalist values. 
However, when the form is only visible upon the country field choice, it shows no options.