@endpoint/todo

Provides a universal mechanism for interacting with todos

Usage no npm install needed!

<script type="module">
  import endpointTodo from 'https://cdn.skypack.dev/@endpoint/todo';
</script>

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

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

  1. First update the version in the package.json
  2. Then, update your local main branch
  3. 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

The Todo

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
}

The TodoStep

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.

The TodoStepPrimitive

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.