User interface for responsive, dynamic content trees on the modern web
v2.3.0
- Move ← ↑ → ↓ or tap on target to navigate
- Double tap to view overflow content or select text
[Mouse]
- Click + drag (← ↑ → ↓)
[Scroll wheel]
- Scroll (↑ ↓)
- Shift key + scroll (← →)
[Keyboard]
- Arrow keys (← ↑ → ↓)
- Spacebar / Shift + Spacebar to skip multiple nodes (↑ ↓)
- PageUp / PageDown to skip multiple nodes (↑ ↓)
- Home / End to skip to first or last node (↑ ↓)
- Shift + [PageUp / PageDown / Home / End] (← →)
[Touch]
- Swipe (← ↑ → ↓)
[Mouse/Touch]
- Tap on target
(to view overflow content or select text)
[Mouse/Touch]
- Double tap
[Keyboard]
- Enter key
Noodel - short for node model - is a 4-way scroll-snap user interface for content trees, which you can navigate by simply moving up, down, left and right. It offers a new way to present content that is clean, intuitive, and adaptable.
Noodel is currently available as Noodel.js, an open source JavaScript library for use in web projects. Powered by the reactive view engine of Vue.js.
At the maximum, you can use Noodel to present a full website or web app as a single, interactive content tree. It can be used for virtually anything - from static blogs and wikis, to dynamic apps with complex control flow.
At the minimum, Noodel is a component you can plug in to a web page as an interactive viewer for hierarchical information.
Noodel requires you to arrange your content into a tree of nodes, where each node contains a distinct chunk of content.
Your content tree will be presented on an infinite canvas that you can navigate by simply moving up, down, left and right.
Blocks of content will show or hide as you move through the tree, keeping only the relevant subtree visible.
It's like an infinite cascade of drop-down menus - but much more elegant and useful.
Much of the content we encounter everyday naturally comes in tree structures. Trees, however, are difficult to present on a viewing surface. Conventional UI schemes tend to "flatten" trees into linear layouts - the most common offender being long texts with many sections and subsections squashed into a single vertical column. Trying to find the section you want can be an exercise.
This is sometimes helped by tables of contents and navigation menus. But such components become hard to use on smaller screens and does not scale well.
Noodel is a system designed from the ground up that aims to present content in a better way - by integrating navigation with the content itself. It is optimized for tree structures, with a lot of considerations thrown in to make the user experience as great as possible.
Noodel is completely written in TypeScript, with typings out of the box that makes development easier.
The container for a content node in Noodel is called a noode - just like the one you're looking at right now. Noodes are the basic building blocks for your content tree.
Each noode contains a unit of content. It can also have multiple ordered child noodes that have their own unit of sub-content. This can recurse infinitely to form a tree of any size.
Noodel is based on the principle of breaking down large blocks of content into bite-sized chunks. A noode should never exceed the size of the viewport - a precondition for node-based navigation.
A noodel (i.e. node model) refers to the whole content tree formed by noodes (nodes).
If a noode has children, it must have exactly 1 child that's active. The descendents of the active child will be shown while the others hidden.
This means that, at any given time, only the children of noodes on the hierarchy of active noodes starting from the root are shown. This tree of visible content is called the active tree.
The container (slider) for a list of sibling noodes is called a branch.
The axis on which noodes are ordered in branches is called the branch axis. It is vertical (top to bottom) by default.
So when you swipe up and down, you are moving in the branch axis.
The container (slider) for the list of branches in the active tree, ordered from ancestor to descendents, is called the trunk.
The axis on which branches are ordered in the trunk is called the trunk axis. It is horizontal (left to right) by default.
So when you swipe left and right, you are moving in the trunk axis.
The branch that the view is currently focusing on is the focal branch.
Moving in the trunk axis changes the focal branch.
The active noode inside the focal branch is the focal noode.
The focal noode is the noode the view is centred on, and represents the noode that is currently interacting with the user.
<script>
embedYou can use Noodel by directly embedding its script and CSS into your HTML:
// You should specify the version number (e.g noodel@2.3.0) in production
<script src="https://cdn.jsdelivr.net/npm/noodel/dist/noodel-full.umd.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/noodel/dist/noodel.css">
Noodel is available via npm-linked CDNs such as jsdelivr and unpkg.
Noodel comes in two versions, noodel-full.umd.js and noodel.umd.js. The difference is that the full version comes bundled with Vue.js and the base version doesn't.
If you use the base version you need to also include Vue.js dependencies for Noodel to work. This prevents including Vue twice if you use Noodel in a Vue project. See Usage with Vue for more details.
For most other use cases you should use the full version as a standalone library.
For more advanced projects that use bundlers, install from npm:
npm install noodel
And import into your code:
import Noodel from 'noodel'
// Don't forget to also include the CSS, e.g.
// import 'noodel/dist/noodel.css'
Creating a noodel is very simple. The basic syntax is:
// Creates a view model based on the given content tree
const noodel = new Noodel(contentTree);
// Renders the view onto the page
noodel.mount("#target");
The contentTree parameter is an element selector or array of objects that specifies the content tree, and #target is the target element for placing the noodel.
Mounting will replace the target element with the noodel's DOM elements.
There are two ways to define the content tree that will be used by a noodel. For sites driven by static HTML templates, you can define the tree via HTML and give the container element to the constructor:
<div id="noodel" style="display: none;">
<div class="noode">
<p>My first noode</p>
</div>
<div class="noode">
<p>My second noode</p>
<div class="noode">
<p>First child of my second noode</p>
</div>
</div>
</div>
<script>
const noodel = new Noodel("#noodel");
noodel.mount("#noodel");
</script>
Noodel will traverse the descendents of the container element and convert
all <div>
s with
class="noode"
into
noodes of the noodel, keeping all parent-child relationships. Any text or
elements inside a noode that are not child noodes will be treated as the
content of that noode, concatenated in the order they appear.
This concatenation preserves the markup of content elements
so you have complete freedom in structuring and styling the content
of each noode. Note this does not apply
to the <div>
s that serve as
placeholders for noodes, as they are not inserted into the final DOM.
Also note that the original DOM nodes will not be used in the noodel, as a new DOM subtree will be created for it. This means any side effects such as event listeners attached to the original nodes will not be preserved.
By mounting a noodel on the container of the original DOM tree (as in the example), the original tree will be destroyed and replaced by that of the noodel. This is usually the behavior you want.
Alternatively, for dynamic sites/apps driven by JavaScript, you can specify the content tree as an array of objects corresponding to noodes on the first level:
let tree = [
{
content: "<p>My first noode</p>"
},
{
content: "<p>My second noode</p>"
children: [
{
content: "<p>First child of my second noode</p>"
}
]
}
];
const noodel = new Noodel(tree);
noodel.mount("#target");
Always use trusted HTML strings as
content. Never
use unsanitized strings from users.
Noodel uses Vue internally and therefore provides integration with Vue out of the box. Noodel is available as a Vue component, and also allows you to use Vue components as the content for individual noodes.
If you are including Noodel and Vue's scripts directly in your HTML, it is recommended you use Noodel's base ("Vue-less") build, alongside the Vue build used in your main project (Noodel requires Vue ^2.6.11). This prevents Vue from being included twice.
If you are using Noodel as a module from npm, the default entry is the base build and the bundler should be able to load Vue as a dependency.
Noodel only needs Vue's runtime build. But if you need to compile templates in runtime you should include Vue's compiler as appropriate.
Noodel's Vue component is exposed as a static variable on the Noodel class, which can be used like any other Vue components. It takes an instance of Noodel as its only prop.
import Noodel from 'noodel';
const NoodelComponent = Noodel.VueComponent;
const app = new Vue({
render: function(h) {
return h(NoodelComponent, {
props: {
noodel: this.noodel
}
});
},
data: {
// the Noodel instance must be registered as reactive data
noodel: new Noodel()
}
}).mount("#el");
You can control the noodel via its API (which mutates the reactive state), as normal.
Do NOT call Noodel's
mount()
or unmount()
when using Noodel
as a Vue component. Noodel is mounted with your Vue instance!
You can pass Vue components as noode content and they will be rendered via a
<component :is="yourComponent"></component>
.
To do so, you need to provide an object as noode content:
const noodel = new Noodel([
{
content: {
component: YourVueComponent, // your Vue component object or name
props: { // name-value pairs of props to pass to this component
"propA": "abc",
"propB": 20
},
eventListeners: { // name-value pairs of event listeners to attach onto the component
"onButtonClick": () => console.log("button click")
}
}
}
]);
// Set noodel options on init
const noodel = new Noodel(tree, {
visibleSubtreeDepth: 10,
swipeMultiplierTrunk: 0.9
});
// Change options dynamically on-the-fly
noodel.setOptions({
visibleSubtreeDepth: 5
});
// Set options at content tree creation
const noodel = new Noodel([
{
content: "something cool",
options: {
onEnterFocus: () => console.log("entered a cool noode")
}
}
]);
// Set options dynamically
noodel.getRoot().getChild(0).setOptions({
onExitFocus: () => console.log("exited a cool noode")
});
Setting options on individual noodes is available only via JavaScript. If your noodel is defined by an HTML template you need to set your options programmatically before mount.
By default, a unique ID will be generated for each noode, which will be used for various purposes. You can provide your own ID for noodes using a data attribute:
<div class="noode" data-id="myCustomId">
...
</div>
or in JavaScript:
let noodeWithCustomId = {
id: "myCustomId",
content: "A special noode"
}
Your custom ID must be unique and should not start with "_" as it may conflict with Noodel's default IDs.
By default, the initial active child for each noode is at index 0 (the first child). You can specify another child to be initially active by using:
<div class="noode" data-active>
...
</div>
or in JavaScript:
let tree = [
{
content: "First child"
},
{
isActive: true, // this noode will be active instead
content: "Second child"
},
];
You can specify custom class/styles on individual noodes that will be applied to their container elements. See styling your noodel for more information.
<div class="noode" data-class="myClassA myClassB" data-style="color: green; max-width: 900px;">
...
</div>
In JavaScript:
let noodeWithCustomStyles = {
style: {
color: "green",
"max-width": "900px"
},
className: [
"myClassA",
"myClassB"
],
content: "A fancy noode"
}
If you are using the object syntax, you can also associate custom data with each noode. This can be useful in advanced situations where you need to tag individual noodes with your own data:
const noodel = new Noodel([
{
id: "noodeWithCustomData",
data: "sad"
}
]);
// get/set the custom data
const noodeWithCustomData = noodel.findNoodeById("noodeWithCustomData");
const data = noodeWithCustomData.data; // "sad"
noodeWithCustomData.data = "happy";
You can attach listener callbacks for events by setting them in the noodel/noode options:
const noodel = new Noodel([
{
options: {
// noode specific event
onEnterFocus: () => console.log("entered focus")
}
}
], {
// noodel event
onMount: () => console.log("mounted")
});
// removes the listener by setting the option to null
noodel.setOptions({
onMount: null
});
By default, a noodel responds to four types of user input for navigation: swipe/drag, keyboard, scroll wheel, and tap. Navigation will occur even when such input originates from content elements inside noodes.
This behaviour can be undesirable in situations when you have UI elements that should capture those inputs and prevent them from causing navigation, for example a slider or a textbox.
To address this issue, Noodel provides the following "prevention classes" that you can selectively apply to individual elements in your content:
nd-prevent-swipe
nd-prevent-key
nd-prevent-wheel
nd-prevent-tap
Noodel will ignore the input that originates from an element (or its descendents) with the corresponding prevention class and stops it from triggering any Noodel-specific behaviour.
<div class="noode">
<input type="text" class="nd-prevent-swipe nd-prevent-key"></input>
<button class="nd-prevent-tap">Submit</button>
</div>
You should use prevention classes sparingly, on elements that really need them. Having large blocks of content with navigation prevention can impact usability, especially on smaller screens.
For situations that require extensive navigation prevention, you should rely on the inspect mode instead. Alternatively, you can disable the corresponding input in Noodel's options and provide your own means of navigation via Noodel's API.
For swipe-based interfaces such as Noodel, there is always an unresolvable conflict between the swipe mechanism and native conveniences that depend on the same motion, such as text selection.
The inspect mode is the solution Noodel offers to address this issue. By default, text selection is disabled on a noodel and all input directed towards navigation. Once inspect mode is on, all navigation methods will be suspended and the user is free to select text or perform other conflicting interactions such as drag-and-drop on the focal noode.
It is recommended that you rely on inspect mode, in addition to prevention classes, to deal with situations where UI elements in your content conflict with Noodel's mechanism.
The default trigger for inspect mode is the double tap (on any area of the noodel), or the Enter key on a keyboard.
This trigger has be chosen for maximum convenience and device compatibility, but like anything else it does end up conflicting with interactions such as multiple clicks to select a word/line. If you wish to prevent the default trigger you can always disable it through Noodel's options and provide your own means of toggling inspect mode via Noodel's API (e.g a custom button).
As of Noodel V2, support for custom scrolling inside noodes has been dropped in favor of using the inspect mode to handle content overflow, which provides a more streamlined viewing experience. Scrolling inside noodes is disabled normally but enabled during inspect mode.
(This allows scrolling to be handled by the browser natively, which is a much better idea.)
Noodel is based on the principle of content chunking. Because each noode has a limited space, as a general rule, you should try to break down large blocks of content into bite-sized pieces that can fit into individual noodes, with responsive styling for different screen sizes.
However, this can be impractical in some situations, so inspect mode is provided as a fallback for viewing overflow content.
The object you get when you create a noodel is its view model. It has 2-way binding with the view, and you can perform operations on it to change the view dynamically.
// view model of whole noodel
const noodel = new Noodel(tree);
// view model of an individual noode
const currentFocalNoode = noodel.getFocalNoode();
From v2.1, each Noode instance (view model object of a noode) uniquely represents a noode in the tree. This means you can do things such as compare the equality of two noodes by simply comparing the reference of their Noode instances.
const noodel = new Noodel(tree);
const specialNoode = noodel.findNoodeById("special");
// move to the next noode
noodel.moveForward();
// move 2 steps back towards ancestors
noodel.moveOut(2);
// move to the 3rd branch in the active tree
noodel.setFocalLevel(3);
// jump to a specific noode
specialNoode.jumpToFocus();
const noodel = new Noodel([
{
content: "old"
}
]);
noodel.getRoot().getChild(0).setContent("new");
Note this replaces the original content by setting the innerHTML of the noode. The original content elements will be removed.
Always use trusted HTML strings as
content. Never
use unsanitized strings from users.
const rootNoode = new Noodel().getRoot();
// inserts a noode
rootNoode.addChild({
content: "Hello!"
});
// deletes the noode just added
rootNoode.removeChild(0);
// inserts multiple noodes and their descendents
rootNoode.addChildren([
{
content: "Fruits!",
children: [
{
content: "Tomatoes!"
}
]
},
{
content: "Vegetables!"
}
]);
Noode insertions and deletions inside the active tree will affect the view, while those outside the active tree will not.
Noodel will always try to preserve the current focal noode during insertion and deletion, if possible. This means that if the current focal noode is at index 3, and you insert a noode at index 1, the focal noode becomes the noode at index 4, and no navigation will occur.
Navigation happens only when a deletion includes the focal noode. In that case the focus will go to an adjacent noode, as appropriate.
The noodel's view state is independent of its view. You can mutate it in between mounting and unmounting the view.
const noodel = new Noodel();
noodel.getRoot().addChild({
content: "first child of root",
children: [
{
id: "grandChild",
content: "first child of first child of root"
}
]
});
noodel.jumpTo(noodel.findNoodeById("grandChild"));
// the view will focus on the "grandChild" noode initially
noodel.mount("target");
// the view model is still focused on the same noode after unmount
noodel.unmount();
By default, routing is enabled for your noodel. This allows Noodel to control the hash of your URL to sync with the location of the current focal noode. It will use the ID of the focal noode as the fragment identifier.
This allows you to use the browser's history navigation to jump between noodes, as well as create URL links that target specific noodes in a noodel.
If routing is enabled at a noodel's creation, it will check the URL to focus on an initial target noode, if applicable.
The sync between the focal noode and the URL hash happens regardless whether the noodel is in view, i.e. the sync will happen even if you programmatically navigate a noodel while it's unmounted.
Currently, only explicit changes to the URL will cause a new entry to be pushed to the browser history. Noodel uses history.replaceState() to replace the URL hash as you navigate around the noodel normally to prevent creating too many unnecessary entries. This includes when you use Noodel's API to navigate.
If you need to do a programmatic navigation that pushes to the history stack, you should manually change the URL hash to point to the target noode instead of using Noodel's API.
You should use routing on only one noodel per page (ideally, when you use a single noodel to control a site). Routing behaviour for multiple noodels on the same page is not well defined.
Due to the fact that Noodel does not use native scrolling, basic Ctrl+F search in browsers will not be able to jump to individual occurrences.
Please see noodel-search for a simple DOM based text search plugin for Noodel.
You can control most aspects of Noodel's appearance using plain CSS, by extending/overriding Noodel's CSS classes, or by setting custom class/styles for individual noodes.
In addition, you have complete freedom in structuring/styling the content inside your noodes.
Noodel comes with a basic stylesheet that determines the layout, with a simple greyscale theme. You can extend/override most of Noodel's CSS classes to customize various visual features.
See CSS classes in the API section for the list of classes you can work with.
You can specify custom class/styles for each noode that will be applied to the
nd-noode
element. This is useful when you need
to have different types of noodes (e.g large vs small) in the same tree.
See specifying custom class/styles for example.
The size of noodes are determined in a very simple way - they are placed in a normal block context, so will grow or shrink to fit their content while conforming to the size of siblings, with respect to any CSS height and width properties.
This means you can fully control the size of noodes by setting the height, width,
max-height,
max-width, min-height and min-width properties on the nd-noode
class (or on custom class/styles you provide for individual noodes). You can have either
fixed size noodes
(by setting a specific width/height) or
flexible size noodes (by not setting a specific width/height), and well as using max/min
sizes
to adapt to a range of situations. In addition, you can style your content
and let them influence the size of their noode containers.
And whenever the size of a noode changes, Noodel will automatically align everything so your focal noode is always centered at the right position!
By default, noodes only come with a max-height: 600px
and max-width: 800px
that matches the default size of the
canvas.
As an example, it's very easy to use Noodel to create the typical full-page carousel site, with some simple CSS overrides:
.nd-canvas {
height: 100vh;
width: 100vw;
}
.nd-noode {
height: 100vh;
width: 100vw;
margin: 0; /* Removes the "gap" between noodes */
}
All animations are done via CSS transitions. This means you can control the duration and timing functions of all animations via CSS overrides of the corresponding classes.
See CSS classes in the API section for the list of classes you can work with.
Enter/leave animations make use of the transition classes of Vue.js. You can apply any combination of them according to your needs.
The view model of a noodel. Has 2-way binding with the view.
constructor(contentTree: NoodeDefinition[] | Element | string, options?: NoodelOptions)
Creates the view model of a noodel based on the given content tree.
contentTree
Initial content tree for the noodel. Can be
an array of NoodeDefinition
objects that specify noodes on the first level, an HTMLElement that contain templates
for noodes on
the first level, or a selector string for such an element. If nothing is provided, will
create an empty
noodel with just the root.
options
Global options for the noodel[Deprecated] You can also pass in a single NoodeDefinition object that serves as the root.
mount(el: string | Element)
Mounts the noodel's view at the target element, replacing it.
unmount()
Destroys the noodel's view and removes it from the DOM, but keeping the current state of the view model.
nextTick(callback: () => any)
Schedules a callback function to be called after Noodel's current DOM update cycle. Use this if you need to access DOM elements after performing an update.
getEl(): HTMLDivElement
Gets the container element of this noodel (i.e. nd-canvas), if mounted.
getFocalLevel(): number
Gets the level of the current focal branch. The first branch has level 1.
getActiveTreeHeight(): number
Gets the height (total number of levels) in the current active tree.
getNoodeCount(): number
Gets the number of noodes in this noodel (excluding the root).
getRoot(): Noode
Gets the root noode. The root is an invisible noode that serves as the parent of the topmost branch, and always exists.
getFocalParent(): Noode
Gets the parent noode of the current focal branch.
getFocalNoode(): Noode
Gets the current focal noode.
findNoodeByPath(path: number[]): Noode
Gets the noode at the given path, an array of 0-based indices starting from the root (e.g [0, 2] gets the 3rd child of the first child of the root). Returns null if no noode exists on that path.
findNoodeById(id: string): Noode
Gets the noode with the given ID. Returns null if does not exist.
setOptions(options: NoodelOptions)
Changes the options of the noodel. Properties of the given object will be merged into the current options.
setFocalLevel(level: number)
Navigates the noodel to focus on the branch at the given level of the current active tree. If the level is greater or smaller than the possible limits, will navigate to the furthest level in that direction.
moveIn(levelCount?: number)
Navigates towards the child branches of the current focal noode.
levelCount
number of levels to move, defaults to 1moveOut(levelCount?: number)
Navigates towards the parent branches of the current focal noode.
levelCount
number of levels to move, defaults to 1moveForward(noodeCount?: number)
Navigates towards the next siblings of the current focal noode.
noodeCount
number of noodes to move, defaults to 1moveBack(noodeCount?: number)
Navigates towards the previous siblings of the current focal noode.
noodeCount
number of noodes to move, defaults to 1toggleInspectMode(on: boolean)
Turns inspect mode on/off.
The view model of a noode. Has 2-way binding with the view.
data: any
Custom data associated with this noode. You can freely get/set this property.
isDeleted(): boolean
Returns true if this noode has been deleted from its noodel. Most operations on a deleted noode will throw an error.
getParent(): Noode
Gets the parent of this noode. All noodes should have a parent except the root, which will return null.
getPrev(): Noode
Gets the previous sibling, or null if this is already the first.
getNext(): Noode
Gets the next sibling, or null if this is already the last.
getPath(): number[]
Gets the path (an array of zero-based indices counting from the root) of this noode.
getDefinition(): NoodeDefinition
Extracts the definition tree of this noode (including its descendents). Useful if you want to perform your own operations on the content tree.
getEl(): HTMLDivElement
Gets the container element of this noode (i.e. nd-noode-box), if mounted.
getChildBranchEl(): HTMLDivElement
Gets the container element of the branch containing this noode's children (i.e. nd-branch-box), if it has children and is mounted.
getChild(index: number): Noode
Gets the child at the given index. Returns null if does not exist.
getChildren(): Noode[]
Gets a mapped array of this noode's list of children.
getChildCount(): number
Gets the number of children of this noode.
getId(): string
Gets the ID of this noode.
getContent(): string | ComponentContent
Gets the content of this noode.
getClass(): string[]
Gets the custom class names for this noode.
getStyle(): object
Gets the custom styles for this noode.
getIndex(): number
Gets the 0-based index (position among siblings) of this noode.
getLevel(): number
Gets the level of this noode. The root has level 0, the first branch has level 1, and so on.
getActiveChildIndex(): number
Gets the active child index. Returns null if there's no active child.
getActiveChild(): Noode
Gets the current active child. Returns null if does not exist.
isRoot(): boolean
Returns true if this noode is the root.
isActive(): boolean
Returns true if this noode is active.
isVisible(): boolean
Returns true if this noode is logically visible (i.e even when rendered beyond canvas boundary). Note that visibility may be delayed due to debounce effects.
isChildrenVisible(): boolean
Returns true if this noode's children is logically visible (i.e even when rendered beyond canvas boundary). Note that visibility may be delayed due to debounce effects.
isInFocalBranch(): boolean
Returns true if this noode is inside the focal branch.
isFocalParent(): boolean
Returns true if this noode is the parent of the focal branch.
isFocalNoode(): boolean
Returns true if this noode is the focal noode.
setId(id: string)
Sets the ID of this noode.
setContent(content: string | ComponentContent)
Replaces the content of this noode.
setClass(className: string | string[])
Replaces the custom class names for this noode. Can be an array or a space-delimited string.
setStyle(style: string | object)
Replaces the custom inline styles for this noode. Can be a string or an object of property-value pairs.
setOptions(options: NoodeOptions)
Changes the options for this noode. Properties of the given object will be merged into the existing options.
setActiveChild(index: number)
Changes the active child of this noode. If doing so will toggle the visibility of the focal branch (i.e this noode is an ancestor of the focal branch), a jump to the new active child will be triggered.
jumpToFocus()
Performs a navigational jump to focus on this noode. Cannot jump to the root.
addChild(childDef: NoodeDefinition, index?: number): Noode
Inserts a child noode (and its descendents) at the given index. Will always preserve the current active child. Returns the inserted child.
childDef
definition tree of the childindex
if not provided, will append to the end of the
childrenaddChildren(childDefs: NoodeDefinition[], index?: number): Noode
Inserts a list of child noodes (and their descendents) at the given index. Will always preserve the current active child. Returns the inserted children.
childDefs
definition trees of the childrenindex
if not provided, will append to the end of the
childrenaddBefore(...defs: NoodeDefinition[]): Noode[]
Syntax sugar for adding sibling noode(s) before this noode.
defs
noode definitions to addaddAfter(...defs: NoodeDefinition[]): Noode[]
Syntax sugar for adding sibling noode(s) after this noode.
defs
noode definitions to addremoveChild(index: number): NoodeDefinition
Removes a child noode (and its descendents) at the given index. If the active child is removed, will set the next child active, unless the child is the last in the list, where the previous child will be set active. If the focal branch is deleted, will jump to the nearest ancestor branch. Returns the definition of the deleted noode.
removeChildren(index: number, count: number): NoodeDefinition[]
Removes children noodes (and their descendents) at the given index. If the active child is removed, will set the next child active, unless the child is the last in the list, where the previous child will be set active. If the focal branch is deleted, will jump to the nearest ancestor branch. Returns the definitions of the deleted noodes.
count
number of children to remove, will adjust to
maximum if greater than possible rangeremoveBefore(count: number): NoodeDefinition[]
Syntax sugar for removing sibling noode(s) before this noode.
count
number of noodes to remove, will adjust to
maximum if greater than possible rangeremoveAfter(count: number): NoodeDefinition[]
Syntax sugar for removing sibling noode(s) after this noode.
count
number of noodes to remove, will adjust to
maximum if greater than possible rangeremoveSelf(): NoodeDefinition
Syntax sugar for removing this noode from the tree.
traverseSubtree(func: (noode: Noode) => any, includeSelf: boolean)
Do a preorder traversal of this noode's subtree and perform the specified action on each descendent.
func
the action to performincludeSelf
whether to include this noode in the traversalObject template used for noode creation and insertion.
id?: string
ID of this noode. If provided, must be unique and should NOT start with "_" .
children?: NoodeDefinition[]
Children noodes of this noode. Defaults to an empty array.
isActive?: boolean
If provided, will mark this noode as active, overriding the default (first child). If multiple siblings are marked as active, only the first one will take effect.
content?: string | ComponentContent
Content of this noode. If is a string, will be inserted as innerHTML of the nd-noode
element.
Can also be a ComponentContent object that wraps a Vue component.
className?: string | string[]
Custom class(es) for this noode. Either a string of class names delimited by spaces or an array.
style?: string | object
Custom styles for this noode. Either a string in inline style format or an object in {"property": "value"} format.
options?: NoodeOptions
Options for this noode.
data?: any
Custom data to associate with this noode.
Global options for a noodel.
visibleSubtreeDepth?: number
The number of levels of descendent branches to show after the current focal branch. Defaults to 1.
retainDepthOnTapNavigation?: boolean
If true, will keep the current branch depth (if possible) when tapping on the sibling of an ancestor. Useful to create an effect similar to conventional navigation menus. Defaults to false.
subtreeDebounceInterval?: number
Amount of time to wait in ms (until no more consecutive hits) before showing the subtree of the focal noode, when moving the focal branch. Mainly a performance hack to prevent rapid toggling of subtree elements. Defaults to 360.
useKeyNavigation?: boolean
Whether to apply the default keyboard navigation. Defaults to true.
useWheelNavigation?: boolean
Whether to apply the default wheel navigation. Defaults to true.
useSwipeNavigation?: boolean
Whether to apply the default swipe navigation. Defaults to true.
useTapNavigation?: boolean
Whether to apply the default tap navigation. Defaults to true.
useInspectModeKey?: boolean
Whether to allow toggling inspect mode via the Enter key. Defaults to true.
useInspectModeDoubleTap?: boolean
Whether to allow toggling inspect mode via the double tap. Defaults to true.
skipResizeDetection?: boolean
Global option for disabling resize detection on ALL noodes and branches. Disabling resize detection may give a slight performance boost. Use this option if you know that the size of noodes and branches will never change after creation. Defaults to false.
swipeMultiplierBranch?: number
Number of pixels to move for every pixel swiped in the branch axis. Defaults to 1.
swipeMultiplierTrunk?: number
Number of pixels to move for every pixel swiped in the trunk axis. Defaults to 1.
snapMultiplierBranch?: number
Number of noodes (per unit of velocity) to snap across after a swipe is released. Defaults to 1.
snapMultiplierTrunk?: number
Number of levels (per unit of velocity) to snap across after a swipe is released. Defaults to 1.
useRouting?: boolean
Determines whether routing should be enabled for this noodel. Defaults to true.
showLimitIndicators?: boolean
Whether to render the limit indicators of the canvas. Defaults to true.
showChildIndicators?: boolean
Whether to render the child indicators of noodes. Defaults to true.
showBranchBackdrops?: boolean
Whether to render the branch backdrop elements. Defaults to false.
orientation?: 'ltr' | 'rtl' | 'ttb' | 'btt'
Determines the direction of the trunk axis. Defaults to 'ltr' (left to right).
branchDirection?: 'normal' | 'reverse'
Determines the direction of the branch axis under the current trunk orientation. Defaults to 'normal'.
onMount?: () => any | null
Callback after the view has been mounted and properly aligned after the first render. Changes to the view model from here onward will sync with the view and trigger animation effects.
onFocalNoodeChange?: (current: Noode, prev: Noode) => any | null
Handler called once after noodel creation, and whenever the focal noode has changed.
current
the current focal noodeprev
the previous focal noode, null on initial call
onFocalParentChange?: (current: Noode, prev: Noode) => any | null
Handler called once after noodel creation, and whenever the focal parent has changed.
current
the current focal parentprev
the previous focal parent, null on initial call
onEnterInspectMode?: (noode: Noode) => any | null
Handler called when entered inspect mode.
noode
the current focal noodeonExitInspectMode?: (noode: Noode) => any | null
Handler called when exited inspect mode.
noode
the current focal noodeOptions for an individual noode.
skipResizeDetection?: boolean | null
Option for disabling resize detection (on the branch axis) for this noode. If set to a boolean, will override the global skipResizeDetection option. Defaults to null.
skipBranchResizeDetection?: boolean | null
Option for disabling resize detection (on the trunk axis) for this noode's child branch. If set to a boolean, will override the global skipResizeDetection option. Defaults to null.
showChildIndicator?: boolean | null
If set to a boolean, will override the global showChildIndicators option for this specific noode. Defaults to null.
showBranchBackdrop?: boolean | null
If set to a boolean, will override the global showBranchBackdrops option for the child branch of this specific noode. Defaults to null.
onEnterFocus?: (self: Noode, prev: Noode) => any | null
Handler called whenever this noode entered focus. Will be called once after noodel creation if this is the focal noode.
self
the current focal noode (i.e. this noode)prev
the previous focal noode, null on initial call
onExitFocus?: (self: Noode, current: Noode) => any | null
Handler called whenever this noode exited focus.
self
the previous focal noode (i.e. this noode)current
the current focal noodeonChildrenEnterFocus?: (self: Noode, prev: Noode) => any | null
Handler called whenever this noode's child branch entered focus. Will be called once after noodel creation if this is the focal parent.
self
the current focal parent (i.e. this noode)prev
the previous focal parent, null on initial call
onChildrenExitFocus?: (self: Noode, current: Noode) => any | null
Handler called whenever this noode's child branch exited focus.
self
the previous focal parent (i.e. this noode)prev
the current focal parentObject template used for passing a Vue component as noode content.
component: string | object | Function
Vue component to be rendered using Vue's <component> tag. Can be name or component reference.
props?: object
Name-value pairs of props to pass to this component.
eventListeners?: object
Name-value pairs of event listeners to pass to this component.
List of CSS classes you can use for styling.
nd-canvas
Outermost container of the noodel that processes all inputs. Use this to style the canvas size and background.
nd-canvas-ltr, nd-canvas-rtl, nd-canvas-ttb, nd-canvas-btt
Class applied to the canvas element depending on the current trunk orientation. Use this with descendent combinators to style elements under a specific orientation.
nd-canvas-normal, nd-canvas-reverse
Class applied to the canvas element depending on the current branch direction. Use this with descendent combinators to style elements under a specific branch direction.
nd-limit, nd-limit-left, nd-limit-right, nd-limit-top, nd-limit-bottom
Classes applied to the limit indicators (the bars that come up when you reach the view limits).
nd-limit-[transition class]
Transition classes for enter/leave of limit indicators.
nd-trunk
The trunk container. You should not need to style this element directly in most cases.
nd-trunk-move
Class applied to the nd-trunk
element when moving in the
trunk axis.
Use this to control the timing and duration of the movement.
nd-branch-box
Outermost container of a branch that is responsible for its visibility. Occupies 100% length
of the canvas.
Avoid styling this element directly, use nd-branch-backdrop
instead.
nd-branch-box-focal
Applied to the focal nd-branch-box
element.
nd-branch-box-[transition class]
Transition classes for enter/leave of branches.
nd-branch-backdrop
Absolute positioned child of the nd-branch-box
element
that is optionally rendered.
Prefer to style this instead of nd-branch-box
to prevent conflicts with the visibility mechanism.
nd-branch-backdrop-focal
Applied to the focal nd-branch-backdrop
element.
nd-branch
Outer container for the items of a branch that is responsible for its sliding movement.
Avoid styling this element directly, use nd-branch-inner
instead.
nd-branch-focal
Applied to the focal nd-branch
element.
nd-branch-move
Class applied to an nd-branch
element when moving in the
branch axis.
Use this to control the timing and duration of the movement.
nd-branch-inner
Immediate container for the items of a branch. Prefer to style this instead of nd-branch
to prevent conflicts with the slide mechanism.
nd-branch-inner-focal
Applied to the focal nd-branch-inner
element.
nd-noode-box
Outermost container of a noode.
Avoid styling this element directly, use nd-noode
instead.
nd-noode-box-active
Applied to an active nd-noode-box
element.
nd-noode
Immediate container of your noode content. Use this class to control the size and aesthetics of your noodes.
nd-noode-active
Applied to an active nd-noode
element.
nd-noode-[transition class]
Transition classes for enter/leave of noodes during noode insert/delete.
nd-noode-move
Applied to sibling noodes during noode insert/delete. Use this class to control the animation of siblings as they change position.
Sibling animation during noode insert/delete depend on transitioning the branch and noodes simultaneously. This means they should have identical duration and timing functions - otherwise it will cause a "jump" in the animation.
nd-noode-inspect
Applied to the focal noode during inspect mode.
nd-inspect-backdrop
The backdrop element that appears during inspect mode.
nd-inspect-backdrop-[transition class]
Transition classes for enter/leave of the backdrop.
nd-child-indicator
Class for the child indicator element (the "arrow" that indicates noodes with children).
nd-child-indicator-active
Applied to the nd-child-indicator
of an active noode.
nd-child-indicator-expanded
Applied to the nd-child-indicator
of a noode that has
visible children.
nd-child-indicator-[transition class]
Transition classes for enter/leave of child indicators during noode insert/delete.
Noodel is targeted to work on all major modern browsers.
Support for IE 10 and 11 is best-effort only - Noodel should work on these browsers, but there may be quirks and bugs in some features.
IE 9 and below are not supported.
Contributions are most welcome! Please see here for details.
If you find Noodel useful or inspiring, please consider supporting the author :)
Or via Github Sponsors
MIT License
© 2019-2020 Zuohao (Jonny) Lu