<div class="container">
  <h1>PactSafe Example with a Dynamic Contract</h1>
  <p>Fill out the first name and last name fields below with any data and then click the "Show Contract" button!</p>
  <form id="myPageForm">
    <div class="form-group">
      <label for="firstNameInput1">First Name</label>
      <input type="text" class="form-control" id="firstNameInput1" />
    </div>
    <div class="form-group">
      <label for="lastNameInput1">Last Name</label>
      <input type="text" class="form-control" id="lastNameInput1" />
    </div>

    <!-- We use button to trigger that we're ready to show the Contract. We strongly recommend that you are retrieving the Contract HTML at the point when you are absolutely ready to show and not attempting to retrieve frequently (i.e. keystrokes). Each time you retrieve the contract HTML, it makes an API call to PactSafe, which we'll want to minimize the number of API calls made for optimal performance and user experience. -->
    <button id="showContractButton" type="button" class="btn btn-info" disabled>Show Contract</button>
    <br><br>

    <!-- Note the div container here! -->
    <div id="pactsafeContainer"></div>
    <br><br>
    <!-- By default, the submit button is disabled for validation with JavaScript -->
    <button id="formSubmitButton" type="submit" class="btn btn-primary" disabled>Submit</button>
  </form>
</div>
// PactSafe Snippet
(function (w, d, s, c, f, n, t, g, a, b, l) {
  w["PactSafeObject"] = n;
  (w[n] =
    w[n] ||
    function () {
      (w[n].q = w[n].q || []).push(arguments);
    }),
    (w[n].on = function () {
      (w[n].e = w[n].e || []).push(arguments);
    }),
    (w[n].once = function () {
      (w[n].eo = w[n].eo || []).push(arguments);
    }),
    (w[n].off = function () {
      (w[n].o = w[n].o || []).push(arguments);
    }),
    (w[n].t = 1 * new Date()),
    (w[n].l = 0);
  a = d.createElement(s);
  b = d.getElementsByTagName(s)[0];
  a.async = 1;
  a.src = c;
  a.onload = a.onreadystatechange = function () {
    w[n].l = 1;
  };
  a.onerror = a.onabort = function () {
    w[n].l = 0;
  };
  b.parentNode.insertBefore(a, b);
  setTimeout(function () {
    if (!w[n].l && !w[n].loaded) {
      w[n].error = 1;
      a = d.createElement(s);
      a.async = 1;
      a.src = f;
      a.onload = a.onreadystatechange = function () {
        w[n].l = 1;
      };
      a.onerror = a.onabort = function () {
        w[n].l = 0;
      };
      b.parentNode.insertBefore(a, b);
      l = function (u, e) {
        try {
          e = d.createElement("img");
          e.src =
            "https://d3r8bdci515tjv.cloudfront.net/error.gif?t=" +
            w[n].t +
            "&u=" +
            encodeURIComponent(u);
          d.getElementsByTagName("body")[0].appendChild(e);
        } catch (x) {}
      };
      l(c);
      setTimeout(function () {
        if (!w[n].l && !w[n].loaded) {
          w[n].error = 1;
          if (g && "function" == typeof g) {
            g.call(this);
          }
          l(f);
        }
      }, t);
    }
  }, t);
})(
  window,
  document,
  "script",
  "https://vault.pactsafe.io/ps.min.js",
  "https://d3l1mqnl5xpsuc.cloudfront.net/ps.min.js",
  "_ps",
  4000
);

// We'll need a couple of things to get started from PactSafe.
var siteAccessId = "1e8ddd9d-f32c-4dc7-9c13-62095e6d4317"; // A PactSafe Site Access ID
var groupKey = "full-advanced-dynamic"; // A PactSafe Group Key.

// Creates a Site object with the a PactSafe Site Access ID.
_ps("create", siteAccessId, {
  dynamic: true // Please ensure this is true when using dynamic contracts.
});

// Since we're testing, we can enable debugging
// which will log events to console. You'll want to
// set this to false in a production environment.
_ps.debug = true;

// Options set on the PactSafe Group.
var groupOptions = {
  container_selector: "pactsafeContainer", // ID of where we want the clickwrap to load in the page.
  test_mode: true, // Allows you to clear test data from the PactSafe web app.
  display_all: false, // Prevents the group from showing the contract immediately.
  auto_run: false
};

// Load a Clickwrap group into the page.
_ps("load", groupKey, groupOptions);

// Call when the group is ready and loaded.
_ps.on("initialized", function () {
  // Setting a fake unique ID as the signer id on the Site Object.
  var fakeUniqueId = Math.random().toString(36).substring(7);
  _ps.site.set("signer_id", fakeUniqueId);
});

// If there's an error from the PactSafe snippet,
// you may want to prevent submission if needed.
_ps.on("error", function (message, event_type, context) {
  // Handle any errors.
  console.log(message);
});

/**
 * _ps.on('valid') gets triggered when all contracts within a group
 * have been accepted.
 *
 * Since the user has agreed, we can enable the submit button
 * if basic form validation also passes.
 *
 * Note: if more than one PactSafe group exists on the page,
 * you'll want to add additional validation to ensure both groups
 * are valid if required.
 */
_ps.on("valid", function (params, context) {
  console.log("Valid event fired!");
  if (simpleValidationFormValues()) {
    var submitButton = pageSubmitButton();
    if (submitButton) submitButton.disabled = false; // Only enable the submit button if found.
  }
});

// Triggered when a user unchecks a checkbox in a Group.
_ps.on("invalid", function (params, context) {
  console.log("Invalid event fired!");
  // If a user has disagreed to a contract,
  // you may want to ensure the submit button is disabled.
  var submitButton = pageSubmitButton();
  if (!submitButton.disabled) submitButton.disabled = true;
});

// Check if values exist within the form fields.
function simpleValidationFormValues() {
  var firstNameField = document.getElementById("firstNameInput1");
  var lastNameField = document.getElementById("lastNameInput1");
  return firstNameField !== "" && lastNameField !== "";
}

// Return the form element in the page when called.
function pageFormElement() {
  return document.getElementById("myPageForm");
}

// Return the show contract button when called.
function showContractButton() {
  return document.getElementById("showContractButton");
}

// Return the submit button in the page when called.
function pageSubmitButton() {
  return document.getElementById("formSubmitButton");
}

// Return whether to block the submission or not.
function blockSubmission() {
  // Check to ensure we're able to get the Group successfully.
  if (_ps.getByKey(groupKey)) {
    // Return if we should block the submission using the .block() method.
    return _ps.getByKey(groupKey).block();
  } else {
    // We weren't able to get the group, so blocking form submission may be needed.
    return true;
  }
}

/**
 * When we're ready to retrieve the populated contract,
 * we can set the render data on the Group Object and
 * then retrieve the HTML.
 *
 * The Object keys are the token names inside the contract
 * and will be populated with the values we pass.
 */
function setRenderData() {
  var firstNameVal = document.getElementById("firstNameInput1").value;
  var lastNameVal = document.getElementById("lastNameInput1").value;

  // Render data that we want in the contract but doesn't require
  // a user to input.
  var staticRenderData = {
    first_token: "my first value",
    second_token: "my second value",
    shouldShow: true,
    additional_items: [
      {
        item: "My first item",
        itemValue: 0.99
      },
      {
        item: "My second item",
        itemValue: 1.99
      }
    ]
  };

  var renderData = {
    first_name: firstNameVal,
    last_name: lastNameVal,
    ...staticRenderData
  };

  _ps(groupKey + ":retrieveHTML", renderData);
}

// Here, we'll listen for when the contract HTML is received
// before we display the contract.
_ps.on("set:contract_html", function (html, group) {
  // Retrieved was called from retrieving HTML.
  group.set("display_all", true);
  group.displayRequired();
});

// Handler for when the Show Contract button is clicked.
function handleShowContractButton() {
  var fieldsExist = simpleValidationFormValues();
  if (!fieldsExist) alert("Please fill out the required form fields!");
  setRenderData();
}

/**
 * Here, we add some basic form validation and then manually
 * send acceptance using the JavaScript snippet. We utilize a
 * callback to wait and ensure acceptance has been sent to PactSafe
 * before allowing the form to submit.
 */
function handleFormSubmit(event) {
  // Prevent the form from automatically submitting without checking PactSafe acceptance first.
  event.preventDefault();

  // Simple validation to ensure form fields have values.
  var formFieldsHaveValues = simpleValidationFormValues();
  if (!formFieldsHaveValues) {
    alert("Please ensure all fields are filled out!");
    return false;
  }

  // Check to ensure that the acceptance is still valid on the
  // PactSafe group as a precaution.
  var shouldBlockSubmission = blockSubmission();
  if (shouldBlockSubmission) {
    // We can get the alert message if set on the group or define our own if it's not.
    var acceptanceAlertLanguage =
      _ps.getByKey(groupKey) && _ps.getByKey(groupKey).get("alert_message")
        ? _ps.getByKey(groupKey).get("alert_message")
        : "Please accept our Terms and Conditions.";

    alert(acceptanceAlertLanguage); // Alert the user that the Terms need to be accepted before continuing.
    return false; // Prevent submission
  }

  // We don't need to block the form submission at this point.
  // Manually send acceptance with the PactSafe Group.
  _ps(groupKey + ":send", "agreed", {
    disable_sending: false, // We have to revert to allow sending with the snippet here.
    event_callback: function (err, eventType, group, request) {
      if (err) {
        // Something went wrong with sending the agreed event.
        alert("Uh oh, something went wrong. Please try submitting again."); // Alert the user
        return false; // Prevent form submission due to error.
      }

      // Since we had no errors, go ahead and submit the form.
      var form = pageFormElement();
      if (form) form.submit(); // Check we're able to retrieve the form.
      return true;
    }
  });
}

// We want to add listeners for validation and handling of render data.
function addListeners() {
  var form = pageFormElement(); // Get the form element.
  var showContractBtn = showContractButton();
  var lastNameInputField = document.getElementById("lastNameInput1");

  if (form) {
    // Add listener for form submissions.
    form.addEventListener("submit", function (event) {
      handleFormSubmit(event);
    });
  }

  if (showContractBtn) {
    // Handle when show contract button is clicked.
    showContractBtn.addEventListener("click", function () {
      handleShowContractButton();
    });
  }

  if (lastNameInputField) {
    // Allow show contract button if second input field changes
    // and has text in it.
    lastNameInputField.addEventListener("change", function () {
      if (lastNameInputField.val != "") showContractBtn.disabled = false;
    });
  }
}

// Set up validation of Terms before allowing form submission.
if (document.readyState === "loading") {
  // Loading hasn't finished yet
  document.addEventListener("DOMContentLoaded", addListeners);
} else {
  // `DOMContentLoaded` has already fired
  addListeners();
}

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css

External JavaScript

  1. https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.bundle.min.js