quaire
TypeScript icon, indicating that this package has built-in type declarations

0.1.0 • Public • Published

quaire

a framework-agnostic library to create user-flows, surveys, and questionnaires

Why the name?

Because I needed one and Questionnaire is terrible to type. Let me know if you have a better one!

What use-case does it try to solve?

I have to implement more and more features that behave like questionnaires or surveys. Over the years I tried a couple of things to make it easier for me to implement those features.

Of course, the first approach I tried was to implement a static user flow, for example:

  • present first question on first page
  • user selects option a
  • navigate to the next question
  • etc.

That was ok as long as I didn't have to change the flow but when I had to change the flow, I found myself changing big parts of the implementation.

I also tried to use finite state machines, but I ended up with spaghetti state machines most of the time due to regular changes in the user-flow (re-arranging questions, adding questions, skip questions, etc.)

The solution I found to work best for me was a concept of game-development called decision-tree. That way the whole user-flow is based on a static data structure that can be changed without the need of changing the view or the behaviour. This pattern provides a nice separation of:

  • data
  • behaviour
  • presentation

Features?

How does it work?

Installation

npm i --save quaire

Define quaire data

First you need to define the data (decision tree) based on the QuaireItem interface. This can be static data in a JS/TS file, a JSON file that you load on demand or a dynamic JSON from a CMS or backend API.

The structure for questions looks as follows:

import { QuaireComponentType, QuaireItem, QuaireNavigationItem } from 'quaire';

export const items: QuaireItem[] = [
  {
    id: 1,
    resultProperty: 'foo', // property that is used to save the answer
    navigationItemId: 1, // assiciation with the navigation entry (optional)
    dependsOnResultProperties: [], // dependencies on answers from former questions, based on the result property - not questions IDs
    componentType: QuaireComponentType.SINGLE_SELECT, // component type as indication for custom presentation logic
    question: 'Question 1',
    description: 'Description 1',
    required: true,
    selectOptions: [], // options for select components (optional)
    rangeOption: {}, // option for range components (optional)
    inputOption: {}, // option for input components (optional)
    defaultValue: {}, // default value for any kind of component (optional)
    nextItemId: {}, // id of the follow up question (optional), usually you want to define this in selectOptions, rangeOption or inputOption
  },
  // ...
];

The structure for the navigation looks a follows:

export const navigationItems: QuaireNavigationItem[] = [
  {
    id: 1,
    parentId: null, // has no parent
    name: 'Category 1',
  },
  {
    id: 2,
    parentId: 1, // has a parent (works only for one level)
    name: 'Subcategory 1',
  },
  {
    id: 3,
    parentId: null,
    name: 'Category 2',
  },
];

Use quaire behaviour

To use the default behavior you need to initialize Quaire with the data you defined in the former step.

import { Quaire } from 'quaire';

const q = new Quaire({ items, navigationItems }); // (optional) you can pass an existing result to restore the questionnaire

Next you can get the first active Question and display it in any way you want

let activeQuestion = q.getActiveQuestion(); // first question
let navigation = q.getNavigation(); // initial navigation
let result = q.getResult(); // initial result

// display activeQuestion.question and the related component presentation logic
// Vue.js pseudo code example
<template>
    <div>
        <template v-if="activeQuestion.componentType === 'SINGLE_SELECT'">
            {{ activeQuestion.question }}
            // loop through activeQuestion.selectOptions, etc.
        <template>
    </div>
</template>

After the user selected an answer you can save the answer and get the next question. It's also a good idea to update the navigation and result

onSubmit(value: any) {
    q.saveAnswer(value);
    activeQuestion = q.getActiveQuestion(); // follow up question
    navigation = q.getNavigation(); // update navigation
    result = q.getResult(); // update result

    // ...
}

You need to identify the end of the user-flow on your own. One way to do it is via the question ID or you create some logic around the result object of the Questionnaire.

onSubmit(value: any) {
    // ...
    const isValid = q.isValid();

    // check if the questionnaire is valid
    if(!isValid) {
        return;
    }

    // via ID
    if(activeQuestion.id === 3) {
        // persist result to the backend, redirect to another page,
        // whatever you want after the questionnaire is filled out
    }

    // via result
    if(result.foo && result.bar && result.baz) {
        // persist result to the backend, redirect to another page,
        // whatever you want after the questionnaire is filled out
    }
}

Extend quaire

Examples

Contribute

Contributions are always welcome! Please read the contribution guidelines first.

Contact

License

MIT

Dependencies (1)

Dev Dependencies (18)

Package Sidebar

Install

npm i quaire

Weekly Downloads

23

Version

0.1.0

License

MIT

Unpacked Size

101 kB

Total Files

40

Last publish

Collaborators

  • devcrossnet