question-tree-core
v1.1.3
Published
Tools to navigate a graph of questions.
Downloads
44
Readme
Question Graph
Navigate conditional paths through a json formatted graph of questions. For use with a web application.
React UI example w/this package
Older working example on Github Pages
Overview of the Pieces
- Question Graph json - defines a base path (order) of questions and grouping by modules.
- Question definitions json - defines the questions themselves as well as optional rules about navigation order.
- index.js - provides information to your js application such as what's the next or previous question.
- App/UI files (Not included) - Uses the above to help create the appearance and behavior of your Quiz.
Usage
A barebones example of a React UI using the question-tree-core
package.
import React, { useEffect, useState } from 'react';
import DecisionTree from 'question-tree-core';
const ExampleUIComponent = () => {
const [decisionTreeInitializing, setDecisionTreeInitializing] = useState();
const [decisionTreeInitialized, setDecisionTreeInitialized] = useState();
const [currentQuestion, setCurrentQuestion] = useState();
const [currentAnswerId, setCurrentAnswerId] = useState();
// fetch your graph files (only once)
useEffect(() => {
if(!decisionTreeInitializing) {
const p = DecisionTree.fetch({
graph_path:'/data/graph/some_file',
question_set_path:'/data/questions/some_file'
});
setDecisionTreeInitializing(true);
p && p.then(setDecisionTreeInitialized);
}
},[decisionTreeInitializing]);
// optionally do something once your graph files have been loaded
useEffect(() => {
if(decisionTreeInitialized) {
console.log("DecisionTree Initialized!!");
}
},[decisionTreeInitialized]);
// pass the answer id to the 'next' method so that the decisionTree
// can conditionally route users through the question graph
const handleNextClick = () => {
setCurrentQuestion(DecisionTree.next({ labelIdx: currentAnswerId }));
};
const handlePrevClick = () => setCurrentQuestion(DecisionTree.prev());
const handleInputChange = e => setCurrentAnswerId(e.target.id);
return (
<div className="graph-ui">
{!currentQuestion &&
<div style={{padding:".5rem"}}>Introduction...</div>
}
{currentQuestion &&
<div style={{padding:".5rem"}}>
<p>{currentQuestion.title}</p>
<p>{currentQuestion.id}</p>
{currentQuestion.labels
&& currentQuestion.labels.map(
item => (
<label key={item.qid}>{item.title}
<input type="radio" id={item.qid} onChange={handleInputChange}/>
</label>
)
)}
</div>
}
<button onClick={handlePrevClick}>Prev</button>
<button onClick={handleNextClick}>Next</button>
</div>
);
}
export default ExampleUIComponent;
Graph and Question Json Formatting examples
Example Graph File
{
"meta":{
"graph_id":"1"
},
"module_plantClassification":{
"title":"Plant Classification",
"questions": [
{"id":"plantClassification_1"}
],
"next":"module_plantId"
},
"module_plantId":{
"title":"Plant Identification",
"questions": [
{"id":"plantId_1", "next":"plantId_2"},
{"id":"plantId_2", "next":"plantId_3"},
{"id":"plantId_3"}
],
"next":"module_final"
},
"module_final":{
"title":"Wrapping Up",
"questions": [
{ "id":"survey_results"}
]
}
}
There are a few things to note about the above. First - the questions are grouped by 'modules' - for example there are three defined above: 'plantClassification', 'plantId' and 'final'. This lets authors logically group questions and optionally define paths between modules with the 'next' property. By default the decisionTree will move through modules in order so strictly speaking the module level 'next' properties above are not needed. Next - each module contains a list of question id's which correspond to definitions in the Questions json. Modules and questions in the Graph file define a sequential Base Path without branching. Lastly the 'meta' section above will be ignored by the decisionTree when determining order of questions - it's just a place to keep info about the graph itself for example versioning.
Example Questions File
{
"plantClassification_01":{
"title":"What genus does this plant most likely belong to?",
"media": [
{"type":"image", "src":"/assets/img/a_celsii.jpg"}
],
"labels":[
{"title":"Aloe", "qid":"100", "next":"plantClassification_11"},
{"title":"Agave", "qid":"101"},
{"title":"Sempervivum", "qid":"102", "next":"plantClassification_13"},
{"title":"Gasteria", "qid":"103", "next":"plantClassification_14"},
{"title":"Echeveria", "qid":"104", "next":"plantClassification_12"},
{"title":"Haworthia", "qid":"105", "next":"plantClassification_15"}
],
"actual":"101",
"category":"quiz",
"criterion":"agave",
"type":"radio"
},
"favColors_85":{
"title":"What colors look good in the kitchen",
"labels":[
{"title":"Blue", "qid":"5"},
{"title":"Green", "qid":"6"},
{"title":"Yellow", "qid":"7"},
{"title":"Red", "qid":"8"}
],
"category":"survey",
"criterion":"kitchen",
"type":"checkbox"
}
}
Note how the 'plantClassification_01' has been defined above. The 'labels' property is an array of options associated with a question. Because this question node is categorized as a 'quiz' there is a correct answer specified with the 'actual' property. The 'type' property indicates that the options should be rendered as radio button controls. The 'criterion' property acts as a metadata tag for use by any application logic needing to summarize user responses. The 'media' property is an array of images etc that may be displayed in the UI.
A deceivingly simple looking property 'next' on label members is how branching can be defined between questions. More specifically conditional paths can be created which will either add to or substract from the Base Path graph length. For example question nodes can be added by specifying a 'next' property which points at a question not defined in the Base Path. In other words from the example above the option {"title":"Sempervivum", "qid":"102", "next":"plantClassification_13"}
will add add the 'plantClassification_13' node to the stack of questions visible to the user (assuming it's defined). Upon completion, unless 'plantClassification_13' itself defines a next question, the user will be returned to the Base Path which in the case of the graph file example would be 'plantId_1' (since its the first question in the next module). Similarly Shortcut Paths can be created by setting a question's 'next' value equal to a node on the Base Path more than a single hop away. For example jumping from 'plantId_1' directly to 'plantId_3'. There are more variations on conditional paths which will be covered later.
Finally any question definitions like 'favColors_85' not referenced in the Base Path or directly from other questions will be ignored.
Graph and Questions Helper Files
These files are used by the decisionTree logic to help with tasks like determining which is the current question node and what comes next etc. They are not available to the UI (but still worth mentioning). The Graph and Questions files also handle fetching your json files (via the HTML5 fetch api) and storing the results locally. By design only the decisionTree uses these files, not the view logic which does not need to know about the inner workings of path branching. In general the Graph.js file provides info specific to the graph json while Questions.js does the same for the questions json.
index (DecisionTree)
This file combines information about the graph and questions json to determine a path through the question set. It's instantiated automatically as a Singleton and used by the View logic to determine the 'next' and 'previous' question nodes. The decisionTree logic simply guides the way from the first question to the last it does not try to do other tasks like persisting user answers.
View Logic
Use your own presentation files to create a quiz or survey UI and import 'question-tree-core' to help navigate your project's graph of questions. Three question tree methods are available to your presentation files: fetch()
, next()
and prev()
;
fetch
accepts 2 arguments - the path to a graph json file and the path to a questions json file. returns a promise representing the fetch calls made to get the json files. Once the 'promise' is resolved the UI can start using the decision tree methods.
next
accepts 1 optional argument - an object representing the id of the user selected answer to the current question (as defined in the questions json file). object arg formatted like {labelIdx: 105}
If no answer argument is supplied then the method returns the next question as defined in the Graph json. If an answer argument is supplied then the Questions json file will be examined for conditional paths and a corresponding question returned.
prev
accepts 0 args - examines internal history and returns last question.
Both next()
and prev()
return the full question object which can be used by the UI logic for display.
Path Branching Overview
Below is a listing of path type examples from simple to more complex. The term Graph Length refers to the total number of nodes encountered by a user from start to finish during quiz or survey. It can include from one to many paths e.g. a simple, sequential path or a path which includes several conditional branches.
Base Path is the sequential route through nodes in a graph which avoids any conditional paths. The diagram below has a Graph Length of 4 nodes. This simplest type of path is defined in the Graph json file.
Conditional Path - an optional, offshoot path that exposes users to extra questions or allows skipping questions in the Base Path. Conditional paths are defined with the 'next' property in the Questions json file.
Detour Path - a type of Conditional Path which exposes users to additional questions. In the diagram below if a user took the optional detour branch then the Graph Length is 4 nodes else the Graph Length would be 3 nodes.
Shortcut Path - a type of Conditional Path that allows users to bypass questions on the Base Path. In the diagram below the Graph Length is only 2 nodes if the shortcut is used.
Multi-Node Path - path containing more than one node. Almost all Sequential paths will be multi-node. Conditional Paths might often contain just a single node. The diagram below shows a multi-node detour path with a graph length of 5 nodes.
Multi-Branch Path - node from which multiple, conditional paths are available to choose from.
Compound Conditional Path - A Conditional Path with nested, conditional paths.
Mixed Conditional Path - A conditional path that contains both detour and shortcut paths. In the diagram below 'path 1' represents a Shortcut Path with a Graph Length of 3, while 'path 2' represents a Detour Path with a Graph Length of 5.