Simply put
I have a tree structure made of objects.
Is it possible to build that tree and add to each object a reference to their parent ?
I know referencing works with objects, but i'm not sure if it would in that case?
I would like to be able to write something like this
currentLevel = this.getParent();
another exemple would be
this.getChildList().addChild({name: test,parent: this})
Without having copies and creating multiple tree from the first one.
2nd question
How would referencing works with array? Are they considered objects or does it depends on their content?
3nd question
Would saving the tree in the browser's cache, via string-JSON serialisation destroy the references?
You can do this be creating a "TreeNode" class:
var TreeNode = (function(){
//keep track of parent node
TreeNode.prototype.parent = null;
//keep track of children
TreeNode.prototype.children = [];
function TreeNode(parent) {
if(parent !== undefined) {
if(this.setParent(parent)) {
this.parent.addChild(this);
}
}
//...
}
TreeNode.prototype.setParent = function(parent) {
//add some sort of check to make sure it is a `TreeNode`
if(parent instanceof TreeNode) {
this.parent = parent;
return true;
}
return false;
}
TreeNode.prototype.addChild = function(child) {
//add some sort of check to make sure it is a `TreeNode`
if(child instanceof TreeNode) {
this.children.push(child);
child.setParent(this);
}
}
TreeNode.prototype.getParent = function(){
return this.parent;
}
TreeNode.prototype.getChildren = function(){
return this.children;
}
return TreeNode;
})();
And then you can expand from that.
Example Code:
var node_a = new TreeNode();
var node_b = new TreeNode(node_a);
var node_c = new TreeNode(node_a);
console.log(node_a.getParent(), node_c.get_parent()); //null , node_a
console.log(node_a.getChildren()); //[node_b, node_c]
This is just a start, it needs waaaaaaaaaay more expansion :-)
Okay, so there are most likely frameworks out there, but I wrote a quick thing which supports JSON serialisation and the reverse (via it's own methods). I took base inspiration from Neal's answer. Example
var a = new MyTreeNode('a'), // make some nodes
b = new MyTreeNode('b'),
c = new MyTreeNode('c');
a.addChild(b).addChild(c); // a parent of b parent of c
c.getParent() === b; // true
var str = a.toJSON(); // "{"nodeName":"a","childNodes":[{"nodeName":"b","childNodes":[{"nodeName":"c","childNodes":[]}]}]}"
MyTreeNode.parseJSON(str); // MyTreeNode (same structure as before)
Full code
/* MyTreeNode(String nodeName)
Instance Properties
- nodeName, String
- childNodes, Array of MyTreeNodes
- parentNode, MyTreeNode
Instance Methods
- addChild(MyTreeNode node), child MyTreeNode
- removeChild(MyTreeNode node), child MyTreeNode
- getParent, parent MyTreeNode
- getChildList, Array of MyTreeNodes
- serialise, JSON-safe Object
- toJSON, String
Constructor Methods
- deserialise(Object serialised), MyTreeNode
- parseJSON(String JSONString), MyTreeNode
*/
var MyTreeNode = (function () {
function MyTreeNode(nodeName) {
nodeName && (this.nodeName = nodeName);
this.childNodes = [];
}
MyTreeNode.prototype.parentNode = null;
MyTreeNode.prototype.childNodes = [];
MyTreeNode.prototype.nodeName = '';
// getters
MyTreeNode.prototype.getChildList = function () {
return this.childNodes = [];
};
MyTreeNode.prototype.getParent = function () {
return this.parentNode;
};
// add/remove
MyTreeNode.prototype.removeChild = function (node) {
var i = this.childNodes.indexOf(node);
if (node.parentNode !== this || i == -1)
throw new ReferenceError('node is not a child of this');
this.childNodes.splice(i, 1);
node.parentNode = null;
return node;
};
MyTreeNode.prototype.addChild = function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
node.parentNode = this;
this.childNodes.push(node);
return node;
};
// JSON
MyTreeNode.prototype.serialise = function () {
var o = {
nodeName: this.nodeName,
childNodes: []
}, i;
for (i = 0; i < this.childNodes.length; ++i) {
o.childNodes.push(this.childNodes[i].serialise());
}
return o;
};
MyTreeNode.prototype.toJSON = function () {
return JSON.stringify(this.serialise());
};
MyTreeNode.deserialise = function (o) {
var p = new MyTreeNode(o.nodeName), i;
for (i = 0; i < o.childNodes.length; ++i) {
p.addChild(MyTreeNode.deserialise(o.childNodes[i]));
}
return p;
};
MyTreeNode.parseJSON = function (str) {
var o = JSON.parse(str);
return MyTreeNode.deserialise(o);
};
return MyTreeNode;
}());
You could traverse your object and add parent properties to every subobject:
function addParents(obj) {
var name;
for (name in obj) {
if (typeof obj[name] === "object") {
addParents(obj[name]);
obj[name].parent = obj;
}
}
}
var obj = {
g: {
k: [
{
r : 1
},
{
r : 1
}
],
j: {
h: 1
}
}
};
addParents(obj);
console.log(obj.g.parent === obj); //true
console.log(obj.g.k.parent === obj.g); //true
console.log(obj.g.k[1].parent === obj.g.k); //true
console.log(obj.g.j.parent === obj.g); //true
And if you want to add objects later on, you could use something like this:
function addChild(obj, child, name){
obj[name] = child;
child.parent = obj;
}
addChild(obj.g, {t:1}, "xy");
console.log(obj.g.xy.parent === obj.g); //true
FIDDLE
Related
When executing this javascript code, line #64 (alert message) returns false, can someone tell me the name of the object that is invoking the GetOrMakeDom function from the point of line of code on 64? However the win object is not reference to the this object on line #64. Any idea which name object is reference to the this object on line $64
/*dom - v0.1.0 - 2012-11-15
* http://http://domenlightenment.com/#12
* Copyright (c) 2012 Cody Lindley; Licensed MIT */
(function(win) {
var global = win;
var doc = global.document;
var dom = function(params, context) {
return new GetOrMakeDom(params, context);
};
var GetOrMakeDom = function(params, context) {
var currentContext = doc;
if (context) {
if (context.nodeType) { //its either a document node or element node
currentContext = context;
} else { //else its a string selector, use it to selector a node
currentContext = doc.querySelector(context);
}
}
//if no params, return empty dom() object
if (!params || params === '' || typeof params === 'string' && params.trim() === '') {
this.length = 0;
return this;
}
//if HTML string, construct domfragment, fill object, then return object
if (typeof params === 'string' && /^\s*<(\w+|!)[^>]*>/.test(params)) { //yup its forsure html string
//create div & docfrag, append div to docfrag, then set its div's innerHTML to the string, then get first child
var divElm = currentContext.createElement('div');
divElm.className = 'doc-frag-wrapper';
var docFrag = currentContext.createDocumentFragment();
docFrag.appendChild(divElm);
var queryDiv = docFrag.querySelector('div');
queryDiv.innerHTML = params;
var numberOfChildren = queryDiv.children.length;
//loop over nodelist and fill object, needs to be done because a string of html can be passed with siblings
for (var z = 0; z < numberOfChildren; z++) {
this[z] = queryDiv.children[z];
}
//give the object a length value
this.length = numberOfChildren;
//return object
return this; //return e.g. {0:ELEMENT_NODE,1:ELEMENT_NODE,length:2}
}
//if a single node reference is passed, fill object, return object
if (typeof params === 'object' && params.nodeName) {
this.length = 1;
this[0] = params;
return this;
}
//if its an object but not a node assume nodelist or array, else its a string selector, so create nodelist
var nodes;
if (typeof params !== 'string') { //nodelist or array
nodes = params;
} else { //ok string
nodes = currentContext.querySelectorAll(params.trim());
alert(this === win);
}
//loop over array or nodelist created above and fill object
var nodeLength = nodes.length;
for (var i = 0; i < nodeLength; i++) {
this[i] = nodes[i];
}
//give the object a length value
this.length = nodeLength;
//return object
return this; //return e.g. {0:ELEMENT_NODE,1:ELEMENT_NODE,length:2}
};
//expose dom to global scope
global.dom = dom;
//global short cut to prototype
dom.fn = GetOrMakeDom.prototype;
})(window);
dom.fn.each = function(callback) {
var len = this.length;
for (var i = 0; i < len; i++) {
callback.call(this[i], i, this[i]);
}
return this;
};
dom.fn.html = function(htmlStringOrTextString) {
if (htmlStringOrTextString) {
return this.each(function() {
this.innerHTML = htmlStringOrTextString;
});
} else {
return this[0].innerHTML;
}
};
dom.fn.text = function(textString) {
if (textString) {
return this.each(function() {
this.textContent = textString;
});
} else {
return this[0].textContent.trim();
}
};
dom.fn.append = function(stringOrObject) {
return this.each(function() {
if (typeof stringOrObject === 'string') {
this.insertAdjacentHTML('beforeend', stringOrObject);
} else {
var that = this;
dom(stringOrObject).each(function(name, value) {
that.insertAdjacentHTML('beforeend', value.outerHTML);
});
}
});
};
dom("ul").append("<li>4</li>")
<!DOCTYPE html>
<html lang="en">
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script src="https://raw.github.com/codylindley/domjs/master/builds/dom.js"></script>
</body>
</html>
I am playing around with a binary tree. I am trying to use recursion to find all the nested children's values and push all the values into an array. I started with the left tree to see if it works. I tried to call childrenArray() but the console says childrenArray() is not defined. When I ask if (typeof BinaryTree.prototype.childrenArray === 'function'), it returns true. Please teach me and tell me why I wasn't able to execute my code?
var Tree = function(value) {
if (!(this instanceof Tree)) {
return new Tree(value);
}
this.value = value;
this.children = [];
};
Tree.prototype.addChild = function(value) {
var child = new Tree(value);
this.children.push(child);
};
Tree.prototype.contains = function(value) {
if (this.value === value) {
return true;
} else {
for (var i = 0; i < this.children.length; i++) {
if (this.children[i] && this.children[i].contains(value)) {
return true;
}
}
return false;
}
};
var BinaryTree = function(value) {
if (!(this instanceof BinaryTree)) {
return new BinaryTree(value);
}
Tree.call(this, value);
};
BinaryTree.prototype = Object.create(Tree.prototype);
BinaryTree.prototype.addChild = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.contains = function(value) {
if (value < this.value) {
if (this.children[0] === undefined) {
return false;
}
return this.children[0].contains(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
return false;
}
return this.children[1].contains(value);
}
};
var a = new BinaryTree();
a.value = 10;
a.addChild(4);
a.addChild(11);
a.addChild(3);
BinaryTree.prototype.childrenArray = function() {
var results = [];
if (this.value) {
results.push(this.value);
}
if (this.children[0].length === 0) {
return results;
}
for (var i = 0; i < this.children[0].children.length; i++) {
if (this.children[i].value) {
results.push(this.children[i].value);
return this.childrenArray();
}
}
};
a.childrenArray();
As #melpomene mentioned, you are invoking childArray but you didn't define it anywhere. I assume the line return childArray(); ended up there by mistake, you probably meant to recursively return childrenArray for the left child of root.
I would like to mention that your loop itself (without the recursive call):
for(var i = 0; i < this.children[0].children.length; i++) { // <-- you are iterating over the children of root's left child
if(this.children[i].value) { // <-- but you are accessing root's current child
results.push(this.children[i].value);
}
}
is somewhat confusing. You are iterating over the children of root's left child children[0].children, but on each iteration you check if the root's children themselves children[i] have a value and you push that value.
This is incorrect and will break if, for example, the root only has a left child that has both children (i will be out of bounds).
Here's how you can approach this problem. The recursion to print the left tree in your case can be broken down into the following cases:
If the current node has no value, return an empty array
Else If the current node has no children, return an array only containing the current value
Else if the current node has a left child, run childrenArray on it recursively and add the results to the results array
Here's how that would look:
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
Full Example:
var BinaryTree = function(value) {
this.value = value;
this.children = [];
};
BinaryTree.prototype.addChild = function (value) {
if (value < this.value) {
if (this.children[0] === undefined) {
this.children[0] = new BinaryTree(value);
}
this.children[0].addChild(value);
} else if (value > this.value) {
if (this.children[1] === undefined) {
this.children[1] = new BinaryTree(value);
}
this.children[1].addChild(value);
}
};
BinaryTree.prototype.childrenArray = function() {
var results = [];
// case 1:
if (this.value === undefined) {
return results;
}
// case 2:
results.push(this.value);
if (this.children.length === 0) {
return results;
}
// case 3:
var leftChild = this.children[0];
if (leftChild) {
results = results.concat(leftChild.childrenArray());
}
/* add code here for the right child to complete your function */
return results;
};
var a = new BinaryTree(10);
a.addChild(4);
a.addChild(11);
a.addChild(3);
console.log(a.childrenArray()); // [10, 4, 3]
Last thing, based on your insertion logic (insert left if smaller, right if larger), you are building a Binary Search Tree not a Binary Tree. Binary tree's don't follow any particular insertion logic. Take a look at this question for clarification
I'm looking for a better way to generate a map of parent-child-relations; based on specific id-pattern.
Its faster to ask for void 0 === cache[parent][child]; the expected result:
{
uuid_1: {uuid_2: {}}
uuid_2: {uuid_3: {}, uuid_4: {}}
uuid_3: {}
uuid_4: {}
}
The HTML structure:
<html id="uuid-1">
<body id="uuid-2">
<somewhere>
<whatever id="uuid-3" />
</somewhere>
<foo id="uuid-4" />
</body>
</html>
_fetch():
<1> // register as init
<2> // register as child of 1
<3>
<4 /> // register as child of 2
</3>
<5 /> // register as child of 2
</2>
</1>
Parse ~1300 elements (large menu structure) to find my ~50 uuids.
Try 1 with jQuery:
_fetch: function(element, factoryName)
{
var a = {}, l = 0, t = this, f = function(el, n)
{
if(!a[n]) a[n] = {};
var e = $(el), test = $('[id^="uuid-"]', e);
if(!test.length)
return;
e.children().each(function()
{
var u = $(this), id = u.attr('id'), q;
// anonymous element: no class defined
if(!(id && 'uuid-' === id.slice(0x00, 0x05)))
{
f(this, n); // continue with current name
return;
}
l++;
q = $.T.util.uuidFromId(id);
$.T.__dict[q] = '#' + id;
a[n][q] = {};
// comment in/out
f(this, q);
});
}
f(element, factoryName);
return a;
}
Try 2 with yellow JS:
..., g = function(n, p)
{
var r = [];
for(var d = (p || document).getElementsByTagName('*'), i = 0, l = d.length; i < l; i++)
d[i].getAttribute(n) && r.push(d[i]);
return r;
},
f = function(el, n)
{
var z = el.children.length, y = 0;
if(!a[n]) a[n] = {};
if(z && g('id', el)) for(; y < z; y++)
{
var u = el.children[y], id = u.getAttribute('id'), q;
if(!(id && 'uuid-' === id.slice(0x00, 0x05)))
{
f(u, n);
continue;
}
l++;
$.T.__dict[q = $.T.util.uuidFromId(id)] = '#' + id;
a[n][q] = {};
// it's irrelevant to fetch the full html or a sequence by constructor
//f(u, q);
}
}
My question is:
How to collect DOM elements as flat representation in a faster way; like the mapping above? My current solution is very laggy.
OT:
contextual x-dialog based on map:
<baz><alice><bob><bobchild/></bob></alice><foo />
alice._init:
before init children of bob
tell foo 'go away'
before init bob // context: no bob
after init children of alice // && alice without children
after init baz // && baz not ready -> no hello
tell baz 'hello'
I am still not quite sure I know what you're trying to do, but here's the fastest way I know to walk a DOM tree and accumulate parent/child info like you're doing to build the data structure you indicated you wanted to end up with:
var treeWalkFast = (function() {
// create closure for constants
var skipTags = {"SCRIPT": true, "IFRAME": true, "OBJECT": true,
"EMBED": true, "STYLE": true, "LINK": true, "META": true};
return function(parent, fn, allNodes) {
var parents = [];
var uuidParents = [];
parents.push(parent);
uuidParents.push(parent);
var node = parent.firstChild, nextNode, lastParent;
while (node && node != parent) {
if (allNodes || node.nodeType === 1) {
if (fn(node, parents, uuidParents) === false) {
return(false);
}
}
// if it's an element &&
// has children &&
// has a tagname && is not in the skipTags list
// then, we can enumerate children
if (node.nodeType === 1 && node.firstChild && !(node.tagName && skipTags[node.tagName])) {
// going down one level, add this item to the parent array
parents.push(node);
if (node.id && node.id.substr(0, 5) === "uuid-") {
uuidParents.push(node);
}
node = node.firstChild;
} else if (node.nextSibling) {
// node had no children so going to next sibling
node = node.nextSibling;
} else {
// no child and no nextsibling
// find parent that has a nextSibling
while ((node = node.parentNode) != parent) {
lastParent = parents.pop();
if (lastParent === uuidParents[uuidParents.length - 1]) {
uuidParents.pop();
}
if (node.nextSibling) {
node = node.nextSibling;
break;
}
}
}
}
}
})();
var objects = {uuid_1: {}};
treeWalkFast(document.documentElement, function(node, parents, uuidParents) {
if (node.id && node.id.substr(0, 5) === "uuid-") {
var uuidParent = uuidParents[uuidParents.length - 1];
if (!objects[uuidParent.id]) {
objects[uuidParent.id] = {};
}
objects[uuidParent.id][node.id] = {};
objects[node.id] = {};
}
});
Working demo here: http://jsfiddle.net/jfriend00/yzaJ6/
This is an adaptation of the treeWalkFast() function I wrote for this answer.
I want to create a js class that resembles simple music playlist (Array). I want to instantiate this playlist with IDs, each ID being a track ID in my database. I have interface like this:
function Playlist() {
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length) { // length is index + 1
return null;
}
return this[++this.current];
};
Playlist.prototype.seek = function(id) {
for (i in this) {
if (this[i] == id) {
this.current = parseInt(i);
return i;
}
}
return false;
};
Playlist.prototype.getCurrent() {
return this.current;
};
};
The code above DOES NOT do what I want, because I imagine it as class that has it's method defined, that can be instantiated like this:
var newPlaylist = Playlist(2,3,5,10/* those are my ids */);
And currently the only way I've found is something like:
Playlist.prototype = new Array(2, 3, 5, 10/* those are my ids */);
Which does not make any sense since it can be instantiated as different objects. Any ideas are very welcome!
Best way - nested array;
function Playlist() {
this.current = 0;
this.list = Array.prototype.slice.call(arguments);;
};
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.list.length) { // length is index + 1
return null;
}
return this.list[++this.current];
};
Playlist.prototype.getCurrent = function() {
return this.current;
};
var newPlaylist = new Playlist(2,3,5,10/* those are my ids */);
But you can't use list[i] to get element by index, but you just need add at() method to your class that provide similar functionality
PlayList.prototype.at(i) {
return this.list[i];
}
Since you cannot subclass Array, you should build wrapper objects with your Playlist constructor:
Playlist = (function() {
function Playlist(list) {
this.list = list || [];
}
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current <= 0)
return null;
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length)
return null;
return this.list[++this.current];
};
Playlist.prototype.seek = function(id) {
return this.list.indexOf(id);
};
return Playlist;
})();
Usage:
var newPlaylist = new Playlist([2,3,5,10]);
I've got a class that is basically a native Javascript Array, but it raises events when items are added or removed.
hb.extend( {
Classes: {
Collection: hbClass.inherit({
init: function (arr) {
// get the functions we want to retain
var _on = this.on,
_trigger = this.trigger,
_push = this.push,
_remove = this.remove,
_reset = this.reset,
_from = this.fromArray,
_watch = this.watch;
// Set the object up as an Array
this.__proto__ = Array.prototype;
// get the Array functions we want to use
this.arrPush = this.push;
// reapply the old functions
this.push = _push;
this.remove = _remove;
this.reset = _reset;
this.fromArray = _from;
this.on = _on;
this.trigger = _trigger;
this.watch = _watch;
if (arr && (arr.length && typeof arr !== "string")) this.fromArray(arr, true);
},
fromArray: function (arr, stopEvent) {
this.reset();
for (var i = 0, len = arr.length; i < len; i++) {
this.arrPush(arr[i]);
}
if (!stopEvent) this.trigger('change', this);
},
push: function () {
this.arrPush.apply(this, arguments);
this.trigger('add', this);
this.trigger('change', this);
return this;
},
remove: function (from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
this.arrPush.apply(this, rest);
this.trigger('remove', this);
this.trigger('change', this);
return this;
},
reset: function () {
this.length = 0;
this.trigger('change', this);
this.trigger('remove', this);
}
})
}
});
There may be better ways to do it, but it works for me.......except in IE.
In IE at the line this.arrPush.appy(this, arguments); under the push method, it hits a Stack Overflow error.
Specifically:
SCRIPT28: Out of stack space
But this does NOT occur in Firefox or Chrome.
Anyone have any advice?
EDIT
Trigger code:
this.hbClass.prototype.trigger = function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (handler.method.call(
handler.context, this, type, data
)===false) {
return false;
}
}
return true;
}
The issue is probably this line:
this.__proto__ = Array.prototype;
as __proto__ is not supported in some versions of IE. It has been codified in the ES6 specification, but that isn't implemented in some versions of IE. I don't understand exactly how your code works, but the safe way to set a prototype is like this:
Working demo: http://jsfiddle.net/jfriend00/ff99G/
function myClass() {
// add new methods to this instance in the constructor
this.fromArray = function() {};
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
Here's an example of the kind of thing you're doing using .prototype that works in IE:
function log(msg) {
var result = document.getElementById("result");
var div = document.createElement("div");
div.innerHTML = msg;
result.appendChild(div);
}
function myClass() {
var _push = this.push;
this.count = function() {
return this.length;
}
this.trigger = function(type, name) {
var str = type;
if (name) {
str += ", " + name;
}
log(str);
}
this.push = function() {
var retVal = _push.apply(this, arguments);
this.trigger("change", "push");
return retVal;
}
};
// become an array and get all its methods
myClass.prototype = Array.prototype;
var x = new myClass();
x.push("foo");
x.push("whatever");
log(x.count());