- Branches
A graph structure with navigation and analysis functions to aid in building branching user interfaces.
An example application is located in example/
.
Run the example app:
- Clone this repo
- Navigate to the
example
directory in your terminal - Install the example app's dependencies:
npm install
- Run the app
npm start
Once the start
command is ran, a development server will start and should automatically navigate to localhost:3000 in your browser.
You can edit the files in the example
directory—including graph.json—and see live updates in your browser.
Before you run the tests for the first time, you need to install the project's development dependencies. This adds the test framework and libraries that let it parse the latest javascript syntax. You can add them using npm at the root level of this repository:
npm install
Unit tests can be run using npm:
npm test
To use a flow control graph in your own project, you will first need to import this library and then create a new instance of a Navigator
with your own graph data. We will talk about the sections
and sectionOrdering
passed to the Navigator
later; the other parameters are detailed in the Navigator
source file.
import Navigator from "branches";
const navigator = new Navigator({ sections, sectionOrdering });
🚨 For now, you'll need to include the
branches
source files in your project manually. I hope to publish the code to npm soon to make it even easier to install.
The Navigator
object provides methods for navigating sequentially through the user flow. It looks at the control properties of each graph node to determine a path through the user flow. In your application, you will mostly use the initialPosition
and nextPosition
functions to navigate the graph.
const initialPosition = navigator.initialPosition(applicationData);
const nextPosition = navigator.nextPosition(applicationData, initialPosition);
Position
objects are returned from the Graph's *Position methods. Each position refers to a specific point in the application and lets you get back information about it.
The GraphAnalyzer
provides methods for testing the shape of your graph to ensure users won't get stuck on a bad path or be unable to reach certain nodes.
On each graph node, you can store your own data. Use that data to decide what to show your user. You can think of each node as a page of your application, and the properties you add to that node as props you pass into the render function for that page.
function renderPage(props) {
const { title } = props;
return `<h1>${title}</h1>`;
}
const node = nextPosition.activeNode();
renderPage(node.userData);
The flow control graph represents the user flow through a branching series of user interface elements. Each node of the graph represents a page the user may visit. Root nodes are called sections, which group related pages and allow for consistent entry points throughout the user flow.
Each piece of the graph is a node. All information used by the flow control graph lives inside the nodes' _control
properties. We call these control properties. You can use a different key to store the control properties by passing a controlKey
parameter when constructing your Graph.
A simple node looks like the following:
{
_control: {
next: "anotherNode"
},
data: {
// anything can go here, really
}
}
The next
control property tells the Graph which sibling node should come after the current node. Any other property of the node is simply a place for you to store data relevant to your application.
Nesting all your user data within a single top-level property like data
can make it easier to use with the GraphAnalyzer; you can configure it to ignore a single key and avoid false-positives for unreachable nodes.
For a complete example of the possible control properties and how they fit in a graph, see the mock data in the Graph test file.
Each section is the root node of a graph structure. It specifies the initialNode
to provide an entry point into its child nodes.
const first = {
_control: { initialNode: "a" },
a: { _control: { next: "b" } },
b: { _control: { next: "c" } },
c: {},
};
…
const sections = { first, second, third };
The section ordering array tells the Graph
the order in which to visit each section. The sections are identified by their key, and visited sequentially as the path through each is completed.
const sectionOrdering = ["first", "second", "third"];
The condition
control property lets you determine whether a given node should be visited. If the named condition function returns false, the node will be skipped,and progress will continue with the next node.
_control: {
condition: "isRaining",
next: "following"
}
Condition functions are provided to the graph as a map called filters. When evaluated as part of a control property, the condition functions receive the incoming position as a parameter.
const filters = {
isRaining: (data, position) => false,
isCloudy: (data, position) => true,
isSunny: (data, position) => false
}
new Graph({ sections, sectionOrdering, filters });
Stored in the filters
property of the Graph.
condition(userData, enteringNode)
The next
control property can take a few forms. The simplest is the string key of the following sibling node. To enable a choice between following nodes, you can also provide an array of { key, condition }
pairs.
next: [
{ key: "indoorActivities", condition: "isRaining" },
{ key: "walk", condition: "isCloudy" },
{ key: "picnic", condition: "isSunny" },
"lounge"
]
The key
property is just like the string form of next
; it is the string key of a sibling node. The condition
property is the string key of a filter function provided to the Graph.
The condition functions for the next.condition
control property are the same as those of the entry condition property. When evaluated for a next
condition, the functions receive the departing position as a parameter.
You can reference application data within a Position
. To do so, you can specify a collectionPath
control property. The Graph will then collect the keys for all objects stored at that path within your application data. The keys are retrievable from the Position
object, and can be used to look up the relevant data when you visit that position. We store keys and paths rather than data so the positions work well with copy-on-write data types like those from Immutable.js or produced with Immer.
You can select a subset of a collection by using the collectionFilter
control property. Filters are called with the application data and each collection entry.
Specify an initialNode
in addition to a collectionPath
and the entries in that collection will be iterated over on each visit to the initial node.
Any collection that has zero entries will be skipped, and progress will continue with the next node.
- Try changing parameters within the example graph.
- Look at the Graph.test.js mock data structure for reference controls