Typeform style surveys with Frappe and SurveyJS

Vijay R
7 min readOct 1, 2023
SurveyJS Forms

The Problem

At Greycube Technologies, we recently had a client who needed a complex web form, with several pages and navigation within them, and conditionally show or hide fields and panels. The out-of-the-box frappe features were not enough. So we began looking for a general solution to build forms out of json. Chances are, you have been there as well, building a survey, opinion poll or a sign-up forms.

The Solution

Introducing SurveyJS. A brilliant library that can handle the most complex requirement you can come up with, that is also open source. You can checkout the demos here, that have over 25 different question types. It seemed like the integration would be fairly easy, and easy it was.

Follow along to see how we made a custom frappe app to create complex forms using SurveyJ within the frappe webform.

The POC

To quickly test that the idea works, build a POC using a web page. We will design the necessary web form and Doctypes for Survey and Response later on.

Create a custom app frappe_survey from the bench

bench new-app frappe_survey

Install the app to your test site

bench — site survey.demo install-app frappe_survey

Get the js and css files for SurveyJS to your assets folder

https://cdnjs.cloudflare.com/ajax/libs/survey-jquery/1.9.110/survey.jquery.min.js to /public/js/survey.jquery.min.js
https://cdnjs.cloudflare.com/ajax/libs/survey-jquery/1.9.110/survey.min.css to /public/css/survey.min.css

To make form library available inside web forms, include the above files into the web bundle from hooks.py

web_include_css = "/assets/frappe_survey/css/survey.min.css"
web_include_js = "/assets/frappe_survey/js/survey.jquery.min.js"

Pick up a basic form from the example page https://surveyjs.io/form-library/examples/yes-no-question/jquery#content-code or use the json below.

{
"title": "Webinar Feedback",
"description": "Please give your feedback to help us improve our courses.",
"pages": [
{
"name": "page1",
"elements": [
{
"type": "text",
"name": "question1",
"title": "Name"
}
],
"title": "Participant Details"
}
]
}

Create a container for the survey in the web page HTML

Set a container for the survey instance

Now set the js shown below in the javascript field of the web form. This script creates a survey instance with the given json model

const survey_json = {
title: "Webinar Feedback",
description: "Please give your feedback to help us improve our courses.",
pages: [
{
name: "page1",
elements: [
{
type: "text",
name: "question1",
title: "Name",
},
],
title: "Participant Details",
},
],
};
frappe.ready(() => {
frappe.require(["/assets/frappe_survey/js/survey.jquery.min.js"], () => {
const survey = new Survey.Model(survey_json);

survey.onComplete.add((sender, options) => {
console.log(JSON.stringify(sender.data, null, 3));
});

$("#surveyElement").Survey({ model: survey });
});
});
set the script for the web page

And open the web page!

Web Form Survey

Awesome! That worked.

The Design

If this could be embedded into a web form, then we would have an easy way to create surveys and then store user responses. Lets go ahead and make some DocTypes. We need a Survey DocType that will represent a Survey. So it can have a Title and any other properties for the survey. We need a Survey Question DocType that will be child DocType and this will hold the questions in a Survey. Then we need a Survey Response DocType, that would hold the responses submitted by a User.

Survey has some basic properties like Title, sub title and the child table for questions. To keep it simple for this example, add a field to store the json for the survey. In production usage, this will have to replaced to construct the survey model from the frappe doc. For now the survey form will be initiated with the json from the doc.

Start with just the basic properties for the Survey Question. We will need to add fields to handle complex question types. This child table is common between Survey and Survey Response, so add a field that will store the response in Survey Response. This will be used to build the survey model dynamically (instead of the survey_json)

Survey Response doctype will be the response submitted by the user. Add a child table for the responses (Survey Question with the value of the response)

Now that the doctypes are ready, we can create some surveys.

Create a Survey and set the Survey Json value to a valid survey json model. You can pick it up from the examples page like this Cancellation Survey

Survey “Webinar Feedback”

Next, create a web form, with the same name as the survey created above. Set the javascript necessary to fetch the Questions from the Survey and render the survey form. This is the same js used in the web page above, but we now get then survey mode through a frappe.call to the Survey. As noted above, we are currently using the json model from the Survey doc, instead of building the model from the questions for the sake of simplicity.

frappe.web_form.after_load = () => {
load_survey(frappe.web_form_doc.title);
};


const load_survey = function (survey_name) {
$(".web-form-container").toggle(false);
$('<div id="surveyElement"></div>').appendTo($(".page_content"));
frappe
.call({
method: "frappe.client.get",
args: {
doctype: "Survey",
name: frappe.web_form.title,
},
})
.then((r) => {
build_survey(r.message);
const survey = new Survey.Model(frappe.survey_json);
survey.applyTheme(frappe.theme_json);
survey.onComplete.add((sender, options) => {
// to submit the response to controller
console.log(JSON.stringify(sender.data, null, 3));
});
// initialize survey
$("#surveyElement").Survey({ model: survey });
});
};

const build_survey = function (data) {
// transform frappe doc to survey model
console.log(data.survey_json.replaceAll("\n", ""));

frappe.survey_json = JSON.parse(data.survey_json.replaceAll("\n", ""));
frappe.theme_json = data.theme_json
? JSON.parse(data.theme_json)
: {
themeName: "plain",
colorPalette: "dark",
isPanelless: true,
};
};
Web Form “Webinar Feedback”

With that done, it is time to test drive our web form. Click the See on Website link on the webform. Voila! the survey form is ready as a dynamic web form now.

We can wrap it up with some simple code to submit the completed response. Since SurveyJS will be handling the form events, we hook into the SurveyJS api onComplete to call the submit method of the web form. We need to transform the SurveyJS model to a frappe Survey Response doc in order to save it.

Again for the sake of simplicity, let us add a long text field in Survey Response to store the response as is. The individual question responses can be parsed and added to the child table within on_update. Update the js in the “Webinar Feedback” web form to below.


frappe.web_form.after_load = () => {
load_survey(frappe.web_form_doc.title);
};

const load_survey = function (survey_name) {
$(".web-form-container").toggle(false);
$('<div id="surveyElement"></div>').appendTo($(".page_content"));
frappe
.call({
method: "frappe.client.get",
args: {
doctype: "Survey",
name: frappe.web_form.title,
},
})
.then((r) => {
build_survey(r.message);
const survey = new Survey.Model(frappe.survey_json);
survey.applyTheme(frappe.theme_json);
survey.onComplete.add((sender, options) => {
submit_response(sender.getAllValues());
});
// initialize survey
$("#surveyElement").Survey({ model: survey });
});
};

const build_survey = function (data) {
// transform frappe doc to survey model
frappe.survey_json = JSON.parse(data.survey_json.replaceAll("\n", ""));
frappe.theme_json = data.theme_json
? JSON.parse(data.theme_json)
: {
themeName: "plain",
colorPalette: "dark",
isPanelless: true,
};
};

const submit_response = function (data) {
// Save
window.saving = true;
frappe.form_dirty = false;

let args = {
survey: frappe.web_form.title,
response_json: JSON.stringify(data),
};

console.log(args);

frappe.call({
type: "POST",
method: "frappe.website.doctype.web_form.web_form.accept",
args: {
web_form: frappe.web_form.name,
data: args,
},
callback: (response) => {
if (!response.exc) {
// Success
frappe.show_alert(__("Your response has been submitted successfully."));
}
},
always: function () {
window.saving = false;
},
});
};

And the survey will be submitted now on complete.

Check if the Survey Response is created.

Next Steps

To make the solution flexible and easy for an end user to create surveys, we need to add some code to build the survey model from the Questions, and also extend the Survey Question doctype with then necessary fields to handle all question types. The survey response will need to be parsed and responses to questions added as lines in the Responses child table. These are fairly easy to accomplish for a frappe developer.

Conclusion

We have used SurveyJS to build a powerful tool for making any kind of Survey, Quiz or Questionnaire in minutes. Any frappe user, with basic skills, can create a Survey by duplicating our web form. You can get the the app from https://github.com/grey-cube/frappe_survey.git and try it on your sites.

--

--

Vijay R

Technical Lead at Greycube Technologies, providing solutions in ERPNext