Example Contact Event

config.json

{
  "workflowApiVersion": "1.1",
  "metaData": {
    "icon": "images/icon.png",
    "transactionKeys": {
      "0": {
        "from": "Event Property",
        "to": "DE_Name.DE_Property"
      }
    }
  },
  "type": "Event",
  "lang": {
    "en-US": {
      "name": "Event (Workflow API v1.1)",
      "description": "An example event using workflow API v1.1 format.",
      "selectCountry": "Select Country",
      "enterFirstName": "Enter First Name",
      "enterLastName": "Enter Last Name",
      "enterFavoriteFood": "Enter Favorite Food",
      "countryCodeLabel": "Country Code",
      "firstNameLabel": "First Name",
      "lastNameLabel": "Last Name",
      "favoriteFoodLabel": "Favorite Food",
      "selectTransactionKeyMapping": "Transaction Key"
    }
  },
  "configurationArguments": {},
  "filterExpressionEnabled": false,
  "wizardSteps": [
    { "key": "selectCountry", "label": "selectCountry" },
    { "key": "enterFirstName", "label": "enterFirstName" },
    { "key": "enterLastName", "label": "enterLastName" },
    {
      "key": "enterFavoriteFood",
      "label": "enterFavoriteFood",
      "active": false
    },
    {
      "key": "selectTransactionKeyMapping",
      "label": "selectTransactionKeyMapping"
    }
  ],
  "userInterfaces": {
    "configModal": {
      "url": "index.html"
    },
    "summary": [
      {
        "valuePath": "arguments.countryCode",
        "label": "countryCodeLabel"
      },
      {
        "valuePath": "arguments.firstName",
        "label": "firstNameLabel"
      },
      {
        "valuePath": "arguments.lastName",
        "label": "lastNameLabel"
      },
      {
        "valuePath": "arguments.favoriteFood",
        "label": "favoriteFoodLabel"
      },
      {
        "valuePath": "metaData.transactionKeys",
        "label": "Transaction Key"
      }
    ]
  }
}

index.html

Every custom event must have an index.html in the root of its endpoint.

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Custom Event Test</title>

    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/require.js"></script>
    <script type="text/javascript">
      (function () {
        var config = {
          baseUrl: "",
        };

        var dependencies = ["customEvent"];

        require(config, dependencies);
      })();
    </script>

    <!--Styles-->
    <style type="text/css">
      body {
        padding: 15px;
        margin: 0;
      }
      h1 {
        font-size: 16px;
      }
      .step {
        display: none;
      }
      #step1 {
        display: block;
      }
    </style>
  </head>
  <body>
    <div id="step1" class="step">
      <h1>Step 1: Country Code</h1>
      <select id="select-country-code">
        <option value="US">United States</option>
        <option value="GB">United Kingdom</option>
        <option value="BR">Brazil</option>
        <option value="DE">Germany</option>
      </select>
      <br />
      <button id="toggleLastStep">Toggle Favorite Food Step</button>
    </div>
    <div id="step2" class="step">
      <h1>Step 2: First Name</h1>
      <input id="select-first-name" />
    </div>
    <div id="step3" class="step">
      <h1>Step 3: Last Name</h1>
      <input id="select-last-name" />
    </div>
    <div id="step4" class="step">
      <h1>Step 4: Favorite Food</h1>
      <input id="select-favorite-food" />
    </div>
  </body>
</html>

customEvent.js

Postmonger is required for communication between Journey Builder and the custom event.

define(["js/postmonger"], function (Postmonger) {
  "use strict";

  var connection = new Postmonger.Session();
  var payload = {};
  var lastStepEnabled = false;
  var steps = [
    // initialize to the same value as what's set in config.json for consistency
    { key: "selectCountry", label: "selectCountry" },
    { key: "enterFirstName", label: "enterFirstName" },
    { key: "enterLastName", label: "enterLastName" },
    { key: "enterFavoriteFood", label: "enterFavoriteFood", active: false },
  ];
  var currentStep = steps[0].key;

  $(window).ready(onRender);

  connection.on("initEvent", initialize);
  connection.on("requestedTokens", onGetTokens);
  connection.on("requestedEndpoints", onGetEndpoints);

  connection.on("clickedNext", onClickedNext);
  connection.on("clickedBack", onClickedBack);
  connection.on("gotoStep", onGotoStep);

  function initialize(data) {
    var countryCode;
    var firstName;
    var lastName;

    if (data) {
      payload = data;
    }

    if (payload["arguments"]) {
      countryCode = payload["arguments"].countryCode;
      firstName = payload["arguments"].firstName;
      lastName = payload["arguments"].lastName;
    }

    $("#select-country-code").val(countryCode);
    $("#select-first-name").val(firstName);
    $("#select-last-name").val(lastName);
  }

  function onGetTokens(tokens) {
    // Response: tokens = { token: <legacy token>, fuel2token: <fuel api token> }
    // console.log(tokens);
  }

  function onGetEndpoints(endpoints) {
    // Response: endpoints = { restHost: <url> } i.e. "rest.s1.qa1.exacttarget.com"
    // console.log(endpoints);
  }

  function onClickedNext() {
    if (
      (currentStep.key === "enterLastName" && steps[3].active === false) ||
      currentStep.key === "enterFavoriteFood"
    ) {
      save();
    } else {
      connection.trigger("nextStep");
    }
  }

  function onClickedBack() {
    connection.trigger("prevStep");
  }

  function onGotoStep(step) {
    showStep(step);
    connection.trigger("ready");
  }

  function onRender() {
    connection.trigger("ready"); // JB will respond the first time 'ready' is called with 'initEvent'

    connection.trigger("requestTokens");
    connection.trigger("requestEndpoints");

    $("#toggleLastStep").click(function () {
      lastStepEnabled = !lastStepEnabled; // toggle status
      steps[3].active = !steps[3].active; // toggle active

      connection.trigger("updateSteps", steps);
    });
  }

  function showStep(step, stepIndex) {
    if (stepIndex && !step) {
      step = steps[stepIndex - 1];
    }

    currentStep = step;

    $(".step").hide();

    switch (currentStep.key) {
      case "selectCountry":
        $("#step1").show();
        break;
      case "enterFirstName":
        $("#step2").show();
        $("#step2 input").focus();
        break;
      case "enterLastName":
        $("#step3").show();
        $("#step3 input").focus();
        break;
      case "enterFavoriteFood":
        $("#step4").show();
        $("#step4 input").focus();
        break;
    }
  }

  function save() {
    var countryCode = $("#select-country-code")
      .find("option:selected")
      .attr("value");
    var firstName = $("#select-first-name").val();
    var lastName = $("#select-last-name").val();
    var favoriteFood = $("#select-favorite-food").val();

    payload["arguments"] = payload["arguments"] || {};
    payload["arguments"].countryCode = countryCode;
    payload["arguments"].firstName = firstName;
    payload["arguments"].lastName = lastName;

    // Example criteria - if 'filterExpressionEnabled' is set to true in config.json, Journey Builder will
    // populate this step with the 'criteria' XML passed here
    // payload['arguments'].criteria = "<FilterDefinition Source='SubscriberAttribute'><ConditionSet Operator='AND' ConditionSetName='Grouping'><Condition ID='13D65BB5-1F98-E411-9D68-00237D5401CE' isParam='false' Operator='Equal' operatorEditable='0' valueEditable='1' annotation=''><Value><![CDATA[" + countryCode + "]]></Value></Condition><Condition ID='0CD65BB5-1F98-E411-9D68-00237D5401CE' isParam='false' Operator='Equal' operatorEditable='0' valueEditable='1' annotation=''><Value><![CDATA[" + firstName + "]]></Value></Condition><Condition ID='12D65BB5-1F98-E411-9D68-00237D5401CE' isParam='false' Operator='Equal' operatorEditable='0' valueEditable='1' annotation=''><Value><![CDATA[" + lastName + "]]></Value></Condition></ConditionSet></FilterDefinition>";

    if (favoriteFood && steps[3].active) {
      payload["arguments"].favoriteFood = favoriteFood;
    }

    payload["metaData"] = payload["metaData"] || {};

    payload["configurationArguments"] = payload["configurationArguments"] || {};

    payload.dataExtensionId = "<data extension ID>";

    connection.trigger("updateEvent", payload);
  }
});