DoneJS StealJS jQuery++ FuncUnit DocumentJS
3.14.1
5.0.0 4.3.0 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-compute
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-stream
      • can-define-stream-kefir
      • can-event
      • can-event/async/async
      • can-event/batch/batch
      • can-event/lifecycle/lifecycle
      • can-kefir
      • can-list
      • can-map
      • can-map-backup
      • can-map-define
      • can-observation
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
    • Data Modeling
      • can-connect
      • can-connect-cloneable
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-signalr
      • can-fixture
      • can-fixture-socket
      • can-ndjson-stream
      • can-set
    • Views
      • can-component
      • can-ejs
      • can-element
      • can-react-component
      • can-stache
      • can-stache/helpers/route
      • can-stache-bindings
      • can-stache-converters
      • can-view-autorender
      • can-view-callbacks
      • can-view-href
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • react-view-model
      • react-view-model/component
      • steal-stache
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-globals
      • can-key-tree
        • types
          • KeyTreeCallbacks
        • prototype
          • add
          • delete
          • get
          • getNode
          • isEmpty
          • size
      • can-make-map
      • can-parse-uri
      • can-string
      • can-string-to-any
      • can-util
      • can-zone
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-control
      • can-dom-events
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-jquery
    • Data Validation
      • can-define-validate-validatejs
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-namespace
      • can-reflect
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-key-tree

  • npm package badge
  • Star
  • Edit on GitHub

Store values in a tree structure.

new KeyTree(treeStructure [, callbacks])

Create a tree instance that stores values on nodes of instances of the types specified in treeStructure. For example, the following creates a keyTree whose root node will be an Object and child nodes will be Arrays:

const cities = new KeyTree( [ Object, Array ] );

Once you've created a keyTree, you can add, delete and get values from it. The following "Chicago" to the tree:

cities.add( [ "Illinois", "Chicago" ] );

Internally, cities structure looks like:

{
    "Illinois": [ "Chicago" ]
}

Parameters

  1. treeStructure {Array<function>}:

    An array of constructor functions. An instance of each type will be used as the nodes of the tree. All of the types except the last should be map-like. The last type should be isListLike. For example:

    new KeyTree( [ Object, Map, Array ] );   // OK
    new KeyTree( [ Object, Set ] );          // OK
    new KeyTree( [ Object, Array, Array ] ); // WRONG
    new KeyTree( [ Array, Object ] );        // WRONG
    

    Instances of the types are created with new Type() for built-in types like Array and Object. User defined types are created with new Type(parentKey) where parentKey is the name of the key of the parent node that points to the node being created.

    can-reflect is used to access and manipulate each type, allowing rich behaviors as demonstrated in the Advanced Use below. The following lists the reflections used by can-key-tree:

    • getKeyValue - to follow a branch from one node to its child node
    • setKeyValue - to create a branch from one node to a child node
    • deleteKeyValue - to delete a branch from one node to a child node
    • [can-reflect/shape.size] - to return the number of child nodes
    • [can-reflect/shape.each] - to recursively get child nodes.
    • addValues - to add values to the leaf list-like nodes.
    • removeValues - to remove values from the leaf list-like nodes.
    • isMoreListLikeThanMapLike - to know if the last node type is list-like or not.
  2. callbacks {KeyTreeCallbacks}:

    An object containing callbacks onFirst and/or onEmpty.

Returns

{can-key-tree}:

An instance of KeyTree.

Use Cases

can-key-tree can be used for a wide variety of purposes. In CanJS, it is used extensively for storing event handlers organized by key and [can-queues event queue]. Its use of can-reflect means it can simplify complex patterns such as implementing event delegation as shown in the Advanced Use section below.

When you are adding, removing, and retrieving items from a nested structure, can-key-tree can likely help.

Use

Import the KeyTree constructor from can-key-tree:

import KeyTree from  "can-key-tree";

Create an instance of KeyTree with an array of types. An instance of each type will be used as the nodes of the tree. The following creates a tree structure 3 levels deep:

const keyTree = new KeyTree( [ Object, Object, Array ], { onFirst, onEmpty } );

Once you've created a keyTree, you can .add, .delete and .get values from it.

.add(keys)

The following adds three handlers:

function handler1() {}
function handler2() {}
function handler3() {}

keyTree.add( [ "click", "li", handler1 ] );
keyTree.add( [ "click", "li", handler2 ] );
keyTree.add( [ "click", "span", handler3 ] );

The keyTree data structure will look like:

{
    "click": {
        "li": [ handler1, handler2 ],
        "span": [ handler3 ]
    }
}

.get(keys)

To get all the li click handlers, use .get:

keyTree.get( [ "click", "li" ] ); //-> [handler1, handler2]

To get all click handlers, you can also use .get:

keyTree.get( [ "click" ] ); //-> [handler1, handler2, handler3]

.delete(keys)

To delete a handler, use .delete:

keyTree.delete( [ "click", "li", handler1 ] );

The keyTree data structure will look like:

{
    "click": {
        "li": [ handler2 ],
        "span": [ handler3 ]
    }
}

To delete the remaining click handlers:

keyTree.delete( [ "click" ] );

The keyTree data structure will look like:

{}

Advanced Use

Often, when a node is created, there needs to be some initial setup, and when a node is empty, some teardown.

This can be achieved by creating custom types. For example, perhaps we want to build an event delegation system where we can delegate from an element like:

eventTree.add( [ document.body, "click", "li", handler ] );

And remove that handler like:

eventTree.delete( [ document.body, "click", "li", handler ] );

We can do that as follows:

// Create an event handler type.
const Delegator = function( parentKey ) {

    // Custom constructors get called with their parentKey.
    // In this case, the `parentKey` is the element we will
    // delegate from.
    this.element = parentKey;

    // the nested data `{click: [handlers...], dblclick: [handlers...]}`
    this.events = {};

    // the callbacks added for each handler.
    this.delegated = {};
};
canReflect.assignSymbols( Delegator.prototype, {

    // when a new event happens, setup event delegation.
    "can.setKeyValue": function( eventName, handlersBySelector ) {

        this.delegated[ eventName ] = function( ev ) {
            canReflect.each( handlersBySelector, function( handlers, selector ) {
                let cur = ev.target;
                do {
                    if ( cur.matches( selector ) ) {
                        handlers.forEach( function( handler ) {
                            handler.call( cur, ev );
                        } );
                    }
                    cur = cur.parentNode;
                } while ( cur && cur !== ev.currentTarget );
            } );
        };
        this.events[ eventName ] = handlersBySelector;
        this.element.addEventListener( eventName, this.delegated[ eventName ] );
    },
    "can.getKeyValue": function( eventName ) {
        return this.events[ eventName ];
    },

    // when an event gets removed, teardown event delegation and clean up.
    "can.deleteKeyValue": function( eventName ) {
        this.element.removeEventListener( eventName, this.delegated[ eventName ] );
        delete this.delegated[ eventName ];
        delete this.events[ eventName ];
    },

    // we need to know how many items at this node
    "can.getOwnEnumerableKeys": function() {
        return Object.keys( this.events );
    }
} );

// create an event tree that stores:
// - "element being delegated" ->
//   - A "delegator" instance for an event ->
//     - The "selectors" we are delegating ->
//       - The handlers to call
const eventTree = new KeyTree( [ Map, Delegator, Object, Array ] );


// to listen to an event:
function handler() {
    console.log( "an li clicked" );
}

eventTree.add( [ document.body, "click", "li", handler ] );

// to stop listening:
eventTree.delete( [ document.body, "click", "li", handler ] );

// to stop listening to all clicks on the body:
eventTree.delete( [ document.body, "click" ] );

// to stop listening to all events on the body:
eventTree.delete( [ document.body ] );

How it works

can-key-tree's' commented source can be found here.

On a high level, KeyTree instances maintain a this.root map-type whose keys point to instances of other map-types. Those keys eventually point to some list-type instance that contains a list of the leaf-values added to the tree. KeyTree's methods walk down the tree structure and use can-reflect to perform operations on it.

Watch this overview video on can-key-tree.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 3.14.1.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news