diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/nested-set-model.js b/nested-set-model.js index 8b364c5..a6c6af0 100644 --- a/nested-set-model.js +++ b/nested-set-model.js @@ -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 @@ -23,7 +22,7 @@ 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) { @@ -31,7 +30,7 @@ var NestedSetModel = function(model, options) { 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); } @@ -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) { @@ -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)); } } @@ -130,16 +132,16 @@ 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(); @@ -147,7 +149,7 @@ NestedSetModelNode.prototype.children = function() { 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() { @@ -155,13 +157,12 @@ NestedSetModelNode.prototype.isParent = function() { } 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; } - diff --git a/package.json b/package.json index c2f0a93..5460a7c 100644 --- a/package.json +++ b/package.json @@ -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" + } } diff --git a/test/custom-keys.json b/test/custom-keys.json new file mode 100644 index 0000000..de4375f --- /dev/null +++ b/test/custom-keys.json @@ -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 + } +] \ No newline at end of file diff --git a/test/fixture.json b/test/fixture.json new file mode 100644 index 0000000..f61b0f9 --- /dev/null +++ b/test/fixture.json @@ -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 + } +] \ No newline at end of file diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..441930e --- /dev/null +++ b/test/test.js @@ -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); + }); + }); +});