Get Information from Client
Requests information from the client, such as user input collected using a form.
About client-facing steps
A journey is a sequence of steps that are executed when it's invoked by a client application (known as the "client"). Some steps require involvement from the client, such as to collect user input. Other steps, like validating a token, are executed in the backend by the Mosaic journey engine alone.
When invoked, the journey begins executing the steps while the client waits for further instructions. When a journey reaches a client-facing step, the journey asks the client for the required input and then waits for the client to respond. The client presents their own UI if user interaction is required, and returns input to the journey. The journey then proceeds in a similar manner until it’s completed.
Description
This client-facing step is used to request information from the client application, which is typically input from the user. For example, it can be used to collect user details upon registration, or collect a user identifier like email or username.
Once the step is triggered, the journey asks the client for input and provides a step ID that uniquely identifies this particular step. The client knows what information is expected based on a JSON schema that was exchanged between the journey author and app developer during the integration process. The client is responsible for retrieving the information, including any supporting UI (e.g., HTML form).
Once the information is returned to the journey, it's stored in the configured output variable so it can be accessed by subsequent journey steps. If successful, the journey proceeds to the next step (or a custom branch if configured). If the client cancels, the journey proceeds to a cancel branch if configured and aborted otherwise.
As implied, this step requires coordination between the journey author and app developer, including:
- How the client should process the request when the journey returns a particular step ID
- Whether the client needs any context data like user-specific values to prefill a form. If so, this is configured in the step as application data as a free-form JSON and the schema is shared offline.
- What information the journey expects from the client. The schema is configured in the step.
- If the client cancels the step, whether it should fail the journey or proceed to another branch
- Whether the step supports additional branches that can be selected by the client. For example, each branch may represent a different option that the user can choose.
Note
For authentication scenarios, the Login Form step is recommended since it provides a more comfortable setup experience. Branches are automatically created based on the authentication methods you enable for the step, and a default schema is provided for each method.
Configuration
Field | Description |
---|---|
Step ID | Identifier of the step, used by the client to process the request for information. It can only contain letters, numbers, and underscores (no spaces), and it cannot begin with a number. Some examples of valid IDs are registrationForm1 or registration_form_1 |
Application Data | Data to send to the client, formatted as a free-form JSON and may include expressions. For example, it can contain user-specific values to prefill fields of an update form. |
Schema | JSON schema describing the expected data returned by the client. It may include expressions. Only used by the journey code generator and journey editor (for autocompleting expressions). This isn't expected to be used by the client dynamically. Access and customize schema via the gear icon. |
Error Data | When this step is executed within a While Loop step, this represents errors to return to the client. For example, if the loop validates the user input, this can provide validation errors to display to the user. May be configured as a variable or JSON object, and contain expressions. |
Custom Branches | Additional journey branches supported for this step. For each branch, you define a branch ID and display name to label it in the editor, and a schema for the input expected back from the client. Note: The schema is used by the code generator and for autocompleting journey expressions, and isn't expected to be used by the client dynamically. The client can select a branch by returning the branch ID when calling submitClientResponse() . See Example 4: Registration form with cancel and login options for implementation details. |
Branch Output Variable | Name of the variable used to store the data returned by the client, which can be used in subsequent journey steps. |
Cancel Behavior | Determines the behavior of client cancellation, which either aborts the journey (default) or proceeds to a cancel branch of the control flow. |
Examples
Below are some examples of implementing this step:
- Example 1: Registration form
- Example 2: Update form
- Example 3: Update form with cancel option
- Example 4: Registration form with cancel and login options
Example 1: Registration form
Consider a form that collects the user's name and email. In our example, the step ID is regForm
and the information will be stored in a variable named registrationData
. The schema contains the fields the client is expected to return, which is used by the journey code generator to render a form.
When executed, this step returns to the client the IDO response object. The client then processes the request based on the step ID passed in journeyStepId
. For example:
`<form id="regForm">
<input type="text" id="full_name" name="full_name" placeholder="full_name" />
<input type="email" id="email" name="email" placeholder="email" />
<button>Submit</button>
</form>`
// Collect the form data
const form = document.querySelector("#regForm");
const formData = new FormData(form);
// The data will look like this:
// {
// "full_name": "John Doe",
// "email": "john@doe.com"
// }
const data = Object.fromEntries(formData.entries());
// Submit the data to the SDK
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.ClientInput, data);
// Presenting view controller
let formViewController = FormViewController()
navigationController?.pushViewController(formViewController, animated: true)
// Inside the View Controller:
@IBAction func onSubmitForm() {
// Collect the form data
let full_name = usernameInput.text
let email = emailInput.text
// Convert form data to JSON format
let formData = {
full_name: "John Doe",
email: "john@doe.com"
}
// Submit the data to the SDK
TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: formData)
}
// Stores the collected full name and email
// The data will look like this:
// {
// "full_name": "John Doe",
// "email": "john@doe.com"
// }
data class RegistrationData(val full_name: String, val email: String)
fun submitRegistrationData() {
// Assumes the UI shows EditText - etCollectFullName, to collect the full name
// and another EditText - etCollectEmail, to collect the email
val responseData = RegistrationData(binding.etCollectFullName.text.toString(), binding.etCollectEmail.text.toString())
// Submits data to the SDK
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, responseData, callback)
}
Example 2: Update form
Consider a form for updating the user's email. In our example, the step ID is updateForm
and the information will be stored in a variable named updatedData
. The application data contains a currentEmail
field containing the user's current email so it can be used to prefill the form. Our example assumes the email was obtained in a prior step and is stored it in a variable named email
. Notice that the variable isn't surrounded by quotes in the expression.
When executed, this step returns to the client the IDO response object. The client then processes the request based on the step ID passed in journeyStepId
and the user's current email provided in data.app_data.currentEmail
. For example:
function handleGetInformationFromClient(idoResponse) {
// Generate the form's HTML
const formHtml = `<form id="updateForm">
<input type="email" id="email" name="email" value="${idoResponse.data.app_data.currentEmail}" />
<button>Submit</button>
</form>`;
// Render the form
document.body.innerHTML = formHtml;
document.querySelector('#updateForm').addEventListener('submit', function(event) {
event.preventDefault();
// Collect the form data
const form = event.target;
const formData = new FormData(form);
// The data will look like this:
// {
// "email": "john@doe.com"
// }
const data = Object.fromEntries(formData.entries());
// Submit the data to the SDK
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.ClientInput, data);
});
}
// Inside your main class:
func handleIdoResponse(_ response: IdentityOrchestration.TSIdoServiceResponse) {
guard let stepId = response.journeyStepId else { debugPrint("Step id is missing in response"); return }
guard let actionData = response.data else { debugPrint("Data is missing in response"); return }
switch stepId {
case .custom(let formId):
// Present view controller and pass the 'data' fetched above
let formViewController = FormViewController(data: actionData)
navigationController?.pushViewController(formViewController, animated: true)
default:
// Handle other steps
break
}
}
// Inside the View Controller:
func viewDidLod() {
super.viewDidLod()
// Fetch email from the data
let appData = data["app_data"] as? [String: Any]
let email = appData["currentEmail"] as? String
// Update email input text
emailInput.text = email
}
@IBAction func onSubmitForm() {
// Collect the form data
let email = emailInput.text
// Convert form data to JSON format
let formData = {
"email": "john@doe.com"
}
// Submit the data to the SDK
TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: formData)
}
// Updates user email
data class EmailInput(val email: String)
fun handleEmailUpdate(idoResponse: TSIdoServiceResponse) {
val appData = (idoResponse.data as JSONObject).opt("app_data")
// Extracts the current email from TSIdoServiceResponse
val currentEmail = (appData as JSONObject).optString("currentEmail")
// Assumes the etEmail EditText is used for presenting current email and collecting the updated one
//and the btnUpdateEmail Button triggers submitClientResponse with the updated email
binding.etEmail.setText(currentEmail)
binding.btnUpdateEmail.setOnClickListener {
val emailResponseData = EmailInput(binding.etEmail.text.toString())
// Submits the data to the SDK
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, emailResponseData, callback)
}
}
Example 3: Update form with cancel option
Building on the previous example, consider an update form that includes a cancel option in case the user decides not to proceed with the update. In this example, a cancel branch is added to the step by updating the Cancel Behavior field to Go To Cancel Branch. This branch will be executed in case the client selects the cancel option.
When executed, this step returns to the client the IDO response object, which now includes the cancel option in the client response options. For example:
function handleGetInformationFromClient(idoResponse) {
// Generate the form's HTML
const formHtml = `<div>
<form id="updateForm">
<input type="email" id="email" name="email" value="${idoResponse.data.app_data.currentEmail}" />
<button type="submit">Submit</button>
</form>
<button id="cancel">Cancel</button>
</div>`;
// Render the form
document.body.innerHTML = formHtml;
document.querySelector('#updateForm').addEventListener('submit', function(event) {
event.preventDefault();
// Collect the form data
const form = event.target;
const formData = new FormData(form);
// The data will look like this:
// {
// "email": "john@doe.com"
// }
const data = Object.fromEntries(formData.entries());
// Submit the data to the SDK
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.ClientInput, data);
});
// Handle the cancel button
document.querySelector('#cancel').addEventListener('click', function() {
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.Cancel);
});
}
// Inside your main class:
func handleIdoResponse(_ response: IdentityOrchestration.TSIdoServiceResponse) {
guard let stepId = response.journeyStepId else { debugPrint("Step id is missing in response"); return }
guard let actionData = response.data else { debugPrint("Data is missing in response"); return }
switch stepId {
case .custom(let formId):
// Present view controller and pass the 'data' fetched above
let formViewController = FormViewController(data: actionData)
navigationController?.pushViewController(formViewController, animated: true)
default:
// Handle other steps
break
}
}
// Inside the View Controller:
func viewDidLod() {
super.viewDidLod()
// Fetch email from the data
let appData = data["app_data"] as? [String: Any]
let email = appData["currentEmail"] as? String
// Update email input text
emailInput.text = email
}
@IBAction func onSubmitForm() {
// Collect the form data
let email = emailInput.text
// Convert form data to JSON format
let formData = {
"email": "john@doe.com"
}
// Submit the data to the SDK
TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: formData)
}
@IBAction func onCancel() {
// Handle the cancel button
TSIdo.submitClientResponse(clientResponseOptionId: .cancel, data: data)
}
// Updates user email
data class EmailInput(val email: String)
fun handleEmailUpdate(idoResponse: TSIdoServiceResponse) {
val appData = (idoResponse.data as JSONObject).opt("app_data")
// Extracts the current email from TSIdoServiceResponse
val currentEmail = (appData as JSONObject).optString("currentEmail")
// Assumes the etEmail EditText is used for presenting current email and collecting the updated one
// the btnUpdateEmail Button triggers submitClientResponse with the updated email
// the btnCancel Button submits user's cancellation to the SDK
binding.etEmail.setText(currentEmail)
binding.btnUpdateEmail.setOnClickListener {
val emailResponseData = EmailInput(binding.etEmail.text.toString())
// Submits the data to the SDK
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, emailResponseData, callback)
}
// Handles the cancel button
binding.btnCancel.setOnClickListener {
// Submits the cancellation to the SDK
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.Cancel.type, null, callback)
}
}
Example 4: Registration form with cancel and login options
Building on the first example, consider a registration form that on top of "Submit" and "Cancel" options includes a option to proceed to login, for example, if the user has already registered with the service before. In this example, a custom branch (login
) is added to the step, which automatically creates a new branch that will be used to login in the existing user. For this branch, the schema is empty.
When executed, this step returns to the client the idoServiceResponse
object, which now includes the login option in the clientResponseOptions
. For example, if the user clicks a "Go to login" link on the registration form, the client can proceed to direct the journey to the relevant branch that handles login.
function handleGetInformationFromClient(idoResponse) {
// Generate the form's HTML
const formHtml = `
<div>
<form id="regForm">
<input type="text" id="full_name" name="full_name" placeholder="Full name" />
<input type="email" id="email" name="email" placeholder="Email address" />
<button>Submit</button>
</form>
<button id="cancel">Cancel</button>
<button id="login">Go to login</button>
</div>
`;
// Render the form
document.body.innerHTML = formHtml;
document.querySelector('#regForm').addEventListener('submit', function(event) {
event.preventDefault();
// Collect the form data
const form = event.target;
const formData = new FormData(form);
// The data will look like this:
// {
// "full_name": "John Doe",
// "email": "john@doe.com"
// }
const data = Object.fromEntries(formData.entries());
// Submit the form data to the SDK
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.ClientInput, data);
});
// Handle the cancel button
document.querySelector('#cancel').addEventListener('click', function() {
// Proceed to cancel branch
window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.Cancel);
});
// Handle the login button
document.querySelector('#login').addEventListener('click', function() {
// Proceed to custom branch
window.tsPlatform.ido.submitClientResponse("login");
});
}
// Presenting view controller
let formViewController = FormViewController()
navigationController?.pushViewController(formViewController, animated: true)
// Inside the View Controller:
@IBAction func onSubmitForm() {
// Collect the form data
let full_name = usernameInput.text
let email = emailInput.text
// Convert form data to JSON format
let formData = {
full_name: "John Doe",
email: "john@doe.com"
}
// Submit the data to the SDK
TSIdo.submitClientResponse(clientResponseOptionId: .clientInput, data: formData)
}
// Proceed to custom branch
@IBAction func onLogin() {
TSIdo.submitClientResponse(clientResponseOptionId: .custom(id: "login"))
}
// Proceed to cancel branch
@IBAction func onCancel() {
TSIdo.submitClientResponse(clientResponseOptionId: .cancel)
}
// Data class that stores the collected full name and email
// The data will look like this:
// {
// "full_name": "John Doe",
// "email": "john@doe.com"
// }
data class RegistrationData(val full_name: String, val email: String)
...
private fun setupUI() {
//handle submit button click
binding.btnSubmitUserInfo.setOnClickListener{
submitRegistrationData()
}
//handle login button click
binding.btnSubmitUserInfo.setOnClickListener{
handleLoginBranch()
}
//handle cancel button click
binding.btnCancel.setOnClickListener{
handleCancellation()
}
}
private fun submitRegistrationData() {
// Assumes the UI shows EditText - etCollectFullName, to collect the full name
// and another EditText - etCollectEmail, to collect the email
val responseData = RegistrationData(binding.etCollectFullName.text.toString(), binding.etCollectEmail.text.toString())
// Submits data to the SDK
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, responseData, callback)
}
private fun handleLoginBranch() {
// Proceed to custom branch
TSIdo.submitClientResponse("login", null, callback)
}
private fun handleCancellation() {
// Proceed to cancel branch
TSIdo.submitClientResponse(TSIdoClientResponseOptionType.Cancel.type, null, callback)
}