Alright, I've got this blank array of objects.
I am dynamically finding every node in a web page and each node is going to have it's own object and properties.
I need a way to throw the values I need into their respective objects property
So, for example, I find the body node. I now have a special little object for this node. I need to throw pretty much everything about this little guy into his object's properties.
So I pretty much need it to render like this:
Turning this:
<html>
<body style="margin:0; padding:0;" title="My Title">
<p>some text</p>
<div class="wrapper"></div>
<footer></footer>
</body>
</html>
Into this:
this.nodesInfo = [ // All nodes in the page's DOM
{
type: 'body', // ex: body, section, aside, div, etc.
id: 'myID', // the Id of that element
class: ['myClass1', 'myClass2'], // the class/class' of that element
depth: '2', // the level in the page's DOM in which that element sits, this will be an integer
parent: 'html', // that elements direct parent Node
children:['div.wrapper', 'p', 'footer'], // any child Nodes that, that element may be a parent to
text: '', // the text inside that element if any exists
attributes: ["style=margin:0; padding:0;", "title='My Title'"] // all attributes of this node
}
];
It would of course cycle through each node it discovered and do this for each node accordingly, until it ran out of nodes.
The class, children, and attributes properties are arrays for the simple possibility of multiples of any of these. Everything else is just a string since a node can't have more than one ID, title, or direct parent tag.
If a node does not contain some of these properties then that property would remain blank/null/undefined.
My question is simple. Is this possible, if not would I instead have to create each object individually and the push them into my nodesInfo array?
I think the easiest way to go about this would be making an object of each Node and then pushing them all (once they are all created) into an array.
I was building something like this the other night. This should work and you can add more stuff easily. http://jsfiddle.net/elclanrs/UHbMa/
$.fn.buildTree = function() {
var tree = {};
this.find('*').andSelf().each(function(i, v) {
var parents = $(this).parents().length - 1,
depth = 0;
while (depth < parents) {
depth++;
}
tree[v.tagName.toLowerCase() + '(' + i + ')'] = {
id: (v.id) ? '#' + v.id : '',
className: (v.className) ? '.' + v.className.replace(' ', '.') : '',
depth: depth
};
});
return tree;
};
// Then you can do this...
var tree = $('#element').buildTree();
for (var tag in tree) {
// Get your variables
var tag.match(/\w+/), // Get rid of `(n)`
id = tree[tag].id,
className = tree[tag].className,
depth = tree[tag].depth;
html = 'something';
// Bla bla
}
Related
Let's say I have an array as so with objects:
[
{
"id": "5a97e047f826a0111b754beb",
"name": "Hogwarts",
"parentId": "5c7bf2191d41c810b2ad6186",
"childrenIds": []
},
{
"id": "5c7bf2191d41c810b2ad6186",
"name": "Defense Against The Dark Arts",
"parentId": null,
"childrenIds": [
"5a97e047f826a0111b754beb"
]
}
]
What I'd like to do is a function that returns another array, but this time with only items that don't have a parentID as root elements, and have a children array on them containing their children, so on reaching leaves who have an empty childrenIDs array. (also remove the parent/children id properties)
For the previous input I'd return something like this
[
{
"id": "5c7bf2191d41c810b2ad6186",
"name": "Defense Against The Dark Arts",
"children": [
{
"id": "5a97e047f826a0111b754beb",
"name": "Hogwarts"
}
]
}
]
I can't seem to think of any efficient code for this task, can anyone help?
You could keep a reference to each node by ID in an object and build the tree while you go. Since we may encounter references to entries that we didn't see yet, we'll create stubs in the meantime (consisting only of a children array) and add the rest of their fields later on.
This way we have only a single loop.
It seems you have sort of a vertical double-linked list, saving both the parent ID in the children and the children's IDs in the parent, but we need only one of those to build the tree. We'll use the parent ID stored in each child. (Note that this assumes that your structure is consistent without imbalanced relations or dangling references.)
For simplicity, we create one root node whose children we'll return at the end, so that we don't have to handle the nodes without parent any differently.
Then you could write code like this:
function makeTree (rows) {
// Use a symbol to denote the root ID to avoid clashes with any real IDs
const ROOT = Symbol('ROOT')
// Node index, by ID
// Add a root node from the start
const nodes = { [ROOT]: { children: [] } }
// Helper function to return an existing node or create a stub if not existing
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = { children: [] })
for (const row of rows) {
// Add current row to index, merging data with existing stub, if any.
// This keeps any existing references in other nodes' children arrays intact
// because Object.assign mutates the first object passed to it and returns the
// same object.
const node = Object.assign(getNodeOrCreateStub(row.id), row)
// Remove unwanted properties.
delete node.parentId
delete node.childrenIds
// Get parent or create parent stub if parent node not already existing
const parent = getNodeOrCreateStub(row.parentId || ROOT)
// Add current node as child to parent
parent.children.push(node)
}
// Return children of root node
return nodes[ROOT].children
}
Note that this code currently also creates empty children arrays in the leaf nodes, differently from your example above. However I think this makes the code simpler because it doesn't have to handle leaf nodes any different, neither in creating the tree nor in reading it later! (You wouldn't have to do children && children.length to check for children, you could always just access children directly.)
To change that and have the result exactly as in your example, you'd change the code as follows:
// Change this...
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = { children: [] })
// ...to this:
const getNodeOrCreateStub = id => nodes[id] || (nodes[id] = {})
// Also change this...
parent.children.push(node)
// ...to this:
if (!parent.children) parent.children = []
parent.children.push(node)
// ...or to this:
parent.children = [...parent.children || [], node]
I want to get all children of specific parent. I am new to node.js and not able to write recursive function to do this task
var roots = [1,2,6];
var documents = [
{
"parent_id":1
,childerens:[4,5]
}
,{
"parent_id":4
,childerens:[9]
}
,{
"parent_id":9
,childerens:[]
}
,{
"parent_id":5
,childerens:[3]
}
,{
"parent_id":3
,childerens:[]
}
]
roots.forEach(function (rootParentId) {
var allchilderens=getAllchild(rootParentId);
})
Here's an example of what I mean:
var allchilderens = getAllchild(1);
allchilderens == [4,5,9,3]
function findAllChildren(element,is_root,childerens) {
if(is_root==false&&element!=undefined){
childerens.push(element);
}
var doc = documents.find(o => o.parent_id === element);
if(doc["childerens"].length==0){
return [];
}
else{
doc["childerens"].forEach(function (element) {
findAllChildren(element,false,childerens);
})
}
}
var childerens=[];
console.log(findAllChildren(1,true,childerens));
console.log("childerens==>",childerens);
You really want to build a tree structure here as step one.
Each object should, instead of containing a list of children ids, it should contain the actual children. That way, when you want to get all children, you can traverse that tree.
You probably should consider revising your data structure here. Why not use an object, where the user id is the key (since it should be unique). Then you would just need to get those keys directly.
Having everything inside of an array is potentially bad, since you'd have to iterate through the array to find each child, which would have (at worst), a run time equal to the length of the array.
I want to create and use universal javascript function with which it should be easy to create new children for parent nodes in easy, fast and flexible way.
Look at my code:
<!DOCTYPE html>
<html>
<body>
<style>
div {
border: 2px solid #eeeeee;
background-color: #dff0d8;
}
ol {
background-color: #dff0d8;
}
li {
background-color: #eff0c8;
}
</style>
<script>
function addNewElement(newElementType,parentId) {
var newElement = document.createElement(newElementType);
newElement.innerHTML = 'new element';
parentId.appendChild(newElement);
// actually I want to use just this simple code, what makes this function universal, but it doesn't work..
// while next commented lines work as it should
/**
if (parentId == "someThing"){
someThing.appendChild(newElement);
}
if (parentId == "list"){
list.appendChild(newElement);
}
**/
}
</script>
<p>In next example we can add new child element to this list:</p>
<ol id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<button onclick="addNewElement('li','list')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div','someThing')">Add new div-element to this div</button>
</body>
</html>
With parentId.appendChild(newElement) you doesn't get expected result, while it works as it should with specified calls that are shown in /** commented lines **/:
if (parentId == "someThing"){
someThing.appendChild(newElement);
}
if (parentId == "list"){
list.appendChild(newElement);
}
I'm a newbie in JS, so I don't fully understand why I can't use it parentId.appendChild(newElement) to get same results.
I guess it should be simple to make it work even without any jQuery or other libraries.
So I ask you how can I achieve this?
First of all, you shouldn't use the same element ID more than once.
According to W3C:
The id attribute specifies a unique id for an HTML element (the value must be unique within the HTML document).
So I changed your HTML, i.e. removed IDs from buttons and passed required IDs into addNewElement function:
<p>In next example we can add new child element to this list:</p>
<ol id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<button onclick="addNewElement('li', 'list')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div', 'someThing')">Add new div-element to this div</button>
Then I updated addNewElement function:
function addNewElement(elementType, parentId) {
let parentElement = document.getElementById(parentId);
let newElement = document.createElement(elementType);
newElement.innerHTML = 'new element';
parentElement.appendChild(newElement);
}
And it works.
Please look at the jsFiddle for more details.
While you've already accepted an answer, I felt that it might be worth offering a more extensible approach, which allows you to use unobtrusive JavaScript (rather than relying upon in-line event-handlers such as onclick) for easier maintenance.
It's also a little more extensible and customisable:
// a simple function to help derive the correct element
// from the supplied argument, 'needle':
function derive(needle) {
// if the needle has a nodeType and if that nodeType is
// exactly equal to 1:
if (needle.nodeType && needle.nodeType === 1) {
// the needle is then an element-node, and here
// we convert that node into an Array of one:
needle = [needle];
// otherwise, if the needle is a string, and
// document.getElementById() finds an element
// with that id:
} else if ('string' === typeof needle && document.getElementById(needle)) {
// we find that element-node again, using the string
// and again convert it to an Array of one:
needle = [document.getElementById(needle)];
// otherwise, if the needle is - again - a string, and
// document.querySelectorAll() can find a collection
// (of one or more) elements matching the selector that
// the needle is implied to be then we retrieve those
// elements and, using Array.from(), we convert the
// collection into an Array:
} else if ('string' === typeof needle && document.querySelectorAll(needle)) {
needle = Array.from(document.querySelectorAll(needle));
}
// here we return the results to the calling context:
return needle;
}
function addNewElement(opts) {
// the default settings for the function:
// append: Boolean, true: the content will be
// inserted after the found sibling-
// node; false: the content will be
// inserted before the found sibling-
// node.
// classes: String, a string of white-space
// separated class-names to add to
// the new contents,
// Array, an array of class-names to
// add to the new contents.
// content: String, a string of HTML you wish
// to appear in the newly-added content.
// count: Number, the number of elements you
// wish to insert at once.
// create: String, the element-type to create
// null, if you want the function to
// 'decide' for itself.
// parent: Node, the element to which you want
// to add new elements,
// String, the id of the element to
// which you want to add new elements,
// or a CSS selector by which you want
// find the element(s) in the document
// to add new elements to.
// sibling: Node, the node beside which the new
// element(s) should be added.
// Null, the function will try to determine
// the desired element beside which the
// content should be added, based on
// the 'append' setting (above).
var settings = {
'append': true,
'classes' : null,
'content': 'Newly-added element.',
'count': 1,
'create': null,
'parent': document.body,
'sibling': null
},
// uninitialised variables for use later, primarily
// to declare/instantiate variables in one place:
parents,
childType,
created,
sibling,
clone,
classes,
count,
// a documentFragment to enable the addition of multiple
// elements at the same time without triggering (quite so)
// many redraws of the document/page:
fragment = document.createDocumentFragment();
// using Object.keys to iterate over the opts Object, if
// one is supplied or an empty object to avoid errors,
// using the Array.prototype.forEach() method:
Object.keys(opts || {}).forEach(function(key) {
// here we update/overwrite the keys of the
// settings object to the values held in those
// properties of the opts Object:
settings[key] = opts[key];
});
// we call the derive function to retrieve an array
// of element(s):
parents = derive(settings.parent);
// checking, and then storing, the value of
// settings.append; it it's equal to true the
// assessment returns true, if it's equal to
// false the assessment returns false (this
// is a naive check, because it requires that
// a Boolean is stored in that property):
appendCheck = settings.append === true;
// ensuring that the settings.count number
// is a number by parsing the potential
// String, other-based number, into base-10:
count = parseInt(settings.count, 10);
// iterating over each of the parents:
parents.forEach(function(pater) {
// 'pater' the first argument is a reference
// to the current array-element of the array
// over which we're iterating.
// retrieving the element-type to be created,
// if a value was supplied in settings.create
// then we use that (we don't check it's a
// valid element, or that it can be validly
// contained in the nominated parent), otherwise
// if the current element node has children
// then we retrieve the localName of its
// lastElementChild, if it has no children
// the ternary returns null and we move to
// the string of 'div':
childType = settings.create || (pater.children.length > 0 ? pater.lastElementChild.localName : null) || 'div';
// here we create the element:
created = document.createElement(childType);
// if the earlier assessment of settings.append
// resulted in true:
if (appendCheck === true) {
// we find the sibling beside which to insert the
// new content; if a node was supplied we use that,
// otherwise we use the lastElementChild or lastChild:
sibling = settings.sibling || pater.lastElementChild || pater.lastChild;
} else if (appendCheck === false) {
// otherwise, we use either the supplied value or
// we use the firstElementChild or firstChild:
sibling = settings.sibling || pater.firstElementChild || pater.firstChild
}
// assign the supplied - or default - content to the
// created element:
created.innerHTML = settings.content;
// if any class-names have been supplied:
if (settings.classes) {
// we first check whether the settings.classes
// variable is an Array (using Array.isArray),
// which returns a Boolean (true or false); if
// it returns true we simply use the Array otherwise
// we assume it's a String and split that String
// on its white-space characters (/\s+/):
classes = Array.isArray(settings.classes) ? settings.classes : settings.classes.split(/\s+/);
// iterating over the array of class-names:
classes.forEach(function(cN) {
// the first argument (cN) is a reference
// to the current array-element of the
// Array over which we're iterating.
// here we use the Element.classList API to
// add each of the class-names:
created.classList.add(cN);
});
}
// a simple for loop to add the desired
// number of new elements (as supplied in
// the settings.count, or opts.count
// setting):
for (var i = 0; i < count; i++) {
// clone the created-element (and its
// child elements):
clone = created.cloneNode(true);
// append the cloned node to the
// documentFragment we created
// earlier:
fragment.appendChild(clone);
}
// here we use parentNode.insertBefore() to insert
// the new contents (held in fragment) either the
// sibling.nextSibling (if appendCheck is true) or
// before the sibling (if appendCheck is false):
pater.insertBefore(fragment, (appendCheck ? sibling.nextSibling : sibling));
});
}
// retrieving the <button> elements on the page, and converting
// to an Array, using Array.from():
var buttons = Array.from(document.querySelectorAll('button'));
// iterating over those <button> elements in the Array:
buttons.forEach(function(button) {
// using the anonymous function of the addEventListener()
// to call the addNewElement function, in which
// we set the opts.parent setting to the
// previousElementSibling of the button
// firing the event:
button.addEventListener('click', function() {
addNewElement({
'parent': button.previousElementSibling
});
});
});
function derive(needle) {
if (needle.nodeType && needle.nodeType === 1) {
needle = [needle];
} else if ('string' === typeof needle && document.getElementById(needle)) {
needle = [document.getElementById(needle)];
} else if ('string' === typeof needle && document.querySelectorAll(needle)) {
needle = Array.from(document.querySelectorAll(needle));
}
return needle;
}
function addNewElement(opts) {
var settings = {
'append': true,
'classes': null,
'create': null,
'content': 'Newly-added element.',
'count': 1,
'parent': document.body,
'sibling': null
},
parents,
childType,
created,
sibling,
clone,
classes,
fragment = document.createDocumentFragment();
Object.keys(opts || {}).forEach(function(key) {
settings[key] = opts[key];
});
parents = derive(settings.parent);
appendCheck = settings.append === true;
parents.forEach(function(pater) {
childType = settings.create || (pater.children.length > 0 ? pater.lastElementChild.localName : null) || 'div';
created = document.createElement(childType);
if (appendCheck === true) {
sibling = settings.sibling || pater.lastElementChild || pater.lastChild;
} else if (appendCheck === false) {
sibling = settings.sibling || pater.firstElementChild || pater.firstChild
}
created.innerHTML = settings.content;
if (settings.classes) {
classes = Array.isArray(settings.classes) ? settings.classes : settings.classes.split(/\s+/);
classes.forEach(function(cN) {
created.classList.add(cN);
});
}
for (var i = 0; i < settings.count; i++) {
clone = created.cloneNode(true);
fragment.appendChild(clone);
}
pater.insertBefore(fragment, (appendCheck ? sibling.nextSibling : sibling));
});
}
var buttons = Array.from(document.querySelectorAll('button'));
buttons.forEach(function(button) {
button.addEventListener('click', function() {
addNewElement({
'parent': button.previousElementSibling
});
});
});
div {
border: 2px solid #eeeeee;
background-color: #dff0d8;
}
ol {
background-color: #dff0d8;
}
li {
background-color: #eff0c8;
}
<p>In next example we can add new child element to this list:</p>
<ol id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<button>Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button>Add new div-element to this div</button>
JS Fiddle demo.
References:
Array.from().
Array.isArray().
Array.prototype.forEach().
Conditional (ternary) Operator.
document.createDocumentFragment().
document.createElement().
document.getElementById().
document.querySelector().
document.querySelectorAll().
Element.classList API.
Element.innerHTML.
Element.localName.
EventTarget.addEventListener().
JavaScript regular expressions Guide.
Node.appendChild().
Node.firstChild.
Node.insertBefore().
Node.nextSibling.
Node.nodeType.
Node.previousSibling.
NonDocumentTypeChildNode.nextElementSibling.
NonDocumentTypeChildNode.previousElementSibling.
Object.keys().
ParentNode.children.
ParentNode.children.
ParentNode.children.
String.prototype.split().
typeof operator.
Well, I've found simple way how to fix it, but I was looking for something even more basic:
document.getElementById(parentId).appendChild(newElement);
EDIT:
Another way how to do it:
<!DOCTYPE html>
<html>
<body>
<style>
div {
border: 2px solid #eeeeee;
background-color: #dff0d8;
}
ol {
background-color: #dff0d8;
}
li {
background-color: #eff0c8;
}
</style>
<script>
function addNewElement(newElementType,parentId,parentElementType) {
//document.getElementById(clickedId).appendChild(newElement);
var el = parentElementType + "[id=" + parentId + "]";
el = document.querySelector(el);
var newElement = document.createElement(newElementType);
newElement.innerHTML = 'new element';
el.appendChild(newElement);
}
</script>
<p>In next example we can add new child element to this list:</p>
<ol id="list">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<button onclick="addNewElement('li','list','ol')">Add new li-element to this list</button>
<p>In next example we can add new child element to this div:</p>
<div id="someThing">Something here</div>
<button onclick="addNewElement('div','someThing','div')">Add new div-element to this div</button>
</body>
</html>
But now we need to pass parent node type in addNewElement function in this new example. Or we can also define classes for ul and div elements and use them instead of ids.
It's more advanced way, but it may be more useful in some cases. Here's the documentary about document.querySelector and document.querySelectorAll.
Also read this querySelector and querySelectorAll vs getElementsByClassName and getElementById in JavaScript, if you want to get some additional info.
I know you already have an answer that works for you, but I just wanted to add one that shows a more flexible way of doing this using a configuration object instead of just passing in a tag name. To make it more flexible you can pass in a reference to a parent object instead of an id. Also, it returns a reference to the newly created element in case you want to do something with it after it is added to the DOM.
'use strict';
var addNewElement = function (configItems, elParent) {
var newElements = [];
if (!Array.isArray(configItems)) {
// if configItems is not an array, and therefore a
// single config object or string, turn it into
// a single element array
configItems = [configItems];
}
// If elParent is a string assume it is
// the id of an element in the page and select it
if (typeof elParent === 'string') {
elParent = document.getElementById(elParent);
}
configItems.forEach(function (config) {
var option,
elChild;
// if a string is passed in, assume it is
// the tagName and create a default config object
if (typeof config === 'string') {
config = {tag: config};
}
elChild = document.createElement(config.tag);
for (option in config) {
if (config.hasOwnProperty(option)) {
switch (option) {
case 'tag':
// do nothing, already used tag to create new element
break;
case 'html':
// just a shortcut so we don't have to use
// innerHTML in our config object
elChild.innerHTML = config.html;
break;
case 'text':
// another shortcut
elChild.textContent = config.text;
break;
case 'class':
// if we are passed an array convert it to a space delimited string
elChild.className = Array.isArray(config.class) ?
config.class.join(' ') : config.class;
break;
default:
// if we haven't already handled it, assume it is
// an attribute to add to the element
elChild.setAttribute(option, config[option]);
}
}
}
// default text if none was specified
if (elChild.innerHTML === '') {
elChild.innerHTML = 'new element';
}
newElements.push(elChild);
elParent.appendChild(elChild);
});
// return a reference to the new element(s)
// in case you want to do something else with it
// after it was inserted into the document
// returns a single item or an array depending on how many
// items you passed it in configItems
return newElements.length === 1 ? newElements[0] : newElements;
};
Usage would look like this:
// just add a new element with the default text by id
addNewElement('li', 'list');
var list = document.getElementById('list');
// a little fancier, this time using an element reference
addNewElement({
tag: 'li',
html: 'Custom List Item!',
class: 'fancy'
}, list);
addNewElement({
tag: 'input',
placeholder: 'Type here',
value: 'Delete me'
}, document.body); // attach to the body
// do something with the element
// after we create it
var houdini = addNewElement({
tag: 'li',
text: 'Now you see me.',
class: ['houdini', 'show'],
}, list);
setTimeout(function () {
houdini.textContent = "Now you don't";
houdini.classList.remove('show');
}, 2000);
var checkElements = addNewElement([
{
tag: 'input',
id: 'check',
type: 'checkbox',
checked: 'checked',
},
{
tag: 'label',
for: 'check',
html: 'Uncheck me!'
}
], document.body);
jsFiddle showing it in action.
Using hasOwnProperty is necessary since we are using for in.
The 'class' case is there because in ES3 you could not use reserved words as property names with dot notation, so when the DOM API was designed they instead used className to represent the class property. Ever since ES5 we can use reserved words as properties without quoting them. This allows us to add a 'class' shortcut property.
I'm making a 2D, top-down Zelda style web rpg single player in JavaScript.
When the player (purple shirt) walks near a cat, it will "rescue" it... which basically removes the animalContainer from the ContainerOfAnimals (thereby removing animalContainer's BMP from the stage), and then add the id of that animalContainer to a rescuedTotal_Array...
The weird thing is, In the pic below, I'm able to rescue animalContainer2 and then rescue animalContainer1... But if I go from animalContainer1 to animalContainer2, it throws a
Uncaught TypeError: Cannot read property 'id' of undefined` error.
...
Basically:
Working way: get ID 22, then up to ID 21 ->>> One thing I notice with this is that the element name doesn't change for the ID... not sure why... so for the broken way... it may not be able to even associate element name animalContainer2 with an ID? But I don't know how the ID and the name could get disassociated like that..
ID in rescued array...: 22, element name: animalContainer1, rescued array length: 2, elem index pos: 0, index: 0
ID in rescued array...: 21, element name: animalContainer1, rescued array length: 2, elem index pos: 1, index: 0
Broken way- throws error: get ID 21, then down to ID 22
ID in rescued array...: 21, element name: animalContainer1, rescued array length: 1, elem index pos: 0, index: 0
1. Uncaught TypeError: Cannot read property 'id' of undefined
Code Snippet:
function grabIt(NPC_id, index) {
//check if NPCid already exists in array before adding...
if ($.inArray(ContainerOfAnimals.children[index].id, rescuedTotal_Array) == -1) {
rescuedTotal_Array.push(ContainerOfAnimals.children[index].id);
}
if (rescuedTotal_Array.length == 2) {
for (var z = 0; z < rescuedTotal_Array.length; z++) {
console.log("ID in rescued array...: " + rescuedTotal_Array[z] + ", \n element name: " + ContainerOfAnimals.children[index].name + ", rescued array length: " + rescuedTotal_Array.length + ",\n elem index pos: " + z + ",\n index: " + index);
}
}
//when I remove the first added element to rescuedTotalArray... the 2nd element's index assumes first added element's index... (goes from 1 to 0)
console.log(index);
console.log("element removed: " + ContainerOfAnimals.children[index]);
stage.update();
ContainerOfAnimals.removeChild(ContainerOfAnimals.children[index]);
updateHUD();
}
I have no idea why the order in which I store elements in the array/remove them from the stage would matter...
EDIT: I feel I can solve this issue by removing element from ContainerOfAnimals by element ID instead of by index... by Container object offers no getChildID() function...
so I tried:
var childToRemove = document.getElementById(ContainerOfAnimals.children[index]);
console.log(childToRemove);
ContainerOfAnimals.removeChild(childToRemove);
console.log(childToRemove) gives me null for children
But doing this: console.log(ContainerOfAnimals.children[index].id); gives me id 21, which is the correct id...
Beware that EaselJS elements aren't DOM elements.
Supposing you want to remove an element by its id, I'd suggest this :
function removeElementById(container, id) {
for (var i=container.getNumChildren(); i-->0;) {
if (container.getChildAt(i).id===id) {
container.removeChildAt(i);
return true;
}
}
return false;
}
But using the id might not be the best architectural solution. In my EaselJS program I keep a reference to the objects themselves so that I don't have to search them.
It may be possible that your rescuedTotal_Array is an associative Array (or was morphed to one) and could contain something like:
rescuedTotal_Array = [1:object,2:object,object:object]
The array above has the length of 3. But you cannot access the third element via index 2, because its index is some kind of object.
Try to dump the content of your rescuedTotal_Array before accessing it (so you can see if everything is ok). This is not a solution, but it may help you so that you can find the error by yourself. Use something like
for (index in rescuedTotal_Array) {
if (rescuedTotal_Array.hasOwnProperty(index)) {
console.log(rescuedTotal_Array[index]);
}
else {
console.log("no entry found for index: "+index);
}
}
I am not entirely sure whatever if this is the case (I can't know it without taking a look at your whole program), but it could be this:
I assume that your "ContainerOfAnimals" is a HTML element and that you are retrieving it's child nodes using it's .children -property. The thing is that .children -property returns a "live list"!
Let me demonstrate:
var children = ContainerOfAnimals.children;
console.log(children.length) // 2;
ContainerOfAnimals.removeChild(children[0]);
console.log(children.length) // 1;
Let's say that you remove the very first child of ContainerOfAnimals. What happens? The children -list has now changed so that the second element becomes the first element, third element becomes the second element etc...
You could fix this by using a static array containing all the children and like this:
var children = [].slice.call(ContainerOfAnimals.children);
console.log(children.length) // 2;
ContainerOfAnimals.removeChild(children[0]);
console.log(children.length) // 2;
Now removing the element from the DOM -tree does not remove the element from the children array (which is static). Remember, that [].slice.call does not work in IE8 or lower.
Let me know if this was the problem. I have not enough points to comment, so I had to make a full post :)
HY,
Try for statement like this:
for(var z = rescuedTotal_Array.length; z >= 0; z-- )
If you noticed I just turned it around. It starts at the and going backwards.
It is stupid but, maybe this error is just that :)
Let me know how it goes!
Regards
I have a similar datastructure as this:
var GrandGrandChild = mongoose.Schema({
attribute: String,
id: Number
});
var GrandChild = mongoose.Schema({
children: [GrandGrandChild],
id: Number,
irrelevantAttribute: String
});
var Child = mongoose.Schema({
children: [GrandChild],
id: Number,
irrelevantAttribute2: String
});
var Parent = mongoose.Schema({
children: [Child],
id: Number,
irrelevantAttribute3: String
});
var GrandParent = mongoose.Schema({
children: [Parent],
id: Number,
irrelevantAttribute4: String
});
These are a lot of collections with subdocuments in them.
Note that the ID's are unique to their siblings, but not unique to all elements with that same schema.
So one grand parent can have an parent with id 0, and another grandparent can also have a parent with id 0. but one grandparent can not have 2 parents with id 0.
The only schema that gets saved is the GrandParent schema, and mongoose/mongodb makes a nice big single document of all the data of this grandparent. (Exactly what i am looking for)
So here is my issue: I have a GrandParent ID, Parent ID, Child ID, GrandChildID and GrandGrandChild ID, and i want to somehow get only the GrandGrandChild object to which all these ID's are pointing.
The ugly way would be to, but currently the only way i can get to work, is to make a query that gets this big document of GrandParent, and manually loop through all arrays to find the right Parent, then loop again to find the right child, then loop again to find the right grandchild, then loop again and find the grandgrandchild im needing here.
My question is, how would i compose a query in mongoose that either returns only the grandgrandchild document, or the grandparent document with only the children attribute included, and in that children attribute only the parent object included that refers to the child object that refers to the grandchild object that refers to the grandgrandchild object, allowing the following with the result:
GRANDPARENT PARENT CHILD GRANDCHILD GRANDGRANDCHILD
grandparent.children[0].children[0].children[0].children[0].attribute;
I hope someone can help me on this query, as far is i got is this:
GrandParentModel.findOne(
{
"id" : 0,
"children.id" : 0,
"children.children.id" : 0,
"children.children.children.id" : 0,
"children.children.children.children.id" : 0
},
{"children.children.children.children.$" : 1}, callback);
The problem with this query is that the unnessicary siblings arent trimmed away.
I hope someone can help me out.
Hylke Bron
it has been some time since I asked this question, but I think I found a rather elegant way of working with these kind of structures.
In this case I'll show how it works with only GrandParent, Parent and Child.
Instead of storing a list of subdocuments in each document (GrandParent.children, Parent.children), I created an unique identifier of the following structure:
Child.referenceId = {
grandparent: "some key to the grandparent of the parent",
parent: "some key to the parent",
child: "key of this child"
};
Parent.referenceId = {
grandparent: "some key to its grandparent",
parent: "key of this parent"
}
GrandParent.referenceId = {
grandparent: "key of this parent"
}
This creates a hierarchy of GrandParent > Parent > Child.
The models would be something like the following:
var idStructure = {
grandparent: { type: String, required: true },
parent: { type: String, required: false },
child: { type: String, required: false }
};
var GrandParent = mongoose.Schema({
id: idStructure,
irrelevantAttribute: String
});
var Parent = mongoose.Schema({
id: idSructure,
irrelevantAttribute: String
});
var Child = mongoose.Schema({
id: idStructure,
irrelevantAttribute: String
});
Notice that a Parent doesnt directly knows its parent, for they are not stored as subdocuments. Yet there still is a connection between Parent and Child, through the referenceId.
When searching for the whole familytree of a GrandParent, one would simply execute 3 queries, and then connect them correctly:
// First find all children which belong to the grandparent
Child.find({"id.grandparent" : "some key to the grandparent"})
.exec(function(err, children)
{
if(err)
return;
Parent.find({"id.grandparent" : "some key to the grandparent"})
.exec(function(err, parents)
{
if(err)
return;
// Loop through the parents and children to connect them before returning to a client
for(var i = 0; i < parents.length; i++)
{
var parent = parents[i];
parent.children = [];
// loop through the children to check if they belong to the current parent
for(var j = 0; j < children.length; j++)
{
var child = children[j];
if(parent.id.parent == child.id.parent)
parent.children.push(child);
}
}
// After filling the children into the parents, get the grandparents and do the same for the parents and grandparents as done for the children and parents.
GrandParent.find({"id.grandparent" : "some key to the grandparent"})
.exec(function(err, grandparents)
{
// TODO: the same as done above (two loops, one loops the grandparents, other loops the parents
// Once this is finished, we have a filled grandparent
});
});
});
The code above would result in just ONE grandparent, filled with parents, which are filled with children.
The reason no more grandparents would be found is because the id of the grandParent should be unique, for the referenceId of the grandparent only has a grandparent property.
I hope i made my point clear, because through this method, one can easily search for one specific child, easily get its parent through the reference id, and its grandparent also through the reference id.
It might be a bit complex, but once you figure the method out for yourself, its all kinda straight forward.
Hylke
Is very difficult to get this kind of things work in a clean way.
I didn't find a clean solution on this topic, but maybe I can help you with the looping thing.
You can avoid the loop using:
var doc = parent.children.id(id); Finding a sub-document
I hope this help you.
Regards,
Sebastian.
this worked for me
model.find({_id:args.id},{ commentList: { $elemMatch: { _id: todo.commentList[todo.commentList.length-1] } } },(err, todos) => {
if (err) reject(err)
else resolve(todos)
console.log(todos);
})
$elemMatch (projection)