view on
Github

surveyJS

Javascript Survey Creation & Management. Made Easy

Version


Documentation

Downloads

Download Latest Version
Go To Releases Page

Intro

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

Browsers Support

surveyJS is compatible with ( desktop & mobile versions ):


* 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 Event WeakMap

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

Dependencies

surveyJS is developed with these dependencies:

<!-- BOOTSTRAP CSS -->
<link rel="stylesheet" href="css/bootstrap.css">

<!-- SURVEY CSS -->
<link rel="stylesheet" href="css/survey.css">

<!-- JQUERY -->
<script src="js/vendors/jquery-1.12.4.min.js"></script>

<!-- SURVEY JS ( with WEB_STORAGE and formJS modules included ) -->
<script src="js/surveyJS.min.js"></script>

HTML

This is the basic HTML structure to initialize the survey ( div with class "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:

CSS classes ( like .surveyjs-* ) are optional but used to give styles to the demos.
For custom graphic inputs, using class surveyjs-custom-inputs for 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.

SURVEY.setup({ url: 'json/survey.json' });

JSON File

Example of JSON file for the survey is here.
( image, title and description are optional )

Note that the AJAX call to get the JSON file MUST return it as JSON string or JS/JSON object ( it's the same for the response from survey submit ).

Below the explanation of the JSON:

{
    "result": "OK", // IF "OK" THE SURVEY WILL BE BUILT
    "survey": {
        "id": 0,
        "image": "img/surveyjs-image.png",
        "title": "My Survey",
        "description": "Answer the questions",
        
        // LIST OF ALL QUESTIONS ( AS OBJECTS )
        "questions": [
        
            // RADIO ANSWER
            {
                // EVERY QUESTION HAS ITS OWN DATA
                "id": "1",
                "question": "Hai animali domestici?",
                "answers": [
                    // EACH ANSWER IS AN OBJECT WITH SOME DATA
                    { "id": "1", "type": "radio", "answer": "Sì", "sort": 1 },
                    { "id": "2", "type": "radio", "answer": "No", "sort": 2 }
                ],
                
                // IF required IS 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
            },
            
            
            
            // TEXT INPUT ANSWER NOT MANDATORY
            {
                "id": "1b",
                "question": "Hai animali domestici?",
                "answers": [
                    { "id": "1ba", "type": "text", "answer": "" }
                ]
            },
            
            
            
            // RELATED FIELD ANSWER WITH INPUT TEXT ( SEE attribute FIELD )
            {
                "id": "5",
                "question": "Quanto è importante che il programma fedeltà...?",
                "answers": [
                    { "id": "19", "type": "radio", "answer": "Sia divertente" },
                    { "id": "20", "type": "radio", "answer": "Non richieda molto impegno di partecipazione" },
                    {
                        "id": "200", "type": "radio", "answer": "Altro...",
                        "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": "Quale MODALITÀ DI PARTECIPAZIONE al programma fedeltà preferisci?",
                "answers": [
                    {
                        "id": "21", "type": "radio", "answer": "Sito",
                        "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": "Sito",
                        "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": "Telefonica", "sort": 4 },
                    { "id": "26", "type": "radio", "answer": "Con utilizzo card/tessera", "sort": 5 }
                ],
                "required": "",
                "sort": 7
            },
            
            
            
            // CHECKBOX ANSWER WITH MULTIPLE CHOICE ( maxChoice: NUMBER OF MAXIMUM SELECTABLE ANSWERS )
            {
                "id": "8",
                "question": "Cosa hai gradito di più del programma in corso?",
                "answers": [
                    { "id": "43", "type": "checkbox", "answer": "Chiarezza della comunicazione", "sort": 1 },
                    { "id": "44", "type": "checkbox", "answer": "Facilità e modalità di partecipazione", "sort": 2 },
                    { "id": "45", "type": "checkbox", "answer": "Stile del programma (grafica e linguaggio)", "sort": 3 },
                    { "id": "46", "type": "checkbox", "answer": "Durata del tempo di partecipazione", "sort": 4 },
                    { "id": "47", "type": "checkbox", "answer": "Contenuti/premi proposti", "sort": 5 }
                ],
                "checks": "[1,2]",
                "required": "",
                "sort": 9
            },
            
            
            
            // SELECT ANSWER
            {
                "id": "9",
                "question": "Quanto è importante che il programma fedeltà sia divertente?",
                "answers": [
                            { "id": "160", "type": "option", "answer": "Poco", "sort": 1 },
                            { "id": "161", "type": "option", "answer": "Così così", "sort": 2 },
                            { "id": "162", "type": "option", "answer": "Tanto", "sort": 3 }
                ],
                "required": "",
                "sort": 10
            },
            
            
            
            // PRIVACY CHECK
            {
                "id": "22",
                "question": "hidden-privacy",
                "answers": [
                            { "id": "44", "type": "radio", "answer": "Sì" },
                            { "id": "45", "type": "radio", "answer": "No" }
                ],
                "required": "",
                "sort": 10
            },
        ]
    }
}

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 ):

<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:

name
type
description

cssClasses

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

boolean

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

fieldErrorMessage *

string

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

fieldErrorMessageMultiChoice *

string

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

fieldOptions

object

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

formOptions

object

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

lang

string

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

loadingBox *

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 *

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 ).

onInitComplete( $surveyForm )

function

Fires when the AJAX call ( to get the survey JSON ) ends.

Parameters:

  • $surveyForm: jQuery object of the form

onInitError( jqXHR, textStatus, errorThrown, $surveyForm )

function

Fires when the AJAX call ( to get the survey JSON ) throws an error.

Parameters:

  • jqXHR: as returned by jQuery
  • textStatus: as returned by jQuery
  • errorThrown: as returned by jQuery
  • $surveyForm: jQuery object of the form

onInitSuccess( json, $surveyForm )

function

Fires when all the survey is successfully loaded in the page.

Parameters:

  • json: JSON data returned from AJAX call
  • $surveyForm: jQuery object of the form

selectFirstOption *

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

string

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

templates.input

string

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

templates.inputGroup

string

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

See Templates section.

templates.inputTag

string

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

templates.labelTag

string

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

templates.question

string

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

templates.select

string

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

templates.selectTag

string

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

templates.textarea

string

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

textareaPlaceholder *

string

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

useLocalStorage

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.

Here an example of custom initialization:

SURVEY.setup({
    url: 'json/survey.json',
    onInitSuccess: function( json, $surveyForm ){
        var $surveyBody = $surveyForm.find('.surveyjs-body'),
            initStatus = json.status;

        if( initStatus === 'success' ){
            $('.stackSlider').stackslider({ piles : false });
        } else {
            $surveyForm.find('.surveyjs-footer').remove();
            $surveyBody.html( '<div class="surveyjs-message">Errore durante il caricamento. Per favore, ricarica la pagina.</div>' );
        }
    },
    onInitError: function( jqXHR, textStatus, errorThrown, $surveyForm ){
        $surveyForm.find('.surveyjs-loading').html( '<div class="surveyjs-message">Errore durante il caricamento. Per favore, ricarica la pagina.</div>' );
    },
    fieldOptions: {
        onValidation: function( fields ){
            if( fields.length > 1 ){
                // GO TO THE FIRST UNANSWERED QUESTION
                var $stWrapper = $('.surveyjs-form .st-wrapper'),
                    activeIndex = $stWrapper.find('.st-center').index(),
                    $invalidField = (function(){
                        for( var f=0; f<fields.length; f++ ){
                            var obj = fields[f];
                            if( !obj.result && !$(obj.field).is('[data-name="bind-surveyjs-answer"]') ){
                                return $(obj.field);
                            }
                        }
                        return $();
                    })();
                
                if( $invalidField.length ){
                    
                    var invalidIndex = $invalidField.closest('.st-item').index(),
                        clickDirection = ( activeIndex > invalidIndex ? 'prev' : 'next' ),
                        $btn = ( clickDirection === 'prev' ? $stWrapper.find('nav > span:first-child') : $stWrapper.find('nav > span:last-child') ),
                        clicksLength = ( clickDirection === 'prev' ? activeIndex - invalidIndex : invalidIndex - activeIndex );

                    for(var i=0; i<clicksLength; i++){
                        $btn.trigger('mousedown.stackslider');
                        $btn.trigger('mouseup.stackslider');
                    }
                    
                }
            } else {
                console.log('validating field ', fields[0]);
            }
        }
    },
    formOptions: {
        beforeSend: function( data ){
            console.log('Survey formOptions.beforeSend call...', data);

            if( !data.stopExecution ){
                //$surveyForm.find('[type="submit"]').button('loading');
                $surveyCont.find('.alert').remove();
                data.formData = 'surveyJSON=' + JSON.stringify( data.formData );
            }

            return data;
        },
        onSubmitComplete: function( ajaxData ){
            console.log('formOptions.onSubmitComplete', ajaxData);
            //$surveyForm.find('[type="submit"]').button('reset');
        },
        onSubmitError: function( ajaxData ){
            console.log('Survey formOptions.onSubmitError', ajaxData);
            // PRINT THE ERROR MESSAGE AFTER THE FORM
            $surveyForm.closest('.surveyjs-container').append( '<div class="alert alert-danger text-center" role="alert"><p>Generic error, please retry.</p></div>' );
        },
        onSubmitSuccess: function( ajaxData ){
            console.log('Survey formOptions.onSubmitSuccess', ajaxData);
            if( typeof ajaxData === 'string' ){
                try{
                    ajaxData = JSON.parse(ajaxData);
                } catch(error){
                    throw new Error('String returned from ajax call to "' + ajaxOptions.url + '" is not a valid JSON string');
                }
            }

            if( $.isPlainObject( ajaxData ) ){
                if( ajaxData.data.status === 'success' ){
                    // REMOVE THE SURVEY FORM FROM THE PAGE AND PRINT A RESPONSE MESSAGE
                    $surveyForm.parent().html( '<div class="alert alert-success text-center" role="alert"><p>Great! You completed the mission.</p></div>' );

                    // OPEN THE BOOTSTRAP MODAL TO SHOW A CONGRATULATION MESSAGE
                    $('#modal-notification').modal('show');
                } else {
                    // PRINT THE ERROR MESSAGE AFTER THE FORM
                    $surveyForm.closest('.surveyjs-container').append( '<div class="alert alert-danger text-center" role="alert"><p>Generic error, please retry.</p></div>' );
                }
            }
        }
    }
});

Localization

You can add a new language ( or override an existing one ), with SURVEY.addLanguage( langString, langObject ).
This will also set the language used for the survey.

You MUST pass all the key/value pairs as shown below:

var newLang = {
        fieldErrorMessage:  'Du musst antworten',
        fieldErrorMessageMultiChoice:  'Sie Können zwischen 1 und 3 Antworten wählen.',
        loadingBox:         '<div class="surveyjs-loading" data-surveyjs-loading><i class="glyphicon glyphicon-refresh icon-spin"></i> Wird geladen...</div>',
        maxChoiceText:      'ANTWORTEN MAX',
        selectFirstOption:  'Wähle eine Antwort...',
        textareaPlaceholder:'Schreibe eine Antwort...'
    };

SURVEY.addLanguage( 'de', newLang );

Methods

name
parameters
description
addLanguage( langString, langObject )
  • langString
  • langObject

See Localization section.

setup( options )
  • options

See Options section for details and default values.

Templates

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

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

SURVEY.setup({
    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

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

Custom Template for Inputs

SURVEY.setup({
    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

SURVEY.setup({
    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

SURVEY.setup({
    url: 'json/survey.json',
    templates: {
        inputTag: '<input type="{{answerType}}" 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

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

Custom Template for Questions

SURVEY.setup({
    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

SURVEY.setup({
    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

SURVEY.setup({
    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

SURVEY.setup({
    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

Bugs

If you find a bug, please report it here