Advanced Survey Features

  • Relevance Expressions: These expressions are the most commonly used advanced feature. They allow the user to hide questions based on the responses to previous questions. Relevance expressions are simple javascript expressions and can be programmed to have flexible AND/OR logic. For more information about relevance or to find example expressions, more details are below.
  • Initialization: This feature allows survey builders to create code that will pre-populate a question based on the answer to a previous question.
  • API Compose: Uses javascript to allow you to send the survey, in it's entirety or in some modified form, to any publicly available API or to a non-public API which has been added to the SurveyStack Integrations. In short - you can send your data where you want. For example, we use API compose expressions to push survey data into farmers FarmOS accounts in the Bionutrient Institute.

Note: When you code a relevance, initialize, or apiCompose script you do not have access to any javascript libraries! You can only use the basic logic of javascript code plus a few special functions created to help you. These functions will be further described in the examples below.

Finding Advanced Features

The Advanced features are available in the survey builder, below the Required and Private checkboxes.

image

Once a user has selected advanced options, additional panes appear: one for coding expressions, a console log, and a Shared Code tab. The Shared Code tab shows EXACTLY the format of the survey submission and it is updated in real time as you fill out a survey on the far right pane. The survey submission is a JSON, and items in it are accessible in your code. Each question and its outputs can be found in the Shared Code pane. You will use this reference a lot when using Advanced options so get familiar with the submission structure!

A note on terminology - if you have not coded before you may not be familiar with the term JSON, and that’s ok! In this case a JSON is a data format used to store surveys and submissions. For the purposes of creating surveys with basic logic you will not need to be proficient in javascript, but if you want to learn more W3 schools and Codeacademy are great places to start!

image

Relevance

This feature allows survey creators to deploy logic expressions to only show users the questions that are relevant to them. Relevance expressions use javascript and are very flexible. Below are some examples of the most common use cases and associated relevance expressions.

We are going to use basic javascript if statements, as well as some special functions "checkIfAny" and "checkIfNone". checkIfAny and checkIfNone are smarter because they not only check if an answer is there (or not), but they also confirm that the previous question was relevant. This is important if you are making a dependency on a question which is either optional (and can be skipped) or itself has a dependency, and therefore may be unanswered or answered but irrelevant. In addition, they check both single answer AND multiple answer question types. Therefore in general, it is safest to use checkIfAny and checkIfNone when possible.

Use Case 1: Show a question or group of questions based on the PRESENCE of a specific answer from multi-select questions OR single-answer question that is not required (so the answer may be empty or null).

function relevance(submission,survey,parent) {
if (utils.checkIfAny(parent.activity, 'planted')) {
        return true
    }
return false
}

Where activity is the data_name of the question being referenced and "planted" is the Value of the answer. You can use parent as a substitute for the full data name as long as the question you are referencing is in the same group as the question you are creating the relevance expression for. Otherwise you will need to write out the full data name starting with submission.data.This question or group of questions will appear if any of the answers to the activity question is "planted." Here we are use a special utils function which checks if any of the listed items are, in this case just 'planted', are present in the answer to the question 'activity'.

Use Case 2: Show a question or group of questions on the ABSENCE of a specific answer from multi-select questions OR single-answer question that is not required (so the answer may be empty or null).

function relevance(submission,survey,parent) {
if (utils.checkIfNone(parent.activity, 'planted')) {
        return true
    }
return false
}

Where activity is the data_name of the question being referenced and "planted" is the Value of the answer. This question or group of questions will appear BOTH if the 'activity' question is answered AND none of the answers provided are "planted." Using this utils function, you could add additional options besides 'planted' and it would only return true if none of the listed options were included in the 'activity' answer.

Use Case 3: Show a question or group of questions if a previous question is itself relevant (even if it's unanswered).

function relevance(submission,survey,parent) {
    if (utils.checkIfAny(parent.containers)) {
        return true
    }
return false
}

CheckIfAny, when provided no additional parameters, returns true if the question itself is relevant.

Use Case 4: Show a question or group of questions if the number answer to a previous question is <, >, ===, or !== to a specific value.

function relevance(submission,survey,parent) {
    if (parent.containers.value > 2) {
        return true
    }
return false
}

This question or group of questions will appear if the answer to the containers question is greater than 2.

Adding AND/OR statements to relevance expressions

Using OR with CheckIfAny or CheckIfNone statements

function relevance(submission,survey,parent) {
     if (utils.checkIfAny(parent.activity, 'planted', 'weeded')) {
        return true 
     }
return false
}

The question will be relevant if the user answers either "planted" OR "weeded"

Using AND with to CheckIfAny or CheckIfNone statements

function relevance(submission,survey,parent) { 
     if (utils.checkIfAny(parent.activity, 'weeded') && utils.checkIfAny(parent.activity, 'weeded')) {  
        return true 
     }
return false
}

The question will be relevant if the user answers "weeded" AND "watered"

Initialization

The initilization feature allows survey builders to create code that will pre-populate a question based on the answer to a previous question. Some of the applications include:

  • Prepopulate a column in a matrix based on a previous checklist question
  • Prepopulate a map question location or area based on an API
  • Prepopulate a farmOS field name in a matrix question based on the field specified at the top (participating fields for example)
  • Prepopulate a number score based on some calculation from previous questions
  • Prepopulate text based on a unique combination of things - for example, auto-generate field names for people so they don't have to make them themselves.

Use Case 1: Prepopulate a column in a matrix based on a previous checklist question. This example is taken from the Regen1 onboarding survey.

let exclude = ['shallow','less_frequent','equipment','maintain_residue','disk','cultivator'];

function initialize(submission, survey, parent) {
  let practices = utils.getCleanArray(parent.details);
  practices = practices.filter(item => !exclude.includes(item));  // remove practices we don't want... 
  let populated_matrix = [];
  practices.forEach((practice) => populated_matrix.push({ acres: { value: null}, practice: {value: [practice]}, verification: { value: null } }));
  console.log('populated_matrix');
  console.log(JSON.stringify(populated_matrix));

  return populated_matrix;
}

Where details is the data name of the question being referenced and practices is an array variable being created to store the selected answer values from the reference question. Acres/practice/verification are the data names of the columns in the matrix question we want to push to (this should be created before writing your initialization code). In this case we want to pre-populate only the practice column in the matrix so we will push null to the other columns so the user can fill them in themselves.

Use Case 2: Prepopulate a column in a matrix based on an online resource. Note that you need to access something which does not have CORS restrictions. Files in gitlab are CORS restricted, however, artifacts or outputs to gitlab pages are not.

async function initialize(submission, survey, parent) {

    let url = 'https://our-sci.gitlab.io/-/software/json_schema_distribution/-/jobs/4747806888/artifacts/collection/conventions/log--activity--flaming/schema.json';

    const data = await fetch(url).then(function (response) {
       return response.text();
   });

    let jsonData = JSON.parse(data);

    let objects = Object.keys(jsonData.properties)
    let descriptions = objects.map((a) => jsonData.properties[a].description);

    let output = [];
    objects.forEach((key, index) => {
      output.push({
        issues: {
          value: [`${key}`]
        },
        description: {
          value: `${descriptions[index]}`
        }
      })
    })

    return output
}

Use Case 3: Prepopulate a text question with a script output.

function initialize(submission, survey, parent) {
  let val = JSON.stringify(parent.script_1.value);
  return [val];
}