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);
    }
});