Security News
Input Validation Vulnerabilities Dominate MITRE's 2024 CWE Top 25 List
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
subdivision
Advanced tools
This library is a collection of simple patterns that help web developers manage the modularization and decoupling of your application components. It is conceptually based on the SharpDevelop addin tree and is built with the "turtles all the way down" methodology (i.e. each concept is using the previous concepts of the library). This library is not a replacement for Backbone/Ember/Angular/Knockout/ as it provides complementary features that can integrate with these frameworks and in general it is designed to play well with others.
If you have any suggestions, improvements or ideas for this library I would be more than happy to hear them and integrate them.
For a full example and guidelines on using subdivision please see the documentation of the example project.
This readme file contains basic usage examples and
details on the full API, including methods,
attributes and helper functions.
To use the library, include dist/subdivision.js
or dist/subdivision.min.js
in your
index.html.
The library has two dependencies - lodash and Promise polyfill for browsers that do not support ES6 Promise function. It should be possible to load this library using AMD or Common.js loaders and in Node.js as long as the dependencies are provided.
Some objects contain private function denoted with prefixed $
sign. You should not normally call these functions.
Changes made to the signature or the return value of these function does not constitute a breaking API change.
The center of this library is the registry. The registry is basically a tree. Each node in the tree has a string name
and it contains an array of items. We refer to these items as addins (SharpDevelop: codons), more on addins in
a following section. Since every node in the tree may contain addins, each node has a path within the tree.
The path contains the names of the nodes in the order they must be traversed to reach the desired node. For example,
to reach a node with the name foo
which is a direct child of the node boo
which is a direct child
of the tree root, we need to use the path boo/foo
. The root node is denoted by the empty string so the path
/boo/foo
is also correct. The path delimiter is /
and it is (currently) hardcoded. The registry is a global
singleton.
The Registry API is encapsulated within the subdivision.registry
object and is as follows:
Tries to get a tree node from the registry tree based on the given axes. axes can be a valid path string or
an array of node names. If the requested node or any of the nodes in the path to the requested node do not exist,
creates the node based on the createIfNotExists argument. If the requested node is not found null
is returned.
var newNode = subdivision.registry.$getNode(['foo','boo'],true);
//creates the node foo under the root node and the node boo
//under the foo node. The last node (i.e. boo) is returned
console.log(newNode === subdivision.registry.$getNode('foo/boo', false)) // true
Forcefully removes all the nodes and their content from the registry. You should never call this function unless you are absolutely sure you know what you are doing.
Determines if the given axis is a valid axis name for the registry (i.e. can be used as a node name). The axis should be a non empty string which does not contain the delimiter.
subdivision.registry.verifyAxis(null); //false
subdivision.registry.verifyAxis(undefined); //false
subdivision.registry.verifyAxis(10); //false
subdivision.registry.verifyAxis(''); //false
subdivision.registry.verifyAxis('foo'); //true
subdivision.registry.verifyAxis('foo/boo'); //false
subdivision.registry.verifyAxis('You can put almost any string here'); //true
Creates a single path out of the given axes or paths. The axes or paths can be any number of separate strings or an array containing strings up to any depth. The proper delimiter is automatically added between the axes and paths as the path is built.
subdivision.registry.joinPath('a', 'bcd', 'ef'); // 'a/bcd/ef'
subdivision.registry.joinPath('a/bcd', 'ef/g'); //'a/bcd/ef/g');
subdivision.registry.joinPath(['a', ['bcd'], ['ef', 'g']]); //'a/bcd/ef/g';
Breaks the given path to its individual axes. Returns an array containing all the axes as strings.
subdivision.registry.breakPath('abv/efg/aaa'); //['abv', 'efg', 'aaa']
Returns true if the tree node for the given path exists in the tree. Note that when you create a node at some path, all the nodes along the path are also created. For example if you insert into the path a/b/c then a, a/b, and a/b/c will be created.
subdivision.registry.$getNode(['foo','boo'],true);
subdivision.registry.pathExists('foo'); //true
subdivision.registry.pathExists('foo/boo'); //true
subdivision.registry.pathExists('boo'); //false
Returns a list of all the immediate subpaths of the given path. For example if the registry contains the paths: a/b/c, a/b/d, a/b/e then calling this function with the path a/b will return ['c','d','e'] (the order is not guaranteed). If the path doesn't exist then null is returned.
subdivision.addAddin('a/b/c');
subdivision.addAddin('a/b/d');
subdivision.addAddin('a/b/e');
subdivision.registry.getSubPaths('a/b'); //['c','d','e']
subdivision.registry.getSubPaths('x/y'); //null
The registry can contain anything in its nodes but it main purpose is to hold addins. "Addin" is just a fancy name for a JavaScript object that has certain properties. In fact, anything this library adds for you into the registry is an addin.
Creates a new addin and shallow copies all the properties from options onto it. If options is a function, shallow copies all the properties from the object returned by the function. An addin contains at least two properties:
id - A unique string that identifies this addin within a path in the registry. Note that two addins may have the same id if they are added into different paths in the registry.
order - A valid order operator (more on ordering addins in the next section).
var myAddin = new subdivision.Addin({myVar:1, myString:"aaa", order:123});
//an id will be auto generated
console.log(myAddin); // {id:"addin0", myVar:1, myString:"aaa", order:123}
var myOtherAddin = new subdivision.Addin(function(){
return {id:"myId"};
});
console.log(myOtherAddin); //{id:"myId", order:0}
Adds the given addin to the given path in the registry. There is no check that the addin was constructed with the
subdivision.Addin
constructor so you can send any object there as long as it has the appropriate signature.
Returns an array containing all the addins in the given path that match the searchCriteria. If searchCriteria is
undefined
, returns all the addins in the path. Set skipSort to true if you don't want to sort the returned addins by
order (more on ordering addins in the next section). The syntax for the searchCriteria is determined by the lodash
filter function as the predicate argument.
subdivision.addAddin('aaa',new subdivision.Addin({name:"Bob"}));
subdivision.addAddin('aaa',new subdivision.Addin({name:"Alice"}));
subdivision.getAddins('aaa'); //[{name:"Bob" ...},{name:"Alice" ...}]
subdivision.getAddins('aaa',{name:"Alice"}); //[{name:"Alice" ...}]
subdivision.getAddins('aaa',function(addin){
return addin.name === "Alice";
}); //[{name:"Alice" ...}]
For each addin that goes into the registry we define an order
property. This field is used to sort the addins within
a given path. The values for the order
property can be one of the following:
Number - The absolute order of the addin, during the sort addins with lower number order will appear first.
">>id" - (After) The addin with this order must be somewhere after the addin with the given id. For example ">>foo" means that the addin with this order value will be after the addin with id === "foo".
">id" - (Immediately after) The addin with this order must be immediately after the addin with the given id. For example ">foo" means that the addin with this order value will be immediately after the addin with id === "foo".
"<<id" - (Before) The addin with this order must be somewhere before the addin with the given id. For example "<<foo" means that the addin with this order value will be before the addin with id === "foo".
"<id" - (Immediately before) The addin with this order must be immediately before the addin with the given id. For example "<foo" means that the addin with this order value will be immediately before the addin with id === "foo".
It is possible to mix the non absolute values by creating a comma separated list. This list must contain at most one "Immediately" order and this order must appear last. For example "<<foo,>>bar,>moo" is valid while "<<foo,>moo,>>bar" is not.
The order is evaluated in priority, first the immediately orders are calculated then the non immediate and the absolute are calculated last.
Sorts the given array of addins with the rules mentioned above. Returns a copy of the array with all the addins sorted (does not change the original array). Note: You should probably never call this yourself, this function is used internally to retrieve addins.
var addins = [];
addins.push(new subdivision.Addin({id: '1', order: 10}));
addins.push(new subdivision.Addin({id: '2', order: 0}));
addins.push(new subdivision.Addin({id: '3', order: 20}));
addins.push(new subdivision.Addin({id: '4', order: 30}));
addins.push(new subdivision.Addin({id: '5', order: 25}));
var result = subdivision.utils.topologicalSort(addins);
//[{id:2},{id:1},{id:3},{id:5},{id:4}]
var addins = [];
addins.push(new subdivision.Addin({id: '1', order: 0}));
addins.push(new subdivision.Addin({id: '2', order: '<<3,<1'}));
addins.push(new subdivision.Addin({id: '3', order: 20}));
addins.push(new subdivision.Addin({id: '4', order: '>>2,>3'}));
var result = subdivision.utils.topologicalSort(addins);
//[{id:2},{id:1},{id:3},{id:4}]
The manifest is a declarative way to define addins via a simple JavaScript object (or JSON). The manifest has the following structure:
var myManifest : {
paths:[
{
path: 'some/path/to/my/addins',
addins: [
{
target:'my.addin.target',
id:'addinId',
order: 123
//any other properties your addin needs
},
{
target:'my.addin.target',
id:'addinId2',
order: '<addinId'
//any other properties your addin needs
}
]
},
{
path: 'some/other/path',
addins: [
{
target:'my.target',
id:'addinId',
order: 123
//any other properties your addin needs
},
{
target:'my.target',
id:'addinId2',
order: 345
//any other properties your addin needs
}
]
}
//etc...
]
}
The same path may appear more than once in a single manifest, the manifest reader will join all the addins for a certain path. The only limitation is that the ids within a given path are unique.
Adds all the addins within the given manifest into the registry.
An addin can be anything including a metadata for creating an actual object. The builders are used to transform
an adding to another object or value by processing the addin content. Each addin may habe a type
property which tells the library which builder is assigned to build that addin.
In the code
subdivision.systemPaths.builders
In the manifest
subdivision/builders
The default builder is added to the registry (via the default manifest, see details below) with the following definition
{
id: 'subdivision.defaultBuilder',
type: 'subdivision.builder',
target: null,
order: 100,
build: function (addin) {
return _.cloneDeep(addin);
}
}
Creates a new Builder from the given options. A builder is a type of addin therefore the addin constructor
is called for the builder with the given options. A builder must have one function called build(addin) which
takes an addin definition and returns anything. The builder should have a type property which defines the type of
addin this builder can build. If the type of a builder is null
then the builder is the default builder for
all addin types. You may manipulate the builders order within the registry by using the order property of the builder
addin.
//Create a builder that can build addins of type monkey
var builder = new subdivision.Builder({
id: 'abc',
order: 3,
type: 'monkey',
build: function (addin) {
return {
food: addin.food
};
}
});
Creates and adds a new builder based on the provided options (see builder constructor). If a builder for the given
target already exists then it is replaced with the new builder only if force is truthy. Returns true if a builder was
added and false otherwise. To add a default builder use target = null
in the provided options.
var options = {
id: 'abc',
order: 3,
target: 'monkey',
build: function () {
}
};
subdivision.addBuilder(options);
var builder = subdivision.getBuilder('monkey'); //returns the builder with id 'abc'
Returns a builder for the given type or the default builder if a builder with the given type was not registered. Pass ''''null'''' as the argument to get the default builder. If no builder is found for the given type and there is no default builder defined then an exception is thrown.
var options = {
id: 'hello',
target: null,
build: function () {
}
};
subdivision.addBuilder(options, true); //Replace the default builder with a new one
var builder = subdivision.getBuilder(null);
var builder2 = subdivision.getBuilder('no such type');
console.log(builder === builder2); //true
Builds all the addins in the given path by calling the build function for each addin separately based on its type. If searchCriteria is specified the syntax for the is determined by the lodash filter function as the predicate argument. Set skipSort to true if you don't want to sort the returned addins by order.
subdivision.addBuilder({
id: 'a',
target: 'monkey',
build: function (addin) {
return addin.id;
}
});
subdivision.addAddin('aaa', {id: '1', type: 'monkey', order: 1});
subdivision.addAddin('aaa', {id: '2', type: 'monkey', order: 2});
var items = subdivision.build('aaa'); //['1','2']
Same as subdivision.build
but returns a Promise that resolves with the result array. Does not assume that any of the
builders involved in building the given path are asynchronous. Use this variant if any of the builders for the
path are asynchronous.
Builds all the addins in the given path by calling the build function for each addin separately based on its type but also
builds all the sub-paths of the given path. When an addin of a sub path is built it is added to the items property of the
parent addin identified by the addin id. For example, if in the path "aaa" there is an addin with id "myId" and in the path
"aaa/myId" there is an addin with id "innerAddinId" then the result of building a tree on the path "aaa" with this
function is the built addin "myId" and in its items property is the built addin "innerAddinId" (see example below).
The default items property is $items
but it can be changed by specifying the itemsProperty
property on
the addin definition.
subdivision.addBuilder({
id: 'a',
target: 'monkey',
build: function (addin) {
return {id: addin.id};
}
});
subdivision.addAddin('aaa', {id: '1', type: 'monkey', order: 1});
subdivision.addAddin('aaa', {id: '2', type: 'monkey', order: 2});
subdivision.addAddin('aaa', {id: '3', type: 'monkey', order: 3, itemsProperty: 'stuff'});
subdivision.addAddin(subdivision.registry.joinPath('aaa', '2'), {id: '1', type: 'monkey', order: 2});
subdivision.addAddin(subdivision.registry.joinPath('aaa', '2'), {id: '2', type: 'monkey', order: 3});
subdivision.addAddin(subdivision.registry.joinPath('aaa', '3'), {id: '1', type: 'monkey', order: 3});
var items = subdivision.buildTree('aaa');
// [{
// id:'1',
// $items:[{id:'1'},{id:'2'}]
// },
// {
// id:'2'
// },
// {
// id:'3',
// stuff:[{id:'1'}]
// }]
Adds all the builders registered to the builders path in the registry into the internal
builders list. You normally don't need to call this function as it is called automatically
at the initialization of the library. Functions like getBuilder
and addBuilder
work with the internal structure and not the path.
Forcefully clears all the builders from the internal structures. You should never call this function unless you are absolutely sure you know what you are doing.
All the concepts in the following paragraphs are completely optional. They are here to help you organize your code into well known patterns with the help of this library.
Services are singleton objects that are accessible from anywhere within the code without directly referencing them
(similar to Angular.js services). The services are identified by name
because they are built hierarchically.
For example if a service with the name FooService is registered and it has a method bar, and another service with the
name FooService is registered and it has the method baz then the final service will have both bar and baz method.
This is achieved simply by creating prototypical inheritance between all service instances with the same name in the order
they appear in the registry (again, order is key).
In the code
subdivision.systemPaths.services
In the manifest
subdivision/services
Creates a new service from the given options and assigns the given prototype as the prototype of the created service. Normally you don't need to use the service constructor.
Each service has two builtin properties:
$vent - An events aggregator (equivalent to Backbone.Events object) which is used by the service to trigger events. You should use this event aggregator for any events emitted by your service instance.
$next - The next service in the chain (in current implementation equivalent to __proto__
but you should not rely on that).
Use this property to traverse the services chain. This is useful when you need to access your own service explicitly which must be
uniquely identified by id
as any subdivision addin.
Returns the service with the given name or undefined if no service with the given name exists.
var routingService = subdivision.getService('Routing Service');
routingService.doSomething();
Adds a service with the given name to the end of the services chain based on the given options. If the override flag is set to true, the existing service chain with the given name is completely removed and a new chain is started with the created service. Note that if some module is holding a service instance, that instance will not be changed.
You should never hold on to an instance of a service and always call subdivision.getService()
when your code needs the service.
This call is very cheap!
When subdivision first starts it calls the subdivision.buildServices
method. This method builds all the services registered on the services
path in the registry. For each service in the registry it first calls before:service:initialized event on subdivision.vent
callback(serviceName) then tries to run the service initialize method if one exists. This method may return subdivision.Promise.
When all the services are initialized it will trigger the after:service:initialized event on subdivision.vent
callback(serviceName, service).
If your service needs access to some other service after it was initialized but before the application starts, you should register to this
event as it guarantees that all services were initialized when called.
Be sure you have NodeJS and NPM installed on your system
Run npm install
to install Karma and Mocha
From the project folder, run npm run-script test1
to execute the unit tests
grunt build
If all went well, the appropriate files should be generated in the dist directory
##License (MIT License) Copyright (c) 2014 Boris Kozorovitzky,
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FAQs
A simple modularization layer for your JavaScript application
The npm package subdivision receives a total of 2 weekly downloads. As such, subdivision popularity was classified as not popular.
We found that subdivision demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.