1.1.0 • Published 7 years ago

@enotes/groot v1.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
7 years ago

Groot

A UI tree for working with nested content.

Groot menu with TV show information

Prerequisites

If you intend to use Groot as an ES2015 module, you will need:

  1. a transpiler such as Webpack + babel to build your project,
  2. an ES2015 polyfill library, such as babel-polyfill, and
  3. a Handlebars template loader, such as handlebars-loader.

To see how these work together refer to the project's webpack.config.js file, specifically the entry and modules sections.

If you intend to use Groot as an ES5 script, no other prerequisites are necessary.

Installation

As an ES2015 module

Install Groot with npm.

$ npm install --save @enotes/groot

Import the Groot constructor into your own module.

import { Groot } from 'groot';

As an ES5 script

Copy the Groot assets in the dist/ folder to the appropriate vendor location in your project structure, e.g., scripts/vendor/groot/.

Reference the Groot assets in your HTML document.

<!-- creates window.Groot -->
<script src="scripts/vendor/groot/groot.es5.js"></script>
<script>console.log(window.Groot);</script>

Styles and fonts

The Groot style sheet and fonts are located in the dist/ folder. You can copy these artifacts to your own project, but be aware that the stylesheet assumes that its fonts are located in a sibling fonts/ directory. If this does not match your project structure you can:

  • manually edit the groot.css file to suit your needs, or
  • if you use Sass, you can:
    1. set the $icomoon-font-path variable to the path where your font files reside
    2. import the src/groot.scss file into your own stylesheet
    3. recompile your stylesheet

The core Groot css style selectors are:

SelectorPurpose
.groot-treeBase unordered list. Contains .groot-leaf list items.
.groot-leafIndividual list item. May contain neseted .groot-tree element.
.groot-iconAll icons.
.groot-leaf__toggleThe expand/collapse icon.
.groot-leaf__upThe up arrow icon.
.groot-leaf__downThe down arrow icon.
.groot-leaf__labelThe text in a .groot-leaf element. Will have .active or .inactive classes. Will have .drag-target class on drag events. Has :hover pseudo-selector.
.groot-leaf__label-fieldThe input element for creating/editing leafs.
.groot-menuThe pop-up menu.
.groot-menu__labelItems on the pop-up menu.
.groot-menu__closeThe pop-up menu close button.

Building the project

Install the Groot npm dependencies to ensure that the build tools are present.

$ npm install

The Groot distribution artifacts are pre-built and are located in the dist/ folder. These assets are re-built with the command:

$ npm run-script build

The build script relies on bash shell commands and will not work correctly in non-bash environments.

Usage

Creating your tree

Create a tree by iterating over some data and transforming it into instances of Groot.Leaf.

const rootLeaf = new Groot.Leaf('root leaf');
let child = rootLeaf.branch('child leaf 1');
child.branch('child leaf 1.1');
child.branch('child leaf 1.2');
rootLeaf.branch('child leaf 2');
child = rootLeaf.branch('child leaf 3');
child.branch('child leaf 3.1');
// etc.

const containerElement = document.querySelector('#some-container');
const tree = new Groot(containerElement, rootLeaf);
rootLeaf.expand(true);
tree.render();

See src/demo.js for a more complete example.

Adding custom meta-data

Leafs can contain custom meta-data in the form of attributes.

const leaf = tree.branch('child leaf');
leaf.setAttribute('myCustomID', 123);

Setting custom meta-data on a leaf allows you to dereference the leaf against your application's own data. Custom meta-data also plays a part in Groot events (discussed below) and may be changed during leaf creation, or when async events are raised.

Listening for events

Groot raises events for nearly every tree operation. Some of these will require you to commit to, or cancel an operation before it completes.

EventDescriptionAsync?Multisource?Special Properties
renderingthe tree is about to be rendered in the DOMnono
renderedthe tree has been rendered in the DOMnono
leaf.movinga leaf is movingyesyesdirection=up,down,first,last,to,parent,before,after
leaf.moveda leaf has movednoyessee: leaf.moving
leaf.clickinga leaf is being clicked onnonoisRightClick={Boolean}
leaf.clickeda leaf has been clicked onnonosee: leaf.clicking
leaf.dragginga leaf is starting to be draggednono
leaf.draggeda leaf has been draggednono
leaf.droppinga leaf is starting to be dropped onto another leafnoyes
leaf.droppeda leaf has been dropped onto another leafnoyes
leaf.renaminga leaf is about to be renamedyesnoisNew={Boolean}, label={String}
leaf.renameda leaf has been renamednonosee: leaf.renaming
leaf.unnameda leaf has been unnamed (pending rename is canceled)nonosee: leaf.renaming
leaf.expandinga leaf is about to expandnonodeep={Boolean}
leaf.expandeda leaf has expandednonosee: leaf.expanding
leaf.collapsinga leaf is about to collapsenonodeep={Boolean}
leaf.collapseda leaf has collapsednonosee: leaf.collapsing
leaf.deletinga leaf is about to be deletedyesno
leaf.deleteda leaf has been deletednono
leaf.creatinga leaf is about to be creatednonoprojectedPosition={Number}
leaf.createda leaf has been creatednonosee: leaf.creating
leaf.pruninga leaf is about to be prunednono
leaf.pruneda leaf has been prunednono
leaf.activatinga leaf is about to be activatednonodeep={Boolean}
leaf.activateda leaf has been activatednonosee: leaf.activating
leaf.disablinga leaf is about to be disablednonodeep={Boolean}
leaf.disableda leaf has been disablednonosee: leaf.disabling
menu.clickinga menu item is about to be clickednonoaction=create,rename,delete,move-to,move-up,move-down,move-first,move-last,move-before,move-after,make-parent,activate-children,disable-children,close
menu.clickeda menu item has been clickednonosee: menu.clicking
menu.showingthe menu is about to shownono
menu.shownthe menu has been shownnono(menu options)
menu.hidingthe menu is about to be hiddennono
menu.hiddenthe menu has been hiddennono(menu options)

For convenience, event names may be referenced by using the Groot.EVENTS object. For example:

Groot.EVENTS.RENDERED === 'rendered'
Groot.EVENTS.LEAF.DELETING === 'leaf.deleating'
Groot.EVENTS.MENU.HIDDEN === 'menu.hidden'
// etc.

Subscriptions to Groot events are established by calling its on() method with a callback.

tree.on(Groot.EVENTS.LEAF.MOVING, (eventArgs) => {
    // handle the event
});

All event callbacks will receive an eventArgs object. This will contain contextual information about the event. If a single node is involved, the eventArgs object will contain a source property with information about that node. If two nodes are involved (e.g.., drag-n-drop, or "Assign parent"), the event is deemed "multisource", and the eventArgs object will contain both source and target properties with information about the originator of the action (source) and the receiver of the action (target). These properties will also contain all of the custom meta-data added to their corresponding leafs.

Synchronous events cannot be stopped, but asynchronous events can be stopped. An asynchronous eventArgs object will contain two methods, commit() and cancel(), either of which must be called to finish the asynchronous operation. For example:

tree.on(Groot.EVENTS.LEAF.DELETING, (eventArgs) => {
    api.delete(eventArgs.source.myCustomID).then(() => {
        // success -- the tree node will be removed
        eventArgs.commit();
    }, (err) => {
        // failure -- the tree node will remain
        eventArgs.cancel();
    });
});

The public Groot API

After a Groot object is created the tree may be manipulated through the Groot public API, and through events that it raises. At present the public API is fairly terse but more functionality may be added later. The Groot object is designed to emit events, and perform its own internal operations as the application code responds to those events, so the public API is fairly limited.

Note that Groot methods are designed to locate leafs by custom attributes, which means that if you intend to manipulate the Groot instance through its public API, you will want to assign some unique attribute to each leaf as you are creating it (perhaps a database ID). See: Adding custom meta-data.

/**
* @typedef {Object} Groot
* @property {Boolean} isEnabled
* @method {Function} prune
* @method {Function} remove
* @method {Function} rename
* @method {Function} expand
* @method {Function} disable
* @method {Function} enable
* @method {Function} render
* @method {Function} closeMenu
*/

The public Leaf API

Once a Groot object has been created with its initial tree of nodes, it will handle all interactions among its leafs. However, the initial tree must be created manually, so understanding the public Leaf API can be beneficial.

/**
* @typedef {Object} Leaf
* @augments {leafPrototype}
* @property {Number} id - unique, internal identifier
* @property {Leaf|null} parent
* @property {Array.<Leaf>} leafs
* @property {Number} leafCount
* @property {String} label
* @property {Number} level
* @property {Number} position
* @property {Boolean} isRoot
* @property {Boolean} isFirstSibling
* @property {Boolean} isLastSibling
* @property {Boolean} isExpanded
* @property {Boolean} isBeingRenamed
* @property {Boolean} isBeingGrafted
* @property {Boolean} isActive
* @property {Object} attributes
* @method {Function} moveBefore
* @method {Function} moveAfter
* @method {Function} makeParentOf
* @method {Function} makeChildOf
* @method {Function} remove
* @method {Function} move
* @method {Function} branch
* @method {Function} graft
* @method {Function} ungraft
* @method {Function} inosculate
* @method {Function} activate
* @method {Function} deactivate
* @method {Function} getParent
* @method {Function} hasChildren
* @method {Function} hasAnyActiveChildren
* @method {Function} hasAnyInactiveChildren
* @method {Function} getChildren
* @method {Function} getFirstChild
* @method {Function} getLastChild
* @method {Function} getSiblings
* @method {Function} getSiblingsBefore
* @method {Function} getSiblingsAfter
* @method {Function} get
* @method {Function} traverse
* @method {Function} find
* @method {Function} findByAttributes
* @method {Function} isParentOf
* @method {Function} isChildOf
* @method {Function} isAncestorOf
* @method {Function} isDescendantOf
* @method {Function} isSibling
* @method {Function} isBefore
* @method {Function} isAfter
* @method {Function} isLeftOf
* @method {Function} isRightOf
* @method {Function} expand
* @method {Function} expandChildren
* @method {Function} collapse
* @method {Function} collapseChildren
* @method {Function} toggle
* @method {Function} requestLabelChange
* @method {Function} cancelLabelChange
* @method {Function} commitLabelChange
* @method {Function} setAttributes
* @method {Function} setAttribute
* @method {Function} mergeAttributes
* @method {Function} hasAttribute
* @method {Function} anyChildHasAttribute
* @method {Function} equals
* @method {Function} length
* @method {Function} toString
*/

Special terms

Leaf.attributes

A leaf may have custom attributes, defined by application data but irrelevant to Groot itself. For example, in a tree of TV shows, each leaf might contain a tvShowID attribute that is associated with an entry in a TV show database. The public Groot API is designed to manipulate the tree by looking up leafs by attributes, not by using internal leaf identifiers.

Leaf.level and Leaf.position

The level of a leaf defines its depth in the leaf hierarchy. The root leaf exists at level 0, its children exist at level 1, and so on.

The position of a leaf defines its order within its siblings. The root leaf will have a position 0.

Groot.remove() and Groot.prune()

Removing a leaf from the tree will delete it and raise the asynchronous leaf.deleting event, which may be cancelled.

Pruning a leaf from the tree will delete it, but will raise the synchronous leaf.pruning event which may not be cancelled. Prune leafs when you do not care about confirmation or errors.

Leaf.graft(), Leaf.ungraft(), Leaf.inosculate(), and Leaf.isBeingGrafted

When a new leaf is created within the tree by choosing the "Create" option on the Groot menu, it exists as a "grafted" leaf; that is to say, it is not yet formally part of the tree. After the user enters a label for the leaf, the asynchronous leaf.creating event will be raised. If the event is committed, the leaf will officially be inosculated, and its grafted status (isBeingGrafted) will be revoked. If the event is cancelled the leaf will be ungrafted, which will remove it from the tree entirely.

Leaf.activate(), Leaf.disable(), and Leaf.isActive

The active status of a leaf is purely abstract and has no affect on tree operations other than to change the visual representation of a leaf in the DOM (it will be a lighter color).

TODO

  • polish demo
  • enhance documentation
1.1.0

7 years ago

1.0.7-beta

7 years ago

1.0.6-beta

7 years ago

1.0.5-beta

7 years ago

1.0.4-beta

7 years ago

1.0.3-beta

7 years ago

1.0.2-beta

7 years ago