Custom Code Logic

This article will show you how to add Custom Node.js Script to your survey as well as note which functions are available. All the examples provided are in ES5 syntax, but you can freely use ES6 as well.


Background

The Custom Node.js Script allows you to write JavaScript ES6 code to work with datapoints in your survey.  It allows more flexibility to perform simple or complex tasks when working with respondent data.  For example, you can easily take data from one grid and set those values into another grid, call an external API and then process the response, use a third-party node library for some statistical processing, etc.


Getting Started

  1. Click on Add Logic or Quota Page button.
  2. Select Custom JS Script and click on Add.
  3. Select your new Custom Logic page and click on </> Add Javascript.

Built-in objects available

The following objects are available in the custom Node.js code and provide convenience functions for certain actions.

  • survey
    • .id() - returns the current survey's ID.
  • respondent
    • .id() - returns the current respondent's ID.
    • .data(dataPointName) - gets the given data point value and returns it.
    • .data(dataPointName, value) - sets the given data point to a given value.
    • page(pageNumber) - sets the respondent's current page. Can be used to skip to a different page in the survey.
    • termCode(termCodeName) - terminates the respondent with the given termination code name.

Getting and Setting Respondent Data

You can now access respondent data via the respondent.data() function. It accepts up to 2 parameters and both parameters are optional:
 respondent.data(dataPointName, value);
dataPointName =- If this parameter is given, only this datapoint will be returned.
value - If this parameter is given in combination with a dataPointName, you can update the given respondent variable.
Here's a simple example.  In the code below, it will read the value of Q1 and then it will update it to a value of 2.
var q1 = respondent.data('Q1');
// q1 now contains the value of Q1.

respondent.data('Q1', 2);
// Q1 was now updated to the response with a value of 2
    
// Signal to the custom code engine that it should return control to the survey.
done();
You can set any type of datapoint using this format:
  • Single and multi-response type questions and variables .
  • Question specify values

The only exception is that you cannot override system variables:

  • RESP_TOKEN
  • LOCALE
  • SAMPLE_SOURCE
  • DEVICE_TYPE
  • IS_MOBILE
  • PLATFORM
You can also get all respondent data just by calling respondent.data(), with no parameters. This returns an object with the datapoint names as keys. Only datapoints that the respondent has so far are actually set. All other datapoints in the survey will be undefined.
For example, calling respondent.data() after the respondent has answered Q1 and Q2 on a survey, and a variable called VAR_COUNTER was set via logic, will return:
var allData = respondent.data();

done();

allData would be set to:

{
  "Q1": 1,
  "Q2_1": "I love this brand",
  "Q2_2": "I think it's great",
  "LOCALE": 1,
  "SAMPLE_SOURCE": 1,
  "IS_MOBILE": 2,
  "DEVICE_TYPE": 2,
  "PLATFORM": 14,
  "RESP_TOKEN": "RGF123LA",
  "VAR_COUNTER": 1
}
This is useful for iterating over all data if you don't know exactly what you are looking for, or even for logging purposes.
NOTES:
  • Keep in mind that you won't have access to the META accessor :TEXT, as survey content is not available via the custom engine.
  • You can't clear datapoints via the custom engine currently, only get/update their values.
  • Values for multi-response questions/variables are fetched individually. If you have a text question called Q2 with two responses, the only way to access their responses is via the separate datapoints Q2_1 and Q2_2. Calling the data for Q2 will not return any values.

Terminating the respondent

  You can set a respondent as terminated by setting their termination code via the function respondent.termCode(terminationCodeName).
respondent.termCode('DOES_NOT_QUALIFY');
// After end of execution of the custom script, the respondent will be terminated with term code DOES_NOT_QUALIFY.

done();
You can update any respondent data and even change to a different termination code as long as the script hasn't finished running.
NOTE: You can't set someone to a quota termination code, like QUOTA_INACTIVITY, via this method since they require an actual related quota.

Skipping to a page

After custom logic execution, the next page on the flow will be shown to the respondent. You can alter this behavior and send the respondent to a different page by calling the respondent.page(pageNumber) function.
respondent.page(31);
// After the end of execution of this custom script, the respondent will be redirected to page 31.

done();
As with the termination code, you can update any respondent data and even change to a different page as long as the script hasn't finished running.
IMPORTANT NOTE:  If you set both a termination code and a skip to a page, only the termination will be processed, as the respondent will be terminated before being redirected to the new page.

Returning from your custom script

Since you can have asynchronous behavior in your JavaScript code, like calling different external APIs or services, you must signal that your script is done executing. Your function must always include the done() function once you want to return control to the survey engine. Failure to do so will return in a timeout and a related error.

Throwing errors

While you are writing your own survey, it might be useful to see what's going on inside your function. You can throw your own errors by doing: 
throw new Error('Something I want to log here');
Or by calling done() with an error:
done(new Error('Something I want to log here'));

You will encounter the error and its content while running through your survey on test mode.


Calling an API

When you want to call an external API, for instance to automatically issue respondent rewards, or to analyze data via a third party service, you can use the request function. This is a wrapper around the NPM request library (https://www.npmjs.com/package/request) and should provide the same functionality. Furthermore, you can use the request function with either a callback, or a promise.

Callback example:

request({
  url: 'https://fakesite.cmix.com',
  method: 'POST',
  json: true,
  body: {
    respondentId: respondent.id()
}, }, function (error, response, body) { if (!error) { // Do something. done(); } else { done(new Exception('Something I want to log here: ' + error)); } });

Promise example:

// Defaults to a GET request if no options are passed.
request('https://fakesite.cmix.com')
  .then(function (body) {
    // Do something.
    done();
  })
  .catch(done);

IMPORTANT NOTE: To prevent abuse, external APIs have to be whitelisted. If you want to add a domain to our whitelist, contact us with the details for your use case. All CMIX APIs are whitelisted by default.


Using a third party Node.js library

External Node.js libraries are supported and are added on-demand to properly vet them. Currently the following libraries are available:

var distributions = require('distributions');
var normal = distributions.Normal(1 /* mean */, 2 /* std deviation */);
done();
// Make two requests simultaneously and wait for both to be done before returning.
Promise
.all([
request('https://fakesite.cmix.com'), request('https://fakesite2.cmix.com')
])
.then(done)
.catch(done);


Other Examples:

// Copy data over from grid radio question Q1 to grid radio question Q2. Both questions have 5 radio sub-questions 
var q1Data; 
for (var i = 1; i <= 5; i++) {
  q1Data = respondent.data('Q1_' + i);
  respondent.data('Q2_' + i, q1Data);
} 

done();
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.