Enhancing form Select elements using the Chosen JS plugin
A user recently asked about plugging Chosen, a popular HTML <select>
replacement, into Marketo forms. There’s a 3rd-party post from several years ago on the topic, but their code needed a little correction, performance optimization, and configurability. Plus an official Products Blog post felt worthwhile.
Chosen was developed by devs @ Harvest, the time-tracking app. (That doesn’t necessarily make it better than others, but knowing the project is in heavy use by its maintainers is a good thing!)
The main goal of Chosen is to make long dropdowns less unwieldy.
Here’s a native Marketo form with a series of Visibility Rules — I made sure to cover VRs, not just a basic config — where Territory and Country are perfectly usable, but State is characteristically out of control:
With Chosen, all dropdowns become a little slicker (check out how the Territory and Country placeholder text is displayed). Even better, long dropdowns can be replaced by a shorter scrollable list plus a search box:
Nice, eh?
Loading the Chosen CSS & JS
Chosen is a jQuery plugin. If you know me, you know I hate jQuery — from back in the day and especially now, since you don’t need any DOM framework in modern browsers. But it’s a when-in-Rome thing, and the Forms 2.0 library includes its own copy of jQuery, so I’ll deal with it.
Plugging Chosen into a Marketo form with default options is easy. First, load Chosen’s CSS and JS, being sure to hook into Forms 2.0’s built-in jQuery. Put this code anywhere after the form embed:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.min.css">
<script>
if(window.jQuery){
window.jQueryRestore = window.jQuery;
}
window.jQuery = MktoForms2.$;
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.7/chosen.jquery.min.js"></script>
<script>
if(window.jQueryRestore) {
window.jQuery = window.jQueryRestore;
} else {
delete window.jQuery;
}
delete window.jQueryRestore;
</script>
Using Marketo’s built-in jQuery as shown here is very highly recommended. It improves performance, since no redundant framework needs to be loaded over the network. And it avoids the strangeness of having two jQuery libs with different fn
extensions both accessing the same elements.
Attaching Chosen to your form (simple method)
Next, you just need to listen for whenRendered
— not whenReady
, or else you won’t be covering Visibility Rules — and attach to all <select>
elements[1] inside the form:
{
const chosenSettings = {
disable_search_threshold: 5,
placeholder_text_single: "Select..."
};
MktoForms2.whenRendered(function(mktoForm){
mktoForm.getFormElem().find("select").chosen(chosenSettings);
});
}
Notice the Chosen initialization option placeholder_text_single
is passed to the chosen()
method. That’s how you achieve the streamlined look in the second screenshot above, where “Select...” is only displayed once. (In the screenshot of the standard HTML <select>
, the placeholder shows twice when the dropdown is open.)
To let Chosen control the placeholder, you also need to change the field setup slightly in Form Editor. Set the Display Value of the placeholder to an empty string:
The other Chosen option I’m using here is disable_search_threshold
. This is how Chosen defines a dropdown as “long” — if the number of options is greater than this number, the search box is shown.
Attaching Chosen (advanced method)
The code above is nice and short, but it also assumes you want the same Chosen options applied to every Select type field. In practice, you might want different placeholder text and/or search box feedback per field.
Here’s longer code that supports default options and per-field options, where per-field options “win” if present:
{
const systemDefaults = Symbol("system");
const userDefaults = Symbol("user");
const chosenSettings = new Map();
chosenSettings.set( systemDefaults, { no_results_text: "No match found." } );
chosenSettings.set( userDefaults, { disable_search_threshold: 5, placeholder_text_single: "Select..." } );
chosenSettings.set( "State", { no_results_text: "Oops, no State matches!" } );
MktoForms2.whenRendered(function(mktoForm){
mktoForm.getFormElem().find("select").each(function(idx,el) {
let fieldName = el.name;
let currentChosenSettings = Object.assign(
{},
chosenSettings.get(systemDefaults),
chosenSettings.get(userDefaults),
chosenSettings.get(fieldName)
);
MktoForms2.$(el).chosen(currentChosenSettings);
});
});
}
Using Object.assign
to merge system defaults, user defaults, and more granular overrides is a pattern well worth learning.
Notes
[1] One thing I have to reluctantly hand to jQuery plugins is they automatically add themselves only once to a given element, so you don’t have to keep track of whether the <select>
is newly injected or was already there. Still easy to implement without jQuery though!