Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
55 changes: 28 additions & 27 deletions nested-set-model.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
// nested set model (l;r) parent search

var NestedSetModel = function(model, options) {

options = options || {};
this.leftKey = options.left || 'left';
this.rightKey = options.right || 'key';


var self = this;
this.model = [];
this.index = {};

var self = this;
this.options = options || {};
this.options.left = this.options.left || 'left';
this.options.right = this.options.right || 'right';

// sort the set for deterministic order
model.sort(function(a, b)
{
return a[self.leftKey] - b[self.leftKey];
return a[self.options.left] - b[self.options.left];
});

// create an index
Expand All @@ -23,15 +22,15 @@ var NestedSetModel = function(model, options) {
continue;
}

this.index[model[index][this.rightKey]] = index;
this.index[model[index][this.options.right]] = index;
}

for(var entry in model) {
if (!model.hasOwnProperty(entry)) {
continue;
}

var node = new NestedSetModelNode(model[entry], model, this.index);
var node = new NestedSetModelNode(model[entry], model, this.index, this.options);
this.model.push(node);
}

Expand Down Expand Up @@ -86,14 +85,15 @@ NestedSetModel.prototype.find = function(partial, strict) {
if (!this.model.hasOwnProperty(key)) {
continue;
} else if (this.compareNodes(this.model[key], partial, strict)) {
return new NestedSetModelNode(this.model[key], this.model, this.index);
return new NestedSetModelNode(this.model[key], this.model, this.index, this.options);
}
}
}

var NestedSetModelNode = function(node, model, index) {
var NestedSetModelNode = function(node, model, index, options) {
this.model = model;
this.index = index;
this.options = options;

var self = this;
Object.keys(node).forEach(function(prop) {
Expand All @@ -102,26 +102,28 @@ var NestedSetModelNode = function(node, model, index) {
}

NestedSetModelNode.prototype.parents = function() {
var parents = [];

var self = this;

this.model.map(function(node) {
if (self[self.model.leftKey] > node[self.model.leftKey] && self[self.model.rightKey] < node[self.model.rightKey]) {
parents.push(new NestedSetModelNode(node, self.model, self.index));
return this.model.map(function(node) {
if (self[self.options.left] > node[self.options.left] && self[self.options.right] < node[self.options.right]) {
return new NestedSetModelNode(node, self.model, self.index, self.options);
}
return null;
}).filter(function(item) {
return item !== null;
});

return parents;
}

NestedSetModelNode.prototype.descendants = function() {
var descendants = [];
var num_items = Math.floor((this.model.rightKey - this.model.leftKey) / 2);
var num_items = Math.floor((this[this.options.right] - this[this.options.left]) / 2);

for(var right in this.index) {
if (right < this.model.rightKey && right > this.model.leftKey) {
if (right < this[this.options.right] && right > this[this.options.left]) {
var node = this.model[this.index[right]];
descendants.push(new NestedSetModelNode(node, this.model, this.index));
descendants.push(new NestedSetModelNode(node, this.model, this.index, this.options));
}
}

Expand All @@ -130,38 +132,37 @@ NestedSetModelNode.prototype.descendants = function() {

NestedSetModelNode.prototype.children = function() {
var children = [];
var right = this.model.rightKey - 1;
var right = this[this.options.right] - 1;

while(true) {
if (right === this.model.leftKey) {
if (right === this[this.options.left]) {
break;
}

var child = this.model[this.index[right]];
children.push(new NestedSetModelNode(child, this.model, this.index));
right = child.model.leftKey - 1;
children.push(new NestedSetModelNode(child, this.model, this.index, this.options));
right = child[this.options.left] - 1;
}

return children.reverse();
}


NestedSetModelNode.prototype.isLeaf = function() {
return this.model.rightKey - this.model.leftKey === 1;
return this[this.options.right] - this[this.options.left] === 1;
}

NestedSetModelNode.prototype.isParent = function() {
return !this.isLeaf();
}

NestedSetModelNode.prototype.isDescendant = function() {
return this.model.leftKey > 0 && this.model.rightKey < (this.model.length * 2);
return this[this.options.left] > 0 && this[this.options.right] < (this.model.length * 2);
}

// bootstrap
if (typeof define !== 'undefined') {
define('NestedSetModel', NestedSetModel);
define('NestedSetModel', NestedSetModel);
} else if (typeof module !== 'undefined') {
module.exports = NestedSetModel;
}

32 changes: 19 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
{
"name": "nestedjs",
"version": "1.1.0",
"description": "A lightweight library for working with the Nested Set Model",
"author": "Ferdi Schmidt",
"main": "nested-set-model.js",
"repository": {
"url": "https://github.com/uitgewis/nestedjs"
},
"license": "BSD-2-Clause",
"keywords": [
"nested set",
"nested set model"
]
"name": "nestedjs",
"version": "1.1.0",
"description": "A lightweight library for working with the Nested Set Model",
"author": "Ferdi Schmidt",
"main": "nested-set-model.js",
"scripts": {
"test": "mocha"
},
"repository": {
"url": "https://github.com/uitgewis/nestedjs"
},
"license": "BSD-2-Clause",
"keywords": [
"nested set",
"nested set model"
],
"devDependencies": {
"mocha": "^3.0.2"
}
}
38 changes: 38 additions & 0 deletions test/custom-keys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"id": 1,
"title": "root",
"lf": 1,
"rg": 12
},
{
"id": 4,
"title": "child",
"lf": 3,
"rg": 8
},
{
"id": 2,
"title": "parent",
"lf": 2,
"rg": 9
},
{
"id": 3,
"title": "middle",
"lf": 10,
"rg": 11
},
{
"id":5,
"title": "child",
"lf": 4,
"rg": 7
},
{
"id":6,
"title": "leaf",
"lf": 5,
"rg": 6
}
]
38 changes: 38 additions & 0 deletions test/fixture.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"id": 1,
"title": "root",
"left": 1,
"right": 12
},
{
"id": 4,
"title": "child",
"left": 3,
"right": 8
},
{
"id": 2,
"title": "parent",
"left": 2,
"right": 9
},
{
"id": 3,
"title": "middle",
"left": 10,
"right": 11
},
{
"id":5,
"title": "child",
"left": 4,
"right": 7
},
{
"id":6,
"title": "leaf",
"left": 5,
"right": 6
}
]
62 changes: 62 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
var assert = require('assert');
var NestedSetModel = require('./../nested-set-model');

var fixture = require('./fixture.json');
var customKeyFixture = require('./custom-keys.json');

describe('NestedSetModel', function() {
describe('default configuration', function() {
it('should properly recognize leaf nodes', function() {
var model = new NestedSetModel(fixture);
assert(!model.find({ title: 'root' }).isLeaf())
assert(!model.find({ title: 'parent' }).isLeaf())
assert(!model.find({ title: 'child', left: 3 }).isLeaf())
assert(!model.find({ title: 'child', left: 4 }).isLeaf())
assert(model.find({ title: 'middle' }).isLeaf());
assert(model.find({ title: 'leaf' }).isLeaf());
});
it('should properly recognize parent nodes', function() {
var model = new NestedSetModel(fixture);
assert(model.find({ title: 'root' }).isParent())
assert(model.find({ title: 'parent' }).isParent())
assert(model.find({ title: 'child', left: 3 }).isParent())
assert(model.find({ title: 'child', left: 4 }).isParent())
assert(!model.find({ title: 'middle' }).isParent());
assert(!model.find({ title: 'leaf' }).isParent());
});
it('should return the correct set of children', function() {
var model = new NestedSetModel(fixture);
assert.strictEqual(model.find({ title: 'root' }).children().length, 2);
assert.strictEqual(model.find({ title: 'middle' }).children().length, 0);
assert.strictEqual(model.find({ title: 'child', left: 3 }).children().length, 1);
assert.strictEqual(model.find({ title: 'child', left: 4 }).children().length, 1);
assert.strictEqual(model.find({ title: 'leaf' }).children().length, 0);
});
it('should return the correct set of descendants', function() {
var model = new NestedSetModel(fixture);
assert.strictEqual(model.find({ title: 'root' }).descendants().length, 5);
assert.strictEqual(model.find({ title: 'middle' }).descendants().length, 0);
assert.strictEqual(model.find({ title: 'child', left: 3 }).descendants().length, 2);
assert.strictEqual(model.find({ title: 'child', left: 4 }).descendants().length, 1);
assert.strictEqual(model.find({ title: 'leaf' }).descendants().length, 0);
});
it('should return the correct set of parents', function() {
var model = new NestedSetModel(fixture);
assert.strictEqual(model.find({ title: 'root' }).parents().length, 0);
assert.strictEqual(model.find({ title: 'middle' }).parents().length, 1);
assert.strictEqual(model.find({ title: 'child', left: 3 }).parents().length, 2);
assert.strictEqual(model.find({ title: 'child', left: 4 }).parents().length, 3);
assert.strictEqual(model.find({ title: 'leaf' }).parents().length, 4);
});
});
describe('custom configuration', function() {
it('should properly walk the tree using the given keys', function() {
var model = new NestedSetModel(customKeyFixture, {left: 'lf', right: 'rg'});
assert(!model.find({ title: 'child' }).isLeaf())
assert(model.find({ title: 'leaf' }).isLeaf());
assert(model.find({ title: 'child' }).parents().length);
assert(model.find({ title: 'child' }).children().length);
assert(model.find({ title: 'child' }).descendants().length);
});
});
});