Creating a Breakout Custom Attribute Editor in Page Designer for Salesforce Commerce Cloud


Salesforce commerce cloud
      
          {
  "id": "customCarEditor",
    "name": "Custom Car Editor",
    "type": "custom",
    "required": false,
    "editor_definition": {
      "type": "com.sfcc.customCarEditorTrigger",
      "configuration": {
        "options": {
          "config": [
            "Yugo",
            "Mercedes",
            "BMW",
            "Seat"
          ]
        }
      }
  }
}
        
      
          {
    "name": "Car Custom Editor",
    "description": "Custom editor with car picker breakout functionality!",
    "resources": {
        "scripts": [
            "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js",
            "/experience/editors/com/sfcc/customCarEditorTriggerScript.js"
        ],
        "styles": [     "https://cdnjs.cloudflare.com/ajax/libs/design-system/2.8.3/styles/salesforce-lightning-design-system.min.css",
            "/experience/editors/com/sfcc/customCarEditorTriggerCSS.css"
        ]
    }
}
        
      
          'use strict';

var HashMap = require('dw/util/HashMap');
var Resource = require('dw/web/Resource');
var PageMgr = require('dw/experience/PageMgr');

module.exports.init = function (editor) {
    // Default values properties
    var defaults = {
        buttonBreakout: 'Select',
        titleBreakout: 'Cars',
        placeholder: 'Select your custom car',
        description: 'Description of Custom Car Editor',
        group1: 'car group1 configuration'
    };

    // Add some localizations
    var localization = Object.keys(defaults).reduce(function (acc, key) {
        acc.put(key, Resource.msg(key, 'experience.editors.com.sfcc.customCarEditorTrigger', defaults[key]));
        return acc;
    }, new HashMap());
    editor.configuration.put('localization', localization);

    // Pass through property `options.config` from the `attribute_definition` to be used in a breakout editor
    var options = new HashMap();
    options.put('config', editor.configuration.options.config);

    // Create a configuration for a custom editor to be displayed in a modal breakout dialog (breakout editor)
    var breakoutEditorConfig = new HashMap();
    breakoutEditorConfig.put('localization', localization);
    breakoutEditorConfig.put('options', options);

    // Add a dependency to the configured breakout editor
    var breakoutEditor = PageMgr.getCustomEditor('com.sfcc.customCarEditorBreakout', breakoutEditorConfig);
    editor.dependencies.put('customCarEditorBreakoutScript', breakoutEditor);
};
        
      
          {
    "name": "Custom Car Editor Breakout",
    "description": "An car editor with breakout functionality",
    "resources": {
        "scripts": [
            "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js",
            "/experience/editors/com/sfcc/customCarEditorBreakoutScript.js"
        ],
        "styles":[
"https://cdnjs.cloudflare.com/ajax/libs/design-system/2.9.4/styles/salesforce-lightning-design-system.min.css",
            "/experience/editors/com/sfcc/customCarEditorBreakoutCSS.css"
        ]
    }
}
        
      
          // This is optional file where config can also be initialized
("use strict");
var URLUtils = require("dw/web/URLUtils");

module.exports.init = function (editor) {
  var optionsConfig = editor.configuration.options.config;

  // Car values can be set here
  var optionsInit = [];

  // Init editor configuration
  editor.configuration.options.put("init", optionsInit);
  
  // Provide `baseUrl` to the static assets/content
  editor.configuration.put(
    "baseUrl",
    URLUtils.staticURL("/experience/editors/com/sfcc/").https().toString()
  );
};
        
      
          // Code in the client-side JavaScript file for the trigger editor
(() => {
  let resultEl;
  subscribe(
    "sfcc:ready",
    async ({
      value,
      config,
      isDisabled,
      isRequired,
      dataLocale,
      displayLocale,
    }) => {
      ({ localization = {} } = config);
      const template = document.createElement("template");
      template.innerHTML = `
      <div>
        <p>Pick a custom car</p>
        <button id='breakout-trigger'>Click</button>
        <p id='result'></p>
      </div>`;
      const clone = document.importNode(template.content, true);
      document.body.appendChild(clone);
      resultEl = $("#result");
      var openButtonEl = $("#breakout-trigger");
      openButtonEl.on("click", handleBreakoutOpen);
      resultEl.text(obtainDisplayValue(value));
    }
  );
  
  function obtainDisplayValue(value) {
    return (
      "Car picked: " +
      (typeof value === "object" &&
      value != null &&
      typeof value.value === "string"
        ? value.value
        : "")
    );
  }

  function handleBreakoutOpen() {
    const { titleBreakout } = localization;
    emit(
      {
        type: "sfcc:breakout",
        payload: {
          id: "customCarEditorBreakoutScript",
          title: titleBreakout,
        },
      },
      handleBreakoutClose
    );
  }

  function handleBreakoutClose({ type, value }) {
    // Now the "value" can be passed back to Page Designer
    if (type === "sfcc:breakoutApply") {
      handleBreakoutApply(value);
    } else {
      handleBreakoutCancel();
    }
  }

  function handleBreakoutCancel() {
    // left empty in case you want to do more customization on this event
  }

  function handleBreakoutApply(value) {
    // Emit value update to Page Designer host application
    resultEl.text(obtainDisplayValue(value));
    emit({
      type: "sfcc:value",
      payload: value,
    });
  }
})();
        
      
          // Code in the client-side JavaScript file for the breakout editor
(() => {
  let options;
  let carsEl;

  subscribe('sfcc:ready', async ({ value, config, isDisabled, isRequired, dataLocale, displayLocale }) => {
    // Once the breakout editor is ready, the custom code is able to select or
    // Create a value. Any meaningful change to a value/selection needs to be
    // reflected back into the host container via a `sfcc:value` event.
    const template = document.createElement('template');
    template.innerHTML = `<div class="cars"></div>`;
    const clone = document.importNode(template.content, true);
    document.body.appendChild(clone);
    ({ options = {} } = config);
    carsEl = document.querySelector('.cars');
    options.config.forEach(appendItems());
  });

  function appendItems() {
    return option => {
      const itemTemplate = createNewCarTemplate(option);
      const itemEl = document.importNode(itemTemplate.content, true);
      const linkEl = itemEl.querySelector('p');
      linkEl.addEventListener('click', handleSelect);
      carsEl.appendChild(itemEl);
    };
  }

  function createNewCarTemplate(carName) {
    const template = document.createElement('template');
    template.innerHTML = `<p>${carName}</p>`.trim();
    return template;
  }
  
  function handleSelect({ target }) {
    // The value changed and the breakout editor's host is informed about the
    // value update via a `sfcc:value` event.
    const selectedCar = target.innerText;
    emit({
      type: 'sfcc:value',
      payload: selectedCar ? { value: selectedCar } : null
    });
  }
})();
        
blog author

Srđan Đukić

Salesforce Commerce Cloud Developer