README
Todo Package
Provides a mechanism for building a todo and all of its related structures given a todo id. The resulting data structures hold a variety of information that can be used for managing the todo in a UI or to make any updates to the system
- Important concepts
- Publishing to npm
- Installation / setup
- Usage
- Local development with Everest client
Important concepts
For the sake of simplifying naming in this library, every reference to a Todo
is actually intended to represent a TodoAssignment
. So when passing the "todo id" to the "todo loader", really a todo assignment id should be passed.
Publishing to npm
This repo is manually published to npm using Semantic Versioning guidelines.
For example: major.minor.patch
Incrementing +0.0.1 would be a bug. +0.1.0 would be a minor upgrade, +1.0.0 would be major upgrade
- First update the version in the package.json
- Then, update your local
main
branch - Run
npm publish
Installation / setup
In order to load a todo with all of its information, it's necessary to have access to a TodoLoader
. This can either be loaded as a standalone class or built out of the NestJS dependency injection container.
yarn add @endpoint/todo
Standalone
When using the TodoLoader
outside of a NestJS project, the TodoLoader
instance can be built from the TodoLoaderFactory
:
import { TodoLoaderFactory } from '@endpoint/todo';
const apolloClient: ApolloClient<object> = useApolloClient(); // in a react app
const todoLoader = TodoLoaderFactory.build(apolloClient);
Usage
Todo
The The Todo
structure represents a todo as it currently exists in the system. It contains various top-level information and a pointer to the current TodoStep
.
Loading a todo
A Todo
structure can be built by passing a todo's id to the load
method of the TodoLoader
.
yarn add @endpoint/todo
import { Todo, TodoLoader } from '@endpoint/todo';
//...
const todoLoader = TodoLoaderFactory.build(apolloClient);
const todo: Todo = await todoLoader.load('some-todo-assignment-id');
Traversing the steps of a todo
Current step
It is frequently necessary to determine which step a user should currently be working on in a todo. For this you can use the currentStep
property:
import { TodoStep } from '@endpoint/todo';
//...
const step: TodoStep = todo.currentStep;
Moving to the next step
At any point a user may wish to move on to the next step. This may be after they've completed the current step, or if they choose to skip to the next section of the todo.
if (todo.canMoveToNextStep()) {
todo.moveToNextStep();
todo.currentStep; //is now the next step
}
The behavior of moveToNextStep()
is determined by the configuration of the todo sections and the completion status of the current step. If a user has not completed the current step, an attempt will be made to move the user on to the subsequent section, skipping over the remaining uncompleted steps of the current section. If a user has completed the current step, moving to the next step will either bring up the next uncompleted step in the current section or move the user on to the next section if there are no remaining steps in the current section.
Going back to the previous step
The previous step is defined as the last step that a user saw.
if (todo.canMoveToPreviousStep()) {
todo.moveToPreviousStep();
todo.currentStep; //is now the previous step
}
Moving back to a specific step
If a user wishes to edit their answer to a step, it's possible to change the currentStep
pointer to a specific step:
if (todo.canMoveToStep(step)) {
todo.moveToStep(step);
}
Listening for changes to the current step
If it's necessary to listen for when the current step changes, a listener can be attached to the todo's step.change
event.
todo.on('step.change', (newCurrentStep) => {
//render new step
});
Checking if a todo has finished all steps
At any point it may be useful to check if a todo has any more steps to complete:
if (todo.hasFinishedAllSteps()) {
//enable the "submit" button on the confirmation page
}
TodoStep
The A TodoStep
is the atomic unit of action that a user must complete as defined by the TodoStepPrimitive
. A collection of these represent a linear pathway through a todo section.
TodoStepPrimitive
The The TodoStepPrimitive
is a representation of the primitive type of the step. This can be anything from a simple acknowledgement to a flow for gathering financial information.
import { TodoStepPrimitive } from '@endpoint/todo';
//...
const primitive: TodoStepPrimitive = todo.currentStep.primitive;
primitive.name; //ex: acknowledgement
primitive.options; //unique to each primitive
Answering a step
When a todo step primitive's corresponding UI component receives an answer from the user, it can call the setAnswer
method on the TodoStep
:
try {
step.setAnswer({ foo: true });
} catch (error) {
//the answer schema is invalid
}
Once a valid answer is provided, it's possible to move on to the next step:
if (todo.canMoveToNextStep()) {
todo.moveToNextStep();
todo.currentStep; //is now the next step
}
It's also possible to get the value of a step's answer using the answer
property:
step.answer; //schema is unique to each primitive
Removing an answer
It might be necessary to change a step's answer. This can be done by calling the unsetAnswer
method on the step:
step.unsetAnswer();
Layouts
Each TodoStep
has information about the expected layout of the step as it should be presented to the user. These are defined for each todo in their configuration files. The layout of a step can be accessed from the layout
property:
import { TodoStepLayout } from '@endpoint/todo';
//...
const layout: TodoStepLayout = todo.currentStep.layout;
A layout is built out of a sequence of blocks, which are provided in the order that they are expected to be shown. Each block has a name
that describes how it should be laid out in a UI and the options
that were provided to build it:
for (const block of layout.blocks) {
block.name; //ex: title
block.options; //ex: {value: "The todo's title"}
}
Working with data points
Todos are frequently used to gather data from a user. Each todo configuration has the ability to specify a dataKey
which marks it as a piece of data that should be lifted to the surface level of the todo. These data points might be what you use to present a todo "summary/confirmation" page to the user after completing all the steps.
The Todo
has a method getDataPointSteps()
that can be called to iterate over the known data points. Until a todo section's pathway has been traversed by the user, the only data points that will be available are the ones before any uncompleted branching points.
import { TodoStep } from '@endpoint/todo';
//...
const dataPointSteps: TodoStep[] = todo.getDataPointSteps();
Synchronizing with the API
This library will always try to send unsaved data to the API for storage. It also periodically checks for updates that were made on other devices.
When new data has been loaded into the todo from an external source, an event is dispatched:
todo.on('update', () => {});
Local development with Everest client
yarn link
For local development, there are often times you want to test out new feature or when trying to debug an issue with this library in Everest client (all without publishing the package). To do so, you can follow these steps:
In @endpoint/todo
:
- Make changes locally
- Run
yarn build
- Run
yarn link
In Everest client:
- Run
yarn link @endpoint/todo
- Restart your local server
- Profit
node_modules
Depending on your use case, a faster alternative is to make changes directly in the node_modules/@endpoint/todo/
directory of Everest client. You'll also retain hot module reloading.