Back to Home Back to Post

surveyJS

Javascript Survey Creation & Management. Made Easy

Intro

surveyJS let's you create a survey from a JSON and manage all the process ( fields validation, local storage and form data are already managed )
You can manage:

  • HTML for questions, answers and feedback messages
  • Form data before sending it to the backend
  • Callback functions for AJAX calls success/error/end and validation
  • and more...

Browsers Support

surveyJS is compatible with ( desktop & mobile versions ):

  • Chrome
  • Firefox
  • Internet Explorer 10+ / Edge *
  • Safari 9+

* for compatibility with IE 10/11 and old versions of Edge you have to load some JS polyfills ( from polyfill.io or else ):
Array.from Element.prototype.closest Element.prototype.matches Promise Promise.prototype.finally AbortController fetch

<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=Array.from,Element.prototype.closest,Element.prototype.matches,Promise,Promise.prototype.finally,AbortController,fetch"></script>

Dependencies

surveyJS is developed with these dependencies:

  • formJS ( version 3.1.0 or higher - for survey form validation and submit )
  • Bootstrap 4 CSS ( Optional - only form CSS ) but you can write the CSS by yourself ;)

Usage

Include css and js in your page.

<link href="https://unpkg.com/surveyjs/dist/survey.min.css" rel="stylesheet" type="text/css">
<script src="https://unpkg.com/formjs-plugin@3/dist/formjs.min.js"></script>
<script src="https://unpkg.com/surveyjs/dist/surveyjs.min.js"></script>

or from jsDelivr:

<link href="https://cdn.jsdelivr.net/gh/simplysayhi/surveyJS/dist/survey.min.css" rel="stylesheet" type="text/css">
<script src="https://cdn.jsdelivr.net/gh/simplysayhi/formJS@3/dist/formjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/simplysayhi/surveyJS/dist/surveyjs.min.js"></script>

or install via NPM:

npm install surveyjs

and use as ES6 module:

import Survey from 'surveyjs';

or use as CommonJS module:

var Survey = require('surveyjs');

HTML

This is the basic HTML structure to initialize the survey ( element with data-surveyjs-body will be filled with questions & answers. ):

<div class="surveyjs-container" data-surveyjs-container>
    <form action="page.jsp" name="surveyjs-form" class="surveyjs-form" data-surveyjs-form novalidate>
        <div class="surveyjs-body questionsList clearfix" data-surveyjs-body></div>
        <div class="surveyjs-footer">
            <button type="submit">SEND</button>
        </div>
    </form>
</div>

Mandatory attributes:

  • data-surveyjs-* attributes
  • action the page where to send the users answers ( with the AJAX call )
  • novalidate prevent the HTML5 default validation of required fields ( they will be validated via JS )

CSS classes ( like .surveyjs-* ) are optional but used to give styles to the demos.
For custom graphic inputs, using CSS class surveyjs-custom-inputs on the Survey container ( with class "surveyjs-container" ) is suggested in order to default Survey CSS be applied.

Initialization

You must specify the url to retrieve the JSON data to build the survey.

var formEl = document.querySelector('[data-surveyjs-form]');
var options = { url: 'json/survey.json' };
var mySurvey = new Survey( formEl, options );
mySurvey.init().then(function( response ){
    console.log('SURVEY DATA RETRIEVED', response);
});
  • formEl: ( required ) must be a single form node ( not a nodelist! ) or a CSS string selector var formEl = '.my-container .my-form';
  • options: ( required ) a JS object as defined in Options section with at least url

surveyJS has some more properties:

  • data: the survey data structure returned from the AJAX call.
  • internals: objects & methods used internally ( formInstance can be useful also for you ).
  • isInitialized: whether or not the init method was called on the Survey instance.
  • version: the version number.

JSON File

Example of JSON file for the survey is here.
Note that the AJAX call to get the JSON file MUST return it as JSON string or JSON object.
The survey will be built if status is "success" and questions is a non-empty array.

{
    "status": "success", // MANDATORY, IF "success" THE SURVEY WILL BE BUILT
    "data": {
        "id": 0,
        "title": "My Survey",
        "description": "Answer the questions",

        // LIST OF ALL QUESTIONS
        "questions": [

            // RADIO ANSWER
            {
                "id": "1",
                "question": "Do you have pets?",
                "answers": [
                    // EACH ANSWER IS AN OBJECT WITH SOME DATA
                    { "id": "1", "type": "radio", "answer": "Yes" },
                    { "id": "2", "type": "radio", "answer": "No" }
                ],
                // IF required IF FOUND, THE ANSWER FIELDS WILL HAVE THE required ATTRIBUTE (SO THIS QUESTION REQUIRE AN ANSWER)
                "required": "",
                // THE POSITION TO SET FOR THE QUESTION (USED ALSO FOR ANSWERS - SEE ABOVE) - THIS IS OPTIONAL
                "sort": 1
            },

            // VALIDATE INPUT FIELD ONLY IF FILLED ( validateIfFilled ) AND WITH SPECIFIC subtype VALIDATION ( SEE FORM JS PLUGIN )
            {
                "id": "1b0",
                "question": "What's your name?",
                "answers": [
                    { "id": "1ba0", "type": "text", "answer": "", "subtype": "alphabeticExtended" }
                ],
                "validateIfFilled": "",
                "sort": 0
            },

            // TEXT INPUT ANSWER NOT MANDATORY
            {
                "id": "1b",
                "question": "Do you have pets?",
                "answers": [
                    { "id": "1ba", "type": "text", "answer": "" }
                ],
                "sort": 2
            },

            // INPUT WITH SUBTYPE ( IF TYPE EXISTS IN HTML YOU CAN SET IT AS "type" -> "type": "email" )
            {
                "id": "1c",
                "question": "What's your email address?",
                "answers": [
                    { "id": "1c", "type": "text", "answer": "", "subtype": "email" }
                ],
                "required": "",
                "sort": 1
            },

            {
                "id": "2",
                "question": "Which are your hobbies/interests?",
                "answers": [
                    { "id": "6", "type": "radio", "answer": "Music", "sort": 5 },
                    { "id": "7", "type": "radio", "answer": "Photography", "sort": 2 },
                    { "id": "8", "type": "radio", "answer": "Technology", "sort": 3 },
                    { "id": "9", "type": "radio", "answer": "Games", "sort": 4 },
                    { "id": "10", "type": "radio", "answer": "Reading", "sort": 1 },
                    { "id": "11", "type": "radio", "answer": "Wellness", "sort": 6 },
                    {
                        "id": "100", "type": "radio", "answer": "Other...", "sort": 7
                    }
                ],
                "required": "",
                "sort": 3
            },

            // TEXTARE
            {
                "id": "3",
                "question": "If yes, specify which sport:",
                "answers": [
                    { "id": "18", "type": "textarea", "answer": "", "maxlength": 300 }
                ],
                "sort": 4
            },

            // RELATED FIELD ANSWER WITH INPUT TEXT ( SEE attribute FIELD )
            {
                "id": "5",
                "question": "What is important for you about the loyalty program?",
                "answers": [
                    { "id": "19", "type": "radio", "answer": "It should be funny" },
                    { "id": "20", "type": "radio", "answer": "It does not require much time" },
                    {
                        "id": "200", "type": "radio", "answer": "Other...",
                        "attribute": {
                            "id": "", "type": "text", "answers": ""
                        }
                    }
                ],
                "required": "",
                "sort": 6
            },

            // NESTED ANSWERS ( SEE nested ATTRIBUTE )
            // RELATED FIELD ANSWER WITH SELECT FIELD ( SEE attribute FIELD )
            {
                "id": "6",
                "question": "How do you prefer to be interact with the loyalty program?",
                "answers": [
                    {
                        "id": "21", "type": "radio", "answer": "Website",
                        "nested": [
                            { "id": "21a", "type": "radio", "answer": "Pc", "sort": 1 },
                            { "id": "21b", "type": "radio", "answer": "Smartphone", "sort": 2 },
                            { "id": "21c", "type": "radio", "answer": "Tablet", "sort": 3 }
                        ], "sort": 1
                    },
                    {
                        "id": "21aPlus", "type": "radio", "answer": "Website",
                        "attribute": [
                            { "id": "21aa", "type": "option", "answer": "Pc", "sort": 1 },
                            { "id": "21ba", "type": "option", "answer": "Smartphone", "sort": 2 },
                            { "id": "21ca", "type": "option", "answer": "Tablet", "sort": 3 }
                        ], "sort": 6
                    },
                    { "id": "24", "type": "radio", "answer": "App", "sort": 3 },
                    { "id": "25", "type": "radio", "answer": "Phone", "sort": 4 },
                    { "id": "26", "type": "radio", "answer": "With loyalty card", "sort": 5 }
                ],
                "required": "",
                "sort": 7
            },

            // CHECKBOX ANSWER WITH MULTIPLE CHOICE ( maxChoice: NUMBER OF MAXIMUM SELECTABLE ANSWERS )
            {
                "id": "8",
                "question": "What do you like the most about this edition?",
                "answers": [
                    { "id": "43", "type": "checkbox", "answer": "Clear of communication", "sort": 1 },
                    { "id": "44", "type": "checkbox", "answer": "Simplicity of usage", "sort": 2 },
                    { "id": "45", "type": "checkbox", "answer": "Graphic style and language", "sort": 3 },
                    { "id": "46", "type": "checkbox", "answer": "Duration", "sort": 4 },
                    { "id": "47", "type": "checkbox", "answer": "Content/prizes", "sort": 5 }
                ],
                "checks": "[1,2]",
                "required": "",
                "sort": 9
            },

            // SELECT ANSWER
            {
                "id": "9",
                "question": "How much is important that the loyalty program is funny?",
                "answers": [
                            { "id": "160", "type": "option", "answer": "Not much", "sort": 1 },
                            { "id": "161", "type": "option", "answer": "Medium", "sort": 2 },
                            { "id": "162", "type": "option", "answer": "Much", "sort": 3 }
                ],
                "required": "",
                "sort": 10
            },
            
            // PRIVACY CHECK
            {
                "id": "22",
                "question": "hidden-privacy",
                "answers": [
                            { "id": "44", "type": "radio", "answer": "Yes", "sort": 1 },
                            { "id": "45", "type": "radio", "answer": "No", "sort": 2 }
                ],
                "required": "",
                "sort": 12
            }

        ]
    }
}

Note:
To bind the privacy acceptance field to the survey you can set a question object as shown in the JSON above ( see PRIVACY CHECK ):

  • "question" must be the string "hidden-privacy";
  • include a bit of HTML code ( see below ) inside the div ".surveyjs-container";
    • attribute "data-formjs-question" is mandatory;
    • attribute data-name="bind-surveyjs-answer" is mandatory;
    • attribute data-exclude-storage is optional ( used to avoid saving the answer in local storage );
<div class="surveyjs-question-box" data-formjs-question>
    <div class="surveyjs-answers-box">
        <div class="form-check-inline">
            <input data-exclude-storage data-name="bind-surveyjs-answer"/>
            <label>
                <span></span>
            </label>
        </div>
        <div class="form-check-inline">
            <input data-exclude-storage data-name="bind-surveyjs-answer"/>
            <label>
                <span></span>
            </label>
        </div>
    </div>
</div>

Options

You can initialize the survey with some extra options:

cssClasses

Type: object

JS object with keys:

  • checkbox: ( string ) css class(es) to be applied to checkbox fields
  • default: ( string ) css class(es) to be applied to input fields in general ( if the specific one is not set - eg for checkboxes or radios )
  • file: ( string ) css class(es) to be applied to field with type="file"
  • label: ( string ) css class(es) to be applied to label tags
  • radio: ( string ) css class(es) to be applied to radio fields
  • select: ( string ) css class(es) to be applied to select fields
  • textarea: ( string ) css class(es) to be applied to textarea fields
fieldErrorFeedback

Type: boolean

whether or not to load the field error message inside the HTML ( shown on failed field validation )

fieldErrorMessage *

Type: string

Text to show on generic field error validation.
See below for default messages ( depending on the language ).

fieldErrorMessageMultiChoice *

Type: string

Text to show on error validation of checkboxes with multiple choice.
See below for default messages ( depending on the language ).

fieldOptions

Type: object

JS object with field options passed to isValidField method of Form instance.
See formJS plugin for details.

formOptions

Type: object

JS object with form options passed to the Form instance creation.
See formJS plugin for details.

initAjaxOptions

Type: object

JS object with ajax options passed to the fetch call to retrieve the survey data.

lang

Type: string

language for messages (eg: loading box), select default option etc...
other values: 'it'

loadingBox *

Type: string

HTML container and text for the loading box (used when requesting the survey JSON file).
See below for default messages ( depending on the language ).

maxChoiceText *

Type: string

This text can be used for quesions that let the user choose more than one answer (checkboxes).
See below for default messages ( depending on the language ).

selectFirstOption *

Type: string

Used if an option with empty id is NOT found in the JSON.
See below for default messages ( depending on the language ).

templates.fieldError

Type: string

HTML string to be used as field error container ( when showing an error message on failed field validation ).
See Templates section.

templates.input

Type: string

HTML string to be used for input fields when generating the survey
See Templates section.

templates.inputGroup

Type: string

HTML string to be used for input group answers when generating the survey.
See Bootstrap Input Group

See Templates section.

templates.inputTag

Type: string

HTML string to be used as input tags when generating the survey
See Templates section.

templates.labelTag

Type: string

HTML string to be used for label tags when generating the survey
See Templates section.

templates.question

Type: string

HTML string to be used as question (and answers) container when generating the survey
See Templates section.

templates.select

Type: string

HTML string to be used for select answers when generating the survey
See Templates section.

templates.selectTag

Type: string

HTML string to be used for select tags when generating the survey
See Templates section.

templates.textarea

Type: string

HTML string to be used for textarea answers when generating the survey
See Templates section.

textareaPlaceholder *

Type: string

See below for default messages ( depending on the language ).

useLocalStorage

Type: boolean

Whether or not to use JS local storage to save user answers while compiling the survey.
When reloading the page, JS local storage will be used to polulate the questions with the answers selected/typed by the user.
Note that localStorage for the survey will be cleared on submit success.

Methods

destroy()

Description:

Remove all events listeners from the Survey and Form instances.

init()

Description:

This will retrieve the survey data and initialize the Form instance for it.

Return value:

Promise

Prototype Methods

You can use these methods ONLY from the original Survey object ( not from a new instance - but you can always set each single property/method from the class prototype ):

addLanguage( langString, langObject )

Parameters:

  • langString: a string representing the name of the language to add/override
  • langObject: object with key/value pairs

You MUST pass all the key/value pairs to set a new language.
You can pass only new key/value pairs if you want to override an existing language.

Description:

This will affect ONLY new instances

This can be used to add a new language or override an existing one.

setOptions( options )

Parameters:

  • options: JS object with the fieldOptions/formOptions to override ( see Options section )

Description:

This will affect ONLY new instances

Templates

You can initialize the survey with your custom HTML templates for:

  • Loading Box ( loadingBox: in setup or when adding a new language - see above )
  • Fields Error ( fieldError )
  • Inputs - the type will be taken from JSON ( input, inputTag )
  • Label tags ( labelTag )
  • Input Groups - as of Bootstrap Input Group ( inputGroup )
  • Questions ( question )
  • Selects ( select and selectTag )
  • Textareas ( textarea )

Remember to use ALL the data-* attributes and placeholders with the curly braces, for example {{questionId}}, in your custom code.
Here some examples of initialization with custom templates ( these are, actually, the basic templates ):


Custom Template for Loading Box

{
    url: 'json/survey.json',
    loadingBox: '<div class="surveyjs-loading" data-surveyjs-loading><i class="glyphicon glyphicon-refresh icon-spin"></i> Loading...</div>'
}

Custom Template for Fields Error

{
    url: 'json/survey.json',
    templates: {
        fieldError: '<div class="surveyjs-field-error-message">{{fieldErrorMessage}}</div>'
    }
}

Custom Template for Inputs

{
    url: 'json/survey.json',
    templates: {
        input:  '<div class="surveyjs-single-answer surveyjs-input-container surveyjs-answer-{{answerType}} form-check" data-answer-index="{{answerIndex}}">'+
                    '{{inputTagCode}}'+
                    '{{labelTagCode}}'+
                '</div>'
    }
}

Custom Template for Input Groups

{
    url: 'json/survey.json',
    templates: {
        inputGroup: '<div class="surveyjs-single-answer input-group" data-answer-index="{{answerIndex}}">'+
                        '<div class="input-group-prepend">'+
                            '<div class="input-group-text form-check surveyjs-answer-{{answerType}}">'+
                                '<input type="{{answerType}}" name="surveyjs-answer-{{questionNumber}}" id="{{answerCode}}" data-answer-id="{{answerId}}" value="{{answerIdValue}}" {{attrRequired}} data-require-more="" class="surveyjs-input surveyjs-radio form-check-input" />'+
                                '<label for="{{answerCode}}" class="surveyjs-label form-check-label">{{answerString}}</label>'+
                            '</div>'+
                        '</div>'+
                        '{{relatedAnswerField}}'+
                    '</div>'
    }
}

Custom Template for Input Tags

{
    url: 'json/survey.json',
    templates: {
        inputTag: '<input type="{{answerType}}" {{attrSubtype}} name="surveyjs-answer-{{questionNumber}}{{addMoreName}}" class="surveyjs-input surveyjs-{{answerType}} {{fieldClass}}" id="{{answerCode}}" {{nestedAnswer}} data-answer-root="{{progIdsJoined}}" data-answer-id="{{answerId}}" value="{{answerIdValue}}" {{attrRequired}} {{attrChecks}} {{attrRequiredFrom}} />'
    }
}

Custom Template for Label Tags

{
    url: 'json/survey.json',
    templates: {
        labelTag: '<label for="{{answerCode}}" class="surveyjs-label {{labelClass}}">{{answerString}}</label>'
    }
}

Custom Template for Questions

{
    url: 'json/survey.json',
    templates: {
        question:   '<div data-question-id="{{questionId}}" data-question-index="{{questionNumber}}" data-formjs-question class="surveyjs-question-box clearfix">'+
                        '<div class="surveyjs-question-header">Question {{questionNumber}}</div>'+
                        '<div class="surveyjs-question-body">'+
                            '<div class="surveyjs-question-text">{{questionText}}</div>'+
                            '<div class="surveyjs-answers-box form-group clearfix">'+
                                '{{answersHtml}}'+
                                '{{fieldErrorTemplate}}'+
                            '</div>'+
                        '</div>'+
                    '</div>'
    }
}

Custom Template for Selects

{
    url: 'json/survey.json',
    templates: {
        select: '<div class="surveyjs-single-answer surveyjs-answer-select" data-answer-index="{{answerIndex}}">'+
                    '{{selectTagCode}}'+
                '</div>'
    }
}

Custom Template for Select Tags

{
    url: 'json/survey.json',
    templates: {
        selectTag:  '<select id="{{answerCode}}" name="surveyjs-answer-{{questionNumber}}{{addMoreName}}" class="surveyjs-select {{fieldClass}}" {{attrRequired}} {{nestedAnswer}} data-answer-root="{{progIdsJoined}}" {{attrRequiredFrom}}>'+
                        '{{optionsHtml}}'+
                    '</select>'
    }
}

Custom Template for Textareas

{
    url: 'json/survey.json',
    templates: {
        textarea:   '<div class="surveyjs-single-answer surveyjs-answer-textarea">'+
                        '<textarea id="{{answerCode}}" data-answer-id="{{answerId}}" {{nestedAnswer}} name="surveyjs-answer-{{questionNumber}}" {{attrRequired}} class="surveyjs-textarea {{fieldClass}}" {{answerMaxlength}} rows="6" placeholder="{{answerPlaceholder}}"></textarea>'+
                    '</div>'
    }
}

Changelog

    • Updated dependency: formJS 3.3.2 ( new options are available )
    • Minor improvements
  • 2.0.2
    • Minor code improvements.
  • 2.0.1
    • Project published on NPM.
    • Updated webpack config to export library as UMD Module.
    • Updated dependency: formJS 3.1.2
    • Removed "SurveyJS" as alternative namespace.
  • 2.0.0
    • Rewritten with ES6
    • Added possibility to set "subtype" in JSON file to enable "data-subtype" attribute on input fields and take advantage of formJS custom validations.
    • Added possibility to set "validateIfFilled" in JSON file to enable "data-validate-if-filled" attribute on input fields as per formJS spec.
    • Updated dependency: formJS 3.1.0
    • Bugfix and improvements
  • 1.6.1
    • Updated dependency: formJS 2.3.3
  • 1.6.0
    • Updated dependency: formJS 2.3.2 - Survey validation and submit is now automatically handled via formJS initialization.
    • Updated dependency: Bootstrap 4 CSS.
    • Renamed option "setJSONurl" with "url".
    • Added "useLocalStorage" option in "options".
    • Added "cssClasses" in "options".
    • Added support for "maxlength" ( number ) in JSON file. For textareas, the value will make the plugin add maxlength attribute with the specified value.
    • Changed attribute "data-max-check" with "data-checks" ( "checks" in JSON file ) as for new formJS version.
    • Changed some CSS classes in default HTML templates.
    • Changed some CSS classes related to the survey graphic.
    • Moved all ( or almost all ) the template string options in "templates".
    • Moved "beforeSend", "onSubmitComplete", "onSubmitError", "onSubmitSuccess" in "formOptions" as for formJS plugin.
    • Moved "onValidation" in "fieldOptions" as for formJS plugin.
    • Removed "graphicInput" option in "options".
    • Bugfix & many changes under the hood.
  • 1.5.2
    First stable version ( with formJS v1.2.0 )

Bugs

If you find a bug, please report it here