Client-Side JavaScript and CSS for a Custom Attribute Editor

Put the client-side JavaScript files and CSS resources required to run the custom attribute editor in the static/default directory of the custom cartridge at a location that corresponds to the location of the meta definition file and script file for the editor.

For example, let's say the meta definition file and script file for the custom attribute editor are in the following location:

my_bm_cartridge/cartridge/experience/editors/com/sfcc/magical.json

my_bm_cartridge/cartridge/experience/editors/com/sfcc/magical.js

Put the JavaScript and CSS files here:

my_bm_cartridge/cartridge/static/default/experience/editors/com/sfcc

Important: The cartridge that contains the custom attribute editor meta definition file, script file, and client-side code must be added to the cartridge path for the Business Manager site.

This example of a client-side JavaScript file uses a <select> element to display data and interact with the user. The example displays two sets of unicorn types inside of <optgroup> elements. The two different types correspond to the two different sources from which the unicorns were passed to the editor.

In this example, the custom attribute editor subscribes to the event sfcc:ready. As soon as this event is emitted by the host, the editor initializes its DOM (Document Object Model) using configuration and localization information from the init function of the server-side script file and assigning the unicorns to their appropriate <optgroup>. When the user changes the value of the <select> element, the editor sends the sfcc:value event to inform the host.

magical_editor.js
(() => {
  subscribe('sfcc:ready', async ({ value, config, isDisabled, isRequired, dataLocale, displayLocale }) => {
    console.log('sfcc:ready', dataLocale, displayLocale, value, config);
 
    const selectedValue = typeof value === 'object' && value !== null && typeof value.value === 'string' ? value.value : null;
    const { options = {}, localization = {} } = config;
    let isValid = true;
 
    // Append basic DOM
    const template = obtainTemplate(localization);
    const clone = document.importNode(template.content, true);
    document.body.appendChild(clone);
 
    // Set props
    const selectEl = document.querySelector('select');
    selectEl.required = isRequired;
    selectEl.disabled = isDisabled;
 
    // Set <options> from JSON config
    const optgroupEls = selectEl.querySelectorAll('optgroup');
    setOptions(options.config || [], optgroupEls[0], selectedValue);
 
    // Set <options> from init()
    setOptions(options.init || [], optgroupEls[1], selectedValue);
 
    // Apply change listener
    selectEl.addEventListener('change', event => {
      const val = event.target.value;
      emit({
        type: 'sfcc:value',
        payload: val ? { value: val } : null
      });
    });
  });

  function obtainTemplate({ placeholder, description, group1, group2 }) {
    const template = document.createElement('template');
    template.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center;">
  <div class="slds-select_container" title="${description}">
    <select class="slds-select">
      <option value="">-- ${placeholder} --</option>
      <optgroup label="${group1}"></optgroup>
      <optgroup label="${group2}"></optgroup>
    </select>
  </div>
</div>`;
    return template;
  }

  function setOptions(options, optgroupEl, selectedValue) {
    options.forEach(option => {
      const optionEl = document.createElement('option');
      optionEl.text = option;
      optionEl.value = option;
      optionEl.selected = option === selectedValue;
 
      optgroupEl.appendChild(optionEl);
    });
  }
})();