Permalink
Fetching contributors…
Cannot retrieve contributors at this time
468 lines (294 sloc) 13 KB

Panzer

Build Status

version 0.4.0 by Bemi Faison

A comprehensive node-tree solution, for smart data.*

Description

Panzer is a JavaScript utility for defining classes that normalize data into node-trees, then implement hierarchical and/or navigation-based logic. Use Panzer to author classes that codify the intent of, and expose relationships in, structured data.

Below is a trivial example, using Panzer to render nested ULs.

var
  ULMaker = Panzer.create(),
  makerPkg = ULMaker.pkg('maker');

makerPkg.badKey = function (name, value) {
  return typeof value !== 'string' || typeof value !== 'object';
};

* Read The Cathedral and the Bazaar, to understand why "Smart data structures and dumb code works a lot better than the other way around." -- Eric S. Raymond

Usage

Because the problem domain is vast (i.e., everything can be described with a data structure), Panzer offers a bevy of features for you to use a la carte. The following are either covered in the Usage section below, pending documentation, or amongst Panzer's 400+ unit tests:

  • Current state & position
  • Delegate classes and configurations
  • Event object reflection
  • Navigation control interface
  • Navigation loop & events life-cycle
  • Nodal behavior & logic
  • Node & attribute parsing
  • Promise-enabled parsing, initialization, and navigation
  • Prototypal inheritance, inspection and reflection
  • Reusable instances

Customizing with Packages

Parsing Values

Identifying Attributes
Ignoring Object Members
Processing Nodes
Asynchronous Node Processing

Navigating the Node-Tree

First, create a Panzer class.

var BasicTree = Panzer.create();

In order to access and extend Panzer's functionality, we need to define a package. (Otherwise, our instances won't do much.) Let's register a new package by name.

var myPkgDef = BasicTree.pkg('my first package');

The returned value is the package-definition, a function with special members that hook into our class. Many hooks are available, but we'll prototype an instance method now.

myPkgDef.fn.onward = function () {
    var
        // get the corresponding package-instance
        tree = myPkgDef(this),
        // alias the tank that controls our tree's "pointer"
        tank = tree.tank,
        // reference the current node
        currentNode = tree.nodes[tank.currentIndex],
        // reference a sibling or child node, if available
        nextNode = tree.nodes[currentNode.nextIndex || currentNode.firstChildIndex];
    // if there is a nextNode and we've successfully navigated to it...
    if (nextNode && tank.go(nextNode.index)) {
        // return the value of (what is now) the current node
        return nextNode.value;
    }
    // (otherwise) flag that no node is next
    return false;
};

Though simple enough, there is a lot going on in our method. Nevertheless, we can now "walk" the left-branch of any data structure.

var
    myTree = new BasicTree({hello: 'world'}),
    nodeValue, lefties = [];
while (nodeValue = myTree.onward()) {
    lefties.push(nodeValue);
}
//
// lefties[0] -> {hello: 'world'}
// lefties[1] -> 'world'
//

Understanding the package api is key to getting the most from your class. However, in lieu of full documentation, please see the test suite for greater insight. Thanks for your patience!

API

This section serves as reference documentation to using the Panzer module. The module exports a Panzer namespace.

Instance methods are prefixed with a pound-symbol (#). Instance properties are prefixed with an at-symbol (@). Static members (both properties and methods) are prefixed with a double-colon (::).

Panzer::create()

Create a unique Panzer class.

var Klass = Panzer.create();

Returns a constructor function, informally called a Klass.

Panzer::version

The SemVer compatible version string.

Panzer.version;

Klass

Instantiate a Klass instance.

new Klass([source [, config]]);
  • source: (mix) A value to be parsed into nodes.
  • config: (object) Used by packages, during initialization.

Returns a promise when invoked without new. The promise resolves with the initialized Klass instance.

The Klass prototype inherits from it's package (PkgDef.fn).

Klass::id

A unique numeric identifier.

Klass.id;

Klass::pkg()

Retrieve, create, and list packages.

  1. Returns an array of package names. js Klass.pkg();
  2. Returns a new or existing package. js Klass.pkg(name);
    • name: (string) An alphanumeric string.

Throws when given anything but an alphanumeric string.

A package is a delegate class, event subscriber, and collection of tree parsing rules and logic. A Klass may many packages.

Klass@ready

A promise that resolves after this instance has initialized.

klass.ready;

This instance property is a thenable, from which you may use Promise.then.

The promise resolves with the initialized Klass instance.

Package::attrKey

A rule that determines whether an object member should be parsed as a node attribute.

PkgDef.attrKey = rule;

By default, rule is false and may be any falsy value.

When a string, during parsing, member keys that begin with this rule become node attributes. (The comparison is case-sensitive.)

When a regular expression, during parsing, member keys that match this rule become node attributes.

When a function, during parsing, returning a truthy value makes this member a node attribute. Functions receive two arguments, the member name and value. (The first member name is an empty string.)

Package::badKey

A rule that determines whether an object member should not be excluded from the node-tree.

PkgDef.badKey = rule;

By default, rule is false and may be any falsy value.

When a string, during parsing, member keys that begin with this rule are excluded from the node-tree. (The comparison is case-sensitive.)

When a regular expression, during parsing, member keys that match this rule are excluded from the node-tree.

When a function, during parsing, returning a truthy value excludes this member from the node-tree. Functions receive two arguments, the member name and value. (The first member name is an empty string.)

Package::cloneable

Indicates when a Klass instance's nodes may be copied for a new instance.

PkgDef.cloneable = setting;

By default, setting is true and may be any truthy value.

With more than one package, all must use a truthy value. If not, the new proxy object will parse the given proxy object's original source value.

Package::getSuper()

Retrieve the same-name member from an older Package's Klass prototype.

PkgDef.getSuper(name);
  • name (string) name of the member to retrieve.

Returns undefined when the member is not defined.

Package::index

The zero-index order that this package was defined.

PkgDef.index;

Package::off()

Unsubscribe from tank events.

PkgDef.off([name [, callback]]);
  • name - (string) The name of the event to stop listening.
  • callback - (function) The method that was previously subscribed.

Returns the same package definition object.

Removes all subscribers (via this package), when called without arguments.

Removes all subscribers to the given event (via this package), if callback is omitted.

Removes the given callback from the given event (subscribed via this package), when called with both arguments.

Package::on()

Subscribe to tank events.

PkgDef.on([name [, callback]]);

Returns the same package definition object.

  • name - (string|strings) The event name (or array of event names) to observe.
  • callback - (function) The method to invoke when the event is triggered.

Package::prepNode

Determines how and which object members are parsed into nodes.

PkgDef.prepNode = funcRef;

funcRef is a function that is called during instantiation, and returns what will be the node's value. Within the function, this is the global/window object. (Members of the returned value are further parsed.) If a Promise is returned, the resolved value is used.

The first argument passed to the specified function is the object's member's name, as a string. This argument is an empty string, when first called, per instantiation.

The second argument passed to the specified function is the object's member's value. This argument is the value passed to the Klass function, when first called, per instantiation.

Package::fn

An inherited chain of the Klass prototype.

PkgDef.fn;

Like any prototype Members of fn are available to all Klass instances. Precedence is given to the most recently registered Package.

Package@nodes

An array of nodes.

pkg.nodes;

Each node is an object that represents a position in the node-tree, along with a value - parsed from the value parsed from the Klass instance.

This array is empty (until initialization), while node-parsing and/or initialization is delayed.

describing their structure and position, and value, respectively.

Each node is an Object instance with the following properties:

* **attrs** :
* **childIndex** :
* **children** :
* **depth** :
* **firstChildIndex** :
* **index** :
* **lastChildIndex** :
* **nextIndex** :
* **parentIndex** :
* **path** :
* **previousIndex** :
* **value** :

Package@pkgs

Package@proxy

Package@tank

Tank#go()

Tank@active

Tank@id

Tank@cc

Tank@index

Tank@target

Tank#queue()

Tank@queued

Tank#block()

Tank@blocked

Event

Event@id

Event@order

Event@tid

Event@trip

Event@leg

Event@command

Event@type

Event@index

Event@path

Event@depth

Event@stack

Event@trail

Event@proxy

Event@tally

Installation

Panzer works within, and is intended for, modern JavaScript environments. It is available on bower, component and npm as a UMD module (supporting both CommonJS and AMD loaders).

If Panzer isn't compatible with your favorite runtime, please file an issue or - better still - make a pull-request.

Dependencies

Panzer has no module dependencies. Panzer should work wherever these ECMAScript 5 & 6 features are native or polyfilled:

Web Browsers

Use a <SCRIPT> tag to load the panzer.min.js file in your web page. Doing so, adds Panzer to the global scope.

  <script type="text/javascript" src="path/to/panzer.min.js"></script>
  <script type="text/javascript">
    // ... Panzer dependent code ...
  </script>

Note: The minified file was compressed by Closure Compiler.

Package Managers

  • npm install panzer
  • component install bemson/panzer
  • bower install panzer

AMD

Assuming you have a require.js compatible loader, configure an alias for the Panzer module (the term "panzer" is recommended, for consistency). The panzer module exports a module namespace.

require.config({
  paths: {
    panzer: 'my/libs/panzer'
  }
});

Then require and use the module in your application code:

require(['panzer'], function (Panzer) {
  // ... Panzer dependent code ...
});

Note: Prefer AMD optimizers, like r.js, over loading the minified file.

License

Panzer is available under the terms of the MIT-License.

Copyright 2017, Bemi Faison