unwilling-turquoise
unwilling-turquoise15mo ago

Custom Widgets & Effects (Beta) Feature - Form - Issues

Hello! So I am testing out the Custom Widget beta feature and integrated a custom form within the chat interface to collect user details seamlessly in one message. However, when the form has appeared within the conversation, users can still type a message which will automatically skip the form submission step at continue to the conversation. How do I control / stop the user input at this point in order to only continue the conversation once the form details have been inputted and submitted? Also, how do I edit the form layout so it looks better and more appealing? Finally, which isn't included in the loom, where does the form get submitted to, so I can check the details inputted? Here is a quick loom running over x3 issues I am facing with the form + an additional want of making it look more appealing. https://www.loom.com/share/f9b3f10b266f4e65b8952afc2ad60896?sid=a4ce9fb6-e86b-45db-a374-fdfde3d6475e Thanks!
12 Replies
rival-black
rival-black15mo ago
Hey man, I have the solution for you. I've just upgraded this forms extension to block user input.
rival-black
rival-black15mo ago
rival-black
rival-black15mo ago
export const FormExtension = {
name: "FormExtension",
type: "response",
match: ({ trace }) => trace.type === "ext_form" || trace.payload.name === "ext_form",
render: ({ trace, element }) => {

const disableFooterInputs = (isDisabled) => {
const chatDiv = document.getElementById('voiceflow-chat');
if (chatDiv) {
const shadowRoot = chatDiv.shadowRoot;
if (shadowRoot) {
const textareas = shadowRoot.querySelectorAll('textarea');
textareas.forEach(textarea => {
textarea.disabled = isDisabled;
textarea.style.backgroundColor = isDisabled ? '#d3d3d3' : '';
textarea.style.opacity = isDisabled ? '0.5' : '';
textarea.style.pointerEvents = isDisabled ? 'none' : 'auto';
});

const buttons = shadowRoot.querySelectorAll('.c-bXTvXv.c-bXTvXv-lckiv-type-info');
buttons.forEach(button => {
button.disabled = isDisabled;
button.style.pointerEvents = isDisabled ? 'none' : 'auto';
});
}
}
};

const formContainer = document.createElement("form");

formContainer.innerHTML = `
<style>
label {
font-size: 0.8em;
color: #888;
}
input[type="text"], input[type="email"], input[type="tel"] {
width: 100%;
border: none;
border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
background: transparent;
margin: 5px 0;
outline: none;
}
.phone {
width: 150px;
}
.invalid {
border-color: red;
}
.submit {
background: linear-gradient(to right, #2e6ee1, #2e7ff1 );
border: none;
color: white;
padding: 10px;
border-radius: 5px;
width: 100%;
cursor: pointer;
}
</style>

<label for="name">Name</label>
<input type="text" class="name" name="name" required><br><br>

<label for="email">Email</label>
<input type="email" class="email" name="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$" title="Invalid email address"><br><br>

<label for="phone">Phone Number</label>
<input type="tel" class="phone" name="phone" required pattern="\\d+" title="Invalid phone number, please enter only numbers"><br><br>

<input type="submit" class="submit" value="Submit">
`;

formContainer.addEventListener("submit", function (event) {
event.preventDefault();

const name = formContainer.querySelector(".name");
const email = formContainer.querySelector(".email");
const phone = formContainer.querySelector(".phone");

if (!name.checkValidity() || !email.checkValidity() || !phone.checkValidity()) {
name.classList.add("invalid");
email.classList.add("invalid");
phone.classList.add("invalid");
return;
}

formContainer.querySelector(".submit").remove();
disableFooterInputs(false);

window.voiceflow.chat.interact({
type: "complete",
payload: { name: name.value, email: email.value, phone: phone.value },
});
});

element.appendChild(formContainer);

disableFooterInputs(true);
},
};
export const FormExtension = {
name: "FormExtension",
type: "response",
match: ({ trace }) => trace.type === "ext_form" || trace.payload.name === "ext_form",
render: ({ trace, element }) => {

const disableFooterInputs = (isDisabled) => {
const chatDiv = document.getElementById('voiceflow-chat');
if (chatDiv) {
const shadowRoot = chatDiv.shadowRoot;
if (shadowRoot) {
const textareas = shadowRoot.querySelectorAll('textarea');
textareas.forEach(textarea => {
textarea.disabled = isDisabled;
textarea.style.backgroundColor = isDisabled ? '#d3d3d3' : '';
textarea.style.opacity = isDisabled ? '0.5' : '';
textarea.style.pointerEvents = isDisabled ? 'none' : 'auto';
});

const buttons = shadowRoot.querySelectorAll('.c-bXTvXv.c-bXTvXv-lckiv-type-info');
buttons.forEach(button => {
button.disabled = isDisabled;
button.style.pointerEvents = isDisabled ? 'none' : 'auto';
});
}
}
};

const formContainer = document.createElement("form");

formContainer.innerHTML = `
<style>
label {
font-size: 0.8em;
color: #888;
}
input[type="text"], input[type="email"], input[type="tel"] {
width: 100%;
border: none;
border-bottom: 0.5px solid rgba(0, 0, 0, 0.1);
background: transparent;
margin: 5px 0;
outline: none;
}
.phone {
width: 150px;
}
.invalid {
border-color: red;
}
.submit {
background: linear-gradient(to right, #2e6ee1, #2e7ff1 );
border: none;
color: white;
padding: 10px;
border-radius: 5px;
width: 100%;
cursor: pointer;
}
</style>

<label for="name">Name</label>
<input type="text" class="name" name="name" required><br><br>

<label for="email">Email</label>
<input type="email" class="email" name="email" required pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$" title="Invalid email address"><br><br>

<label for="phone">Phone Number</label>
<input type="tel" class="phone" name="phone" required pattern="\\d+" title="Invalid phone number, please enter only numbers"><br><br>

<input type="submit" class="submit" value="Submit">
`;

formContainer.addEventListener("submit", function (event) {
event.preventDefault();

const name = formContainer.querySelector(".name");
const email = formContainer.querySelector(".email");
const phone = formContainer.querySelector(".phone");

if (!name.checkValidity() || !email.checkValidity() || !phone.checkValidity()) {
name.classList.add("invalid");
email.classList.add("invalid");
phone.classList.add("invalid");
return;
}

formContainer.querySelector(".submit").remove();
disableFooterInputs(false);

window.voiceflow.chat.interact({
type: "complete",
payload: { name: name.value, email: email.value, phone: phone.value },
});
});

element.appendChild(formContainer);

disableFooterInputs(true);
},
};
To retrieve the information on canvas, you need to set a JavaScript step with
name = last_event.payload.name
name = last_event.payload.name
(remember to create a variable). Also, I highly recommend you check out this vf.file as it contains a lot of examples of extensions. https://github.com/voiceflow-gallagan/vf-extensions-demo/blob/main/Chat_Widget_Extensions_DEMO-2024-03-07_01-11.vf
metropolitan-bronze
metropolitan-bronze15mo ago
This is the form extension? @randompenna Can you show me the flow you used to do that Been trying to figure that out
rival-black
rival-black15mo ago
You can download this template to better understand everything. There’s also a repo for that
metropolitan-bronze
metropolitan-bronze15mo ago
Thanks Have you also figured out the other extensions? Like confetti and such Well actually have you figure out the Map extension @randompenna
rival-black
rival-black15mo ago
metropolitan-bronze
metropolitan-bronze15mo ago
how do I differentiate them and how exactly do I execute a function from a custom action
rival-black
rival-black15mo ago
You need to name your custom action with the trace type of your extension trace.type === "ext_multiselect" ext_multiselect in the custom action
rival-black
rival-black15mo ago
No description
metropolitan-bronze
metropolitan-bronze15mo ago
ah i see

Did you find this page helpful?