-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue 4 of cosmoz-treenode-navigator #1
Changes from 27 commits
92c303e
9dca38a
b19a36f
b9fb2ff
f7d2fdb
dea0b9a
760b426
1c88582
cd64e21
5a2064e
f8b75df
5af285e
d8e9ffd
e878ec1
00bb3de
44ca085
13f837a
4174ae1
47dfbf0
16dcf27
90122a3
fa33008
e465ec6
9e5078d
3c5318f
23ab530
5308aae
20a3aad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
bower_components/ | ||
node_modules/ | ||
debug.log | ||
.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,23 @@ | ||
<link rel="import" href="../polymer/polymer.html"> | ||
<link rel="import" href="cosmoz-tree.html"> | ||
|
||
<!-- | ||
Navigator through object with treelike datastructure and default settings. | ||
|
||
@demo demo/index.html | ||
--> | ||
<script> | ||
'use strict'; | ||
|
||
// Needed for iron-component-page. | ||
Polymer({ is: 'cosmoz-default-tree' }); | ||
|
||
window.Cosmoz = window.Cosmoz || {}; | ||
|
||
function _objectValues(obj) { | ||
if (!obj) { | ||
return; | ||
} | ||
return Object.keys(obj).map(function (key) { | ||
return obj[key]; | ||
}); | ||
|
@@ -14,143 +27,249 @@ | |
* Cosmoz.DefaultTree | ||
* | ||
* @constructor | ||
*/ | ||
Cosmoz.DefaultTree = function (treeData) { | ||
* | ||
* @param {object} treeData (The tree object.) | ||
* @param {object} options (Tree options.) | ||
* @param {string} options.childProperty ["children"] (The name of the property a search should be based on. e.g. "name") | ||
* @param {string} options.propertyName ["name"] (The name of the property a search should be based on. e.g. "name") | ||
* @param {string} options.pathStringSeparator ["/"] (The string the path should get separated with.) | ||
* @param {string} options.pathLocatorSeparator ["."] (The string which separates the path segments of a path locator.) | ||
*/ | ||
Cosmoz.DefaultTree = function (treeData, options) { | ||
Cosmoz.Tree.apply(this, arguments); | ||
this._treeData = treeData; | ||
this._roots = _objectValues(treeData); | ||
|
||
options = options || {}; | ||
this.pathLocatorSeparator = options.pathLocatorSeparator || '.'; | ||
this.pathStringSeparator = options.pathStringSeparator || '/'; | ||
this.childProperty = options.childProperty || 'children'; | ||
this.searchProperty = options.searchProperty || 'name'; | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype = Object.create(Cosmoz.Tree.prototype); | ||
|
||
Cosmoz.DefaultTree.prototype.getNodeByProperty = function (propertyName, propertyValue) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As in comment, needs to stay. |
||
if (propertyName === undefined || propertyValue === undefined) { | ||
/** | ||
* Searches a (multi root) node and matches nodes based on a property and a value. | ||
* @return {object} - The first found node. | ||
* @param {string} propertyName (The name of the property the match should be based on. e.g. "name") | ||
* @param {string} propertyValue (The value of the property the match should be based on. e.g. "Peter") | ||
* @param {array} nodes [this._roots] (The objects the search should be based on.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getNodeByProperty = function (propertyValue, propertyName, nodes) { | ||
if (propertyValue === undefined) { | ||
return; | ||
} | ||
// Defaults | ||
nodes = nodes || this._roots; | ||
propertyName = propertyName || this.searchProperty; | ||
|
||
if (propertyName === 'pathLocator') { | ||
return this._getNodeByPathLocator(propertyValue); | ||
} | ||
return this.findNode(propertyValue, propertyName, nodes); | ||
}; | ||
|
||
return this._searchAllRoots(propertyName, propertyValue); | ||
/** | ||
* Searches a (multi root) node and matches nodes based on a property and a value. | ||
* @return {array} - All found nodes. | ||
* @param {string} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") | ||
* @param {string} propertyValue (The value of the property the match should be based on. e.g. "Peter") | ||
* @param {boolean} exact [true] (If the search should be executed exact or flaw. true wouldn't match "Pet") | ||
* @param {object} nodes [this._treeData] (The nodes the search should be based on.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype.searchNodes = function (propertyValue, nodes, exact, propertyName) { | ||
var options = { | ||
propertyName: propertyName || this.searchProperty, | ||
exact: exact !== undefined ? exact : true, | ||
firstHitOnly: false | ||
}; | ||
return this._searchNodes(propertyValue, options, nodes); | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype.getPath = function (node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't think we can drop getPath |
||
if (node === undefined) { | ||
return; | ||
} | ||
return this._getPathByPathLocator(node.pathLocator); | ||
/** | ||
* Searches a (multi root) node and matches nodes based on a property and a value. | ||
* @return {object} - The first found node. | ||
* @param {string} propertyValue (The value of the property the match should be based on. e.g. "Peter") | ||
* @param {string} propertyName [this.searchProperty] (The name of the property the match should be based on. e.g. "name") | ||
* @param {object} nodes [this._treeData] (The nodes the search should be based on.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype.findNode = function (propertyValue, propertyName, nodes) { | ||
var options = { | ||
propertyName: propertyName || this.searchProperty, | ||
exact: true, | ||
firstHitOnly: true | ||
}; | ||
return this._searchNodes(propertyValue, options, nodes).shift(); | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype.getPathByProperty = function (propertyName, propertyValue) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't drop getPathByProperty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As in comment, needs to stay. |
||
if (propertyName === undefined || propertyValue === undefined) { | ||
return; | ||
} | ||
/** | ||
* Searches a (multi root) node and matches nodes based on a property and a value. | ||
* @return {array} - The found node(s). | ||
* @param {string} propertyValue (The value of the property the match should be based on. e.g. "Peter") | ||
* @param {object} options (Matching options) | ||
* @param {string} options.propertyName (The name of the property the match should be based on. e.g. "name") | ||
* @param {boolean} options.exact [false] (If the search should be executed exact or fuzzy. true wouldn't match "Pet") | ||
* @param {boolean} options.firstHitOnly [false] (If the search should only return the first found node.) | ||
* @param {object} nodes [this._roots] (The nodes the search should be based on.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype._searchNodes = function (propertyValue, options, nodes) { | ||
var results = []; | ||
|
||
if (propertyName === 'pathLocator') { | ||
return this._getPathByPathLocator(propertyValue); | ||
} | ||
// Defaults | ||
nodes = nodes || this._roots; | ||
|
||
var node = this._searchAllRoots(propertyName, propertyValue); | ||
if (node !== undefined) { | ||
return this._getPathByPathLocator(node.pathLocator); | ||
} | ||
nodes.some(function (node) { | ||
results = results.concat(this.search(node, propertyValue, options)); | ||
return options.firstHitOnly && results.length > 0; | ||
}, this); | ||
|
||
return results; | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype.getChildren = function (node) { | ||
if (node === undefined) { | ||
return; | ||
/** | ||
* Returns the node of a given path. | ||
* @return {object} | ||
* @param {string} pathLocator (The string which describes the path. e.g. "1.2.9") | ||
* @param {object} nodeObj [this._treeData] (The object the search should be based on.) | ||
* @param {string} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getNodeByPathLocator = function (pathLocator, nodeObj, pathLocatorSeparator) { | ||
if (!pathLocator) { | ||
return this._roots; | ||
} | ||
return _objectValues(node.children); | ||
|
||
// Defaults | ||
nodeObj = nodeObj || this._treeData; | ||
pathLocatorSeparator = pathLocatorSeparator || this.pathLocatorSeparator; | ||
|
||
var pathNodes = this.getPathNodes(pathLocator, nodeObj, pathLocatorSeparator); | ||
return pathNodes && pathNodes.pop(); | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype.getProperty = function (node, propertyName) { | ||
if (node === undefined || propertyName === undefined) { | ||
return; | ||
/** | ||
* Returns the nodes on a given path. | ||
* A valid path 1.2.3 should return the items [1, 2, 3] | ||
* - path 1.2.3.3 should return [1, 2, 3, undefined] | ||
* - path 0.1.2.3 should return [1, 2, 3] | ||
* - path 0.1.5.3 should return [1, undefined, undefined] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one important path would be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would actually return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What prevents it from finding the path again next iteration? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Imagine this tree:
We are looking for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly, that's what I mean. But as I understood the implementation, it continues looping on the pathlocator, so while 5 will be undefined, it will try again with 2, and the incorrectly "get back" on the path. |
||
* @return {array} | ||
* @param {string} pathLocator (The string which describes the path. e.g. "1.2.9") | ||
* @param {object} nodeObj [this._treeData] (The object the search should be based on.) | ||
* @param {string} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getPathNodes = function (pathLocator, nodeObj, pathLocatorSeparator) { | ||
if (!pathLocator) { | ||
return this._roots; | ||
} | ||
|
||
return node[propertyName]; | ||
}; | ||
// Defaults | ||
pathLocatorSeparator = pathLocatorSeparator || this.pathLocatorSeparator; | ||
|
||
Cosmoz.DefaultTree.prototype._searchAllRoots = function (propertyName, propertyValue) { | ||
var i, | ||
var path = pathLocator.split(pathLocatorSeparator), | ||
pathSegment = nodeObj || this._treeData, | ||
nodes = [], | ||
node; | ||
|
||
for (i = 0; i < this._roots.length; i+=1) { | ||
node = this.search(this._roots[i], propertyName, propertyValue); | ||
if (node !== undefined) { | ||
return node; | ||
// Get the nodes on the path | ||
path.forEach(function (nodeKey) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation looks very nice, I would just like to see it as a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, we should also have an outer loop to see what root gives the most complete node list as well to handle the "missing ancestor multi root" scenario. Maybe this can be solved by making this internal (
Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now we perform the check against the object tree. So whichever "root object" has the first matching key, gets taken.
What should be changed regarding the behavior? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see how the last would work. Wouldn't we find the 2 and start traversing? Never finding 301 since it's another root? Anyway, both these discussions just needs to be supported with tests I guess and I'll rerrad the code 😊 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added the tests. But you are right, it only works here because Node2 does not have children.. Oh man.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I think the approach from #1 (comment) wouldn't change too much and be pretty secure. (I feel less crazy now though :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually .. does this highlight another bug? Consider the path 1.2.3.4 and a tree like
since 3 doesn't have any children, will we actually end up with [ 1, 2, undefined, 4 ] with 4 pointing to 1.2.4 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, thats I guess actually what you meant with "What prevents it from finding the path again next iteration?" in above discussion. I falsely thought you want me to find the path again. I'll try your approach with looping over the roots & will break (?) on undefined. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well I think you can stop searching any more roots if you find a result without any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! having this tree:
Do we actually need the undefined values at the end of the array or is it fine as below?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well I would say that the right-most parts are the most significant, so we have to inform the caller that we couldn't find them, either by a non-value (null, undefined) or by invalidating the whole path. |
||
node = pathSegment[nodeKey] !== node ? pathSegment[nodeKey] : undefined; | ||
nodes.push(node); | ||
if (node && this.hasChildren(node)) { | ||
pathSegment = node[this.childProperty]; | ||
} | ||
}, this); | ||
|
||
// Filter out undefined items of the start | ||
while (nodes.length > 0 && nodes[0] === undefined) { | ||
nodes.shift(); | ||
} | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype._getNodeByPathLocator = function (pathLocator) { | ||
var node, | ||
path, | ||
i; | ||
return nodes; | ||
}, | ||
|
||
for (i = 0; i < this._roots.length; i+=1) { | ||
node = this._roots[i]; | ||
if (node.pathLocator === pathLocator) { | ||
return node; | ||
} | ||
/** | ||
* Returns a string which describes the path of a node (found by its path locator). | ||
* @return {string} e.g. home/computer/desktop | ||
* @param {string} pathLocator (The string which describes the path. e.g. "1.2.9") | ||
* @param {string} pathProperty (The property of a node on which the path should be build on. e.g "location" with node = {"location": "home", ..}) | ||
* @param {string} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) | ||
* @param {string} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path segments of pathLocator.) | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getPathString = function (pathLocator, pathProperty, pathStringSeparator, pathLocatorSeparator) { | ||
// Defaults | ||
pathProperty = pathProperty || this.searchProperty; | ||
pathLocatorSeparator = pathLocatorSeparator || this.pathLocatorSeparator; | ||
pathStringSeparator = pathStringSeparator || this.pathStringSeparator; | ||
|
||
if (pathLocator.indexOf(node.pathLocator + '.') === 0) { | ||
path = pathLocator.slice(node.pathLocator.length + 1).split('.'); | ||
for (i = 0; i < path.length; i+=1) { | ||
if (node.children) { | ||
node = node.children[path[i]]; | ||
} else { | ||
node = null; | ||
} | ||
if (node === undefined || node === null) { | ||
break; | ||
} | ||
} | ||
|
||
if (node !== undefined && node !== null) { | ||
return node; | ||
} | ||
} | ||
var pathNodes = this.getPathNodes(pathLocator, this._treeData, pathLocatorSeparator); | ||
|
||
if (!pathNodes) { | ||
return; | ||
} | ||
|
||
return pathNodes.map(function (node) { | ||
return node[pathProperty]; | ||
}).join(pathStringSeparator); | ||
}; | ||
|
||
Cosmoz.DefaultTree.prototype._getPathByPathLocator = function (pathLocator) { | ||
var node, | ||
path, | ||
subPath, | ||
i; | ||
/** | ||
* Returns a string which describes the path of a node (found by a node's property and value). | ||
* @return {string} e.g. home/computer/desktop | ||
* @param {string} propertyValue (The value of the property the match should be based on. e.g. "Peter") | ||
* @param {string} propertyName (The name of the property the match should be based on. e.g. "name") | ||
* @param {string} pathProperty (The property of a node on which the path should be build on. e.g "location" if node = {"location": "home"}) | ||
* @param {string} pathStringSeparator [this.pathStringSeparator] (The string the path should get separated with.) | ||
* @param {string} pathLocatorSeparator [this.pathLocatorSeparator] (The string which separates the path. e.g ".") | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getPathStringByProperty = function (propertyValue, propertyName, pathProperty, pathStringSeparator, pathLocatorSeparator) { | ||
if (propertyValue === undefined) { | ||
return; | ||
} | ||
|
||
for (i = 0; i < this._roots.length; i+=1) { | ||
node = this._roots[i]; | ||
path = [node]; | ||
// Defaults | ||
pathProperty = pathProperty || this.searchProperty; | ||
propertyName = propertyName || this.searchProperty; | ||
pathLocatorSeparator = pathLocatorSeparator || this.pathLocatorSeparator; | ||
pathStringSeparator = pathStringSeparator || this.pathStringSeparator; | ||
|
||
if (node.pathLocator === pathLocator) { | ||
return path; | ||
} | ||
if (propertyName === 'pathLocator') { | ||
return this.getPathString(propertyValue, pathProperty, pathStringSeparator, pathLocatorSeparator); | ||
} | ||
|
||
var node = this.getNodeByProperty(propertyValue, propertyName); | ||
|
||
if (pathLocator.indexOf(node.pathLocator + '.') === 0) { | ||
subPath = pathLocator.slice(node.pathLocator.length + 1).split('.'); | ||
for (i = 0; i < subPath.length; i+=1) { | ||
if (node.children) { | ||
node = node.children[subPath[i]]; | ||
} else { | ||
node = null; | ||
} | ||
if (node === undefined || node === null) { | ||
path = null; | ||
break; | ||
} else { | ||
path.push(node); | ||
} | ||
} | ||
|
||
if (path !== null) { | ||
return path; | ||
} | ||
} | ||
if (node) { | ||
return this.getPathString(node.pathLocator || node.path, pathProperty); | ||
} | ||
}; | ||
|
||
/** | ||
* Returns an Object or an Array representing the children of a node. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, multiple return types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docs needs updating |
||
*/ | ||
Cosmoz.DefaultTree.prototype.getChildren = function (node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having a function that only returns the |
||
if (!node) { | ||
return; | ||
} | ||
return _objectValues(node[this.childProperty]); | ||
}; | ||
|
||
/** | ||
* Returns true if a node has children. | ||
*/ | ||
Cosmoz.DefaultTree.prototype.hasChildren = function (node) { | ||
if (!node) { | ||
return false; | ||
} | ||
var children = this.getChildren(node); | ||
return children && children.length > 0; | ||
}; | ||
|
||
/** | ||
* Returns the property of a Node based on a given property name. | ||
*/ | ||
Cosmoz.DefaultTree.prototype.getProperty = function (node, propertyName) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nomego actually similar discussion as with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, it's pretty weird. |
||
if (!node || !propertyName) { | ||
return; | ||
} | ||
return node[propertyName]; | ||
}; | ||
|
||
</script> | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we actually need to make this a Polymer element.
Did you see a reason?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only reason was for the
iron-component-page
to not crash. (demo & docs)Maybe there is another way but I didn't explore it yet..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok well then leave it but maybe add a comment about it, too.