Noodel.js

User interface for responsive, dynamic content trees on the modern web

v2.3.0
GitHub Examples

Basic controls

- Move ← ↑ → ↓ or tap on target to navigate

- Double tap to view overflow content or select text

All navigation controls

[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

Toggle inspect mode

(to view overflow content or select text)

[Mouse/Touch]
- Double tap
[Keyboard]
- Enter key

Overview

Summary

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.

What can I use it for?

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.

How it works

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.

Why Noodel?

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.

What's so good about it?

  • Noodel is clean and intuitive. Most people immediately understand how it works. It reduces clutter and cognitive load by encouraging the removal of navigation bars and menus.
  • Noodel is device-agnostic. Its layout and navigation mechanism remains identical, regardless of screen size and input method.
  • Noodel is accessible. By reducing the essential controls to 4-way movements, it can be easily adapted to contexts where control ability is limited. It is entirely possible to build a Noodel site that operates on just 4 arrow keys.
  • Noodel is flexible. You can use chunking and tree recursion for as little or much as you like. It doesn't care what you put into each node. The look-and-feel is fully customizable via plain CSS.
  • Noodel is dynamic. It is more than just a static content viewer, although you can certainly use it that way. You can add, update or delete nodes at any time to deal with asynchronous data or manage control flow.
  • Noodel is interesting. It is an engaging way to present your content that makes it stand out from others.

TypeScript support

Noodel is completely written in TypeScript, with typings out of the box that makes development easier.

Concepts

Noode

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.

Noodel

A noodel (i.e. node model) refers to the whole content tree formed by noodes (nodes).

Active child noode

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.

Active tree

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.

Branch

The container (slider) for a list of sibling noodes is called a branch.

Branch axis

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.

Trunk

The container (slider) for the list of branches in the active tree, ordered from ancestor to descendents, is called the trunk.

Trunk axis

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.

Focal branch

The branch that the view is currently focusing on is the focal branch.

Moving in the trunk axis changes the focal branch.

Focal noode

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.

Guide

Installation

Direct <script> embed

You 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.

A note on builds

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.

NPM

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' 
                        
                    

Basic usage

Creating a noodel

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.

Defining content tree via HTML

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>
                        
                    

The specifics

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.

Noodel creates its own DOM subtree

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.

Defining content tree via object array

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.

Usage with Vue

Summary

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.

Using the base build

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.

Using Noodel's Vue component

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!

Using Vue components as noode content

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")
                                        }
                                    }
                                }
                            ]);
                        
                    

Configuring your noodel

Options for the noodel

                        
                            // 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
                            });
                        
                    

Options for individual noodes

                        
                            // 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.

Specifying noode ID

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.

Specifying initial active child

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"
                                },
                            ];
                        
                    

Specifying custom class/styles

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"
                            }
                        
                    

Attach custom data

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";
                        
                    

Listening to events

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
                            });
                        
                    

Navigation prevention

Preventing navigation

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.

Prevention classes

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>
                        
                    

Caveat

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.

Inspect mode

Summary

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.

Inspect mode trigger

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).

Noode content overflow

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.

Changing the view dynamically

The view model

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.

Programmatic navigation

                        
                            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();
                        
                    

Replacing noode content

                        
                            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.

Noode insertion and deletion

                        
                            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.

Changing noodel state between mounting

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();
                        
                    

Routing

Summary

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.

History push behavior

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.

Caveat

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.

Search

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.

Styling your noodel

Summary

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.

Styling via Noodel's CSS classes

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.

Styling via noode-specific class/styles

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.

Controlling noode size

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.

Example: full-page carousel

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 */
                                }
                            
                        

Controlling animations

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.

API

Noodelclass

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 1
moveOut(levelCount?: number)

Navigates towards the parent branches of the current focal noode.

  • levelCount number of levels to move, defaults to 1
moveForward(noodeCount?: number)

Navigates towards the next siblings of the current focal noode.

  • noodeCount number of noodes to move, defaults to 1
moveBack(noodeCount?: number)

Navigates towards the previous siblings of the current focal noode.

  • noodeCount number of noodes to move, defaults to 1
toggleInspectMode(on: boolean)

Turns inspect mode on/off.

Noodeclass

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 child
  • index if not provided, will append to the end of the children
addChildren(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 children
  • index if not provided, will append to the end of the children
addBefore(...defs: NoodeDefinition[]): Noode[]

Syntax sugar for adding sibling noode(s) before this noode.

  • defs noode definitions to add
addAfter(...defs: NoodeDefinition[]): Noode[]

Syntax sugar for adding sibling noode(s) after this noode.

  • defs noode definitions to add
removeChild(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 range
removeBefore(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 range
removeAfter(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 range
removeSelf(): 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 perform
  • includeSelf whether to include this noode in the traversal

NoodeDefinitioninterface

Object 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.

NoodelOptionsinterface

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 noode
  • prev 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 parent
  • prev the previous focal parent, null on initial call
onEnterInspectMode?: (noode: Noode) => any | null

Handler called when entered inspect mode.

  • noode the current focal noode
onExitInspectMode?: (noode: Noode) => any | null

Handler called when exited inspect mode.

  • noode the current focal noode

NoodeOptionsinterface

Options 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 noode
onChildrenEnterFocus?: (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 parent

ComponentContentinterface

Object 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.

CSS classes

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.

Browser support

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.

Contribute

Contributions are most welcome! Please see here for details.

Donate

If you find Noodel useful or inspiring, please consider supporting the author :)

Or via Github Sponsors

MIT License
© 2019-2020 Zuohao (Jonny) Lu