Use a Prebuilt Editor in a Custom Attribute Editor

Create custom attribute editors faster and more efficiently with prebuilt attribute editors. You can use the prebuilt editors as building blocks when you create custom attribute editors.

This table lists the prebuilt editors that are available. We provide multiple IDs for each prebuilt editor so that you can use the style you prefer. All the prebuilt editors open in a separate breakout modal window.

Table 1. Prebuilt Editors
Prebuilt Editor Purpose ID Return Value Type Return Value Example
Category Picker Allows merchant to select a category sfcc:categoryPicker or sfcc:category-picker Category ID <string>
{
type: "sfcc:categoryPicker",
value: "1234567890"
}
Link Builder Builds a link to the element selected by the merchant sfcc:linkBuilder or sfcc:link-builder Absolute URL or URL portions <string | string[]>

Depending on the link type the user created using the Link Builder, the return value is either a single string or an array of strings.

A single string indicates that the value is an absolute URL that can be used as-is. An array of strings indicates that the URL must be assembled using the URLUtils.url method.

{
type: "sfcc:linkBuilder",
value: "https://salesforce.com"
}
or
{
type: "sfcc:linkBuilder",
value: ["Page-Show", "cid", "1234567890"]
}
Page Picker Allows merchant to select a page sfcc:pagePicker or sfcc:page-picker Page ID <string>
{
type: "sfcc:pagePicker",
value: "1234567890"
}
Product Picker Allows merchant to select a product sfcc:productPicker or sfcc:product-picker Product ID <string>
{
type: "sfcc:productPicker",
value: "1234567890"
}
The following examples show a custom attribute editor that uses the prebuilt editor for Category Picker. In the meta definition file for the component type, designate the attribute for the Category Picker as type custom.

            selectionTile.json
{
	"name": "Category Selection Tile",
	"description": "A tile that lets the mercant select a category to display.",
	"group": "content",
	"attribute_definition_groups": [{
		"id": "category",
		"name": "Category",
		"description": "You can select the category.",
		"attribute_definitions": [{
			"id": "category",
			"name": "Category",
			"type": "custom",
			"required": true,
			"editor_definition": {
				"type": "com.sfcc.categoryPicker"
			}
		}],
On the server side, create a meta definition JSON file and a .js file for the custom attribute editor just as you would for any custom attribute editor.

            categoryPicker.json

{
  "name": "Category Editor",
  "description": "A prebuilt editor that allows you to select a category.",
  "resources": {
    "scripts": [
      "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js",
      "/experience/editors/com/sfcc/categoryPicker_trigger.js"
    ],
    "styles": [
      "https://cdnjs.cloudflare.com/ajax/libs/design-system/2.8.3/styles/salesforce-lightning-design-system.min.css",
      "/experience/editors/com/sfcc/category.css"
    ]
  }
}

            categoryPicker.js

'use strict';
 
var HashMap = require('dw/util/HashMap');
var Resource = require('dw/web/Resource');
 
module.exports.init = function (editor) {
    // Default values for L10N properties
    var l10nDefaults = {
        buttonBreakout: 'Select',
        titleBreakout: 'Category',
        placeholderInput: 'Select the category',
    };
 
    // Add some localizations
    var localization = Object.keys(l10nDefaults).reduce(function (acc, key) {
        acc.put(key, Resource.msg(key, 'experience.editors.sfcc.categoryPicker', l10nDefaults[key]));
        return acc;
    }, new HashMap());
 
    if (!editor.configuration) {
      editor.configuration = new HashMap();
    }
    editor.configuration.put('localization', localization);
};

On the client side, create a JavaScript file that runs the prebuilt editor, as in this example. Set the payload ID in the sfcc:breakout event to the ID of the prebuilt editor, in this case sfcc:categoryPicker.


         categoryPicker_trigger.js

(() => {
  let localization;
  let inputEl;
  let buttonEl;
​
  /**
   * This listener subscribes to the creation/initialization event of the sandbox component. This event gets fired when a Page Designer user
   * first opens a component of a certain type in the component settings panel (and that component type contains the custom editor).
   * **Note:** This event won't fire when the user switches between components of the same type in the canvas. In that case the existing
   * attribute editor components will be reused which results in a value update event (`sfcc:value`), therefore no new initialization
   * happens.
   */
  subscribe('sfcc:ready', ({ value, config, isDisabled, isRequired, dataLocale, displayLocale }) => {
    console.log('category-trigger::sfcc:ready', { dataLocale, displayLocale, isDisabled, isRequired, value, config });
​
    // Extract data from `config`
    ({ localization = {} } = config);
​
    // Initialize the DOM
    const template = obtainTemplate();
    const clone = document.importNode(template.content, true);
    document.body.appendChild(clone);
​
    // Obtain DOM elements and apply event handlers
    inputEl = document.querySelector('input');
    buttonEl = document.querySelector('button');
    buttonEl.addEventListener('click', handleBreakoutOpen);
​
    // Update <input> value
    inputEl.value = obtainDisplayValue(value);
  });
​
  /**
   * This listener subscribes to external value updates. As stated above, this event also gets fired when the user switches between
   * different components of the same type in the canvas. As in that case already existing custom editor instances get reused, it
   * technically means that switching between components of the same type is just an external value update.
   */
  subscribe('sfcc:value', value => {
    console.log('category-trigger::sfcc:value', { value });
​
     // Update <input> value
    inputEl.value = obtainDisplayValue(value);
  });
​
  function obtainTemplate() {
    const { placeholderInput, buttonBreakout } = localization;
    const template = document.createElement('template');
    template.innerHTML = `
<div class="slds-grid slds-grid_vertical-align-start">
  <div class="slds-col slds-grow">
    <input type="text" disabled class="slds-input" placeholder="${placeholderInput}">
  </div>
  <div class="slds-col slds-grow-none slds-m-left_xx-small">
    <button type="button" class="slds-button slds-button_neutral">${buttonBreakout}</button>
  </div>
</div>`;
    return template;
  }
​
  function obtainDisplayValue(value) {
    return typeof value === 'object' && value != null && typeof value.value === 'string' ? value.value : null;
  }
​
  function handleBreakoutOpen() {
    const { titleBreakout } = localization;
​
    emit({
      type: 'sfcc:breakout',
      payload: {
        id: 'sfcc:categoryPicker',
        title: titleBreakout
      }
    }, handleBreakoutClose);
  }
​
  function handleBreakoutClose({type, value}) {
    if (type === 'sfcc:breakoutApply') {
      handleBreakoutApply(value);
    } else {
      handleBreakoutCancel();
    }
  }
​
  function handleBreakoutCancel() {
    // Grab focus
    buttonEl && buttonEl.focus();
  }
​
  function handleBreakoutApply(value) {
    // Update <input> value
    inputEl.value = obtainDisplayValue(value);
​
    // Emit value update to the PD host application
    emit({
      type: 'sfcc:value',
      payload: value
    });
​
    // Grab focus
    buttonEl && buttonEl.focus();
  }
})();