I am trying to implement my own getElementById() function in Javascript. My idea/algorithm goes like this:
function myGetElemById(id){
// rootNode I suppose will be the BODY tag.
rootElem = get elements by TAGNAME (rootNode);
elems = rootElems.getChildren();
for(i=0; i<elems.length; i++){
if(!elems[i].hasChildren()){
myGetElemById(elems[i]);
} else {
if(elems[i].id == id)
return elems[i];
else
return null;
}
}
}
Method 1:
function myGetElemById(id){
return document.getElementById(id);
}
Method 2:
function myGetElemById(id){
return window[id];
}
Method 3: (newer browsers)
function myGetElemById(id){
return document.querySelectorAll('#' + id);
}
DONE!
Okay, seriously:
function getById(id, parent, list){
parent = parent || document.body;
list = list || [];
var l, child, children = parent.children;
if(children){
l = children.length;
while(l--){
child = children[l];
if(child.id == id) list.push(child);
getById(id, child, list);
}
}
return list;
}
Check out this feature and maybe you can get ideas
function getElementsStartsWithId( id ) {
var children = document.body.getElementsByTagName('*');
var elements = [], child;
for (var i = 0, length = children.length; i < length; i++) {
child = children[i];
if (child.id.substr(0, id.length) == id)
elements.push(child);
}
return elements;
}
first, you must deal with the elements has children, call myGetElemById() and choose to return or not to return, depend on the result. like this
...
if(!elems[i].hasChildren()){
var result = myGetElemById(elems[i]);
if (result != null)
return result;
} else {
...
second why iterate over all the elements of the dom? the native function is much more faster.
Custom get element by ID method using BFS:
function myGetElementById(id, root){
let queue = []; // Using array here but linkedList is more performant in both time and space complexity
queue.push(root);
let currentNode;
while(queue.length){
currentNode = queue.shift();
if(currentNode.id === id){
return currentNode;
}
queue.push(...currentNode.children);
}
return false;
}
Related
I was trying to make a function that gives you the selected CSS properties of an element those you want. But it's pretty laggy if used in console as of it needs to get and match all CSS properties.
function styleOf(elementUseSelectors, propertiesToCheck, tellInConsole) {
var element = elementUseSelectors;
var Arguments = propertiesToCheck;
var calculatedProperties = [];
var matchedProperties = [];
if (tellInConsole !== undefined && tellInConsole == true) {
console.warn("Running styleOf() Please Don't Do Other Calculations This Function Disables Console.")
}
for (var i = 0; i < Object.keys(getComputedStyle(element)).length; i++) {
var value = getComputedStyle(element).getPropertyValue(Object.entries(getComputedStyle(element))[i][0].replace(/([A-Z])/g, ' $1').trim().replaceAll(" ", "-").toLowerCase());
if (value !== "") {
calculatedProperties.push(Object.entries(getComputedStyle(element))[i][0].replace(/([A-Z])/g, ' $1').trim().replaceAll(" ", "-").toLowerCase() + ": " + value);
}
}
for (var i = 0; i < calculatedProperties.length; i++) {
for (var a = 0; a < Arguments.length; a++) {
if (calculatedProperties[i].includes(Arguments[a])) {
window.splitted = calculatedProperties[i].split("");
window.joinThis = [];
for (var k = 0; k < splitted.indexOf(":"); k++) {
joinThis.push(splitted[k]);
};
if (joinThis.join("") == Arguments[a]) {
matchedProperties.push(calculatedProperties[i]);
}
}
}
}
if (tellInConsole !== undefined && tellInConsole == true) {
console.warn("StyleOf() Calculations Completed You Can Now Use Console.")
}
return matchedProperties
}
The TreeWalker object is designed to quickly parse DOM nodes in a document. If you expand on the example given above in the MDN Web Docs you can output the computed CSS properties for a given node.
The first property of the method is the node you want to traverse – in this case it's document.body:
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ELEMENT,
{ acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT; } },
false
);
var nodeList = [];
var currentNode = treeWalker.currentNode;
while(currentNode) {
nodeList.push(currentNode);
const style = getComputedStyle(currentNode)
console.log(style)
currentNode = treeWalker.nextNode();
console.log("moving to next node...");
}
Welp #kaiido answered the question.
function styleOf(element, properties) {
const computed = getComputedStyle(element);
return properties.map( key => key + ": " + computed[ key ] )};
var style = styleOf(document.getElementsByTagName("body")[0], ["height", "width", "background-color", "font-size", "color", "font-family"]);
console.log(style);
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 ultimately wanting to create a jQuery plugin that loops through the top level of the DOM and adds elements to an object until it gets to a heading, at which point it pushes an object with the text of that heading.
The following sibling elements would be added to this new object until a new heading is encountered — if the heading is at the same level, a new object is created as a sibling of the parent object as before, and DOM siblings are added to that instead of the first object; if the heading is at a lower level, that's added as a child of the first object, and sibling DOM elements are added as children to that heading object; if it's a higher level headline, a new object is added one level above the last heading object and the cycle continues.
Example:
<p>wooo</p>
<h1>stuff</h1>
<p>stuff</p>
<p>more stuff</p>
<h2>yet more stuff</h2>
<p>still more stuff</p>
<h3>even still more stuff</h3>
<p>yep — stuff!</p>
<h1>still yet more stuff</h1>
<p>stuff stuff stuff</p>
<p>stuff stuff stuffarino</p>
Becomes...
{
'p_wooo': HTMLElementObject,
'h1_stuff': {
'p_stuff': HTMLElementObject,
'p_more_stuff': HTMLElementObject,
'h2_yet_more_stuff': {
'p_still_more_stuff': HTMLElementObject,
'h3_even_still_more_stuff': {
'p_yep_stuff': HTMLElementObject,
}
},
},
'h1_still_yet_more_stuff': {
'p_stuff_stuff_stuff': HTMLElementObject,
'p_stuff_stuff_stuffarino': HTMLElementObject
{
}
Here's what I have so far:
var root = $(res)
.filter('#contents')
.children()
.not('style'); // Don't need no stylesheets hurr!
var sections = root.filter('h1');
var outline = {};
for (var i = 0; i < sections.length; i++) {
var children;
if (i+1 <= sections.length) {
children = root.filter(sections[i]).after().nextUntil(sections[i+1]).filter(function(){return $(this).text().trim() !== '';});
}
var slug = getSlug($(sections[i]).text(), {separator: '_'});
outline[slug] = children;
}
console.dir(outline);
Alas, it only works for H1s. How would I turn this into a recursive function that adds H2-H6s?
I'll start you with an example that traverses the nodes and adds them all into the same tree object. It should be fairly easy to figure out the rest from here:
JSBin: http://jsbin.com/bixekutuhe/1/edit?html,js,output
// Helpers
function isNode(el) { return el && el.nodeType === 1; }
function tag(el) { return el.tagName.toLowerCase(); }
var tree = {}, key;
var node = document.body.firstElementChild;
while (isNode(node) && tag(node) !== 'script') { // add blacklists or whitelists that you might need
key = node.textContent;
tree[node.tagName.toLowerCase() + '_' +key.split(' ').join('_')] = node;
node = node.nextElementSibling; // move to next element
}
console.log(tree);
Update
Try the following example instead:
var tree = {};
var currentTree = tree, tagName, key;
var node = document.body.firstElementChild;
function isNode(el) { return el && el.nodeType === 1; }
function tag(el) { return el.tagName.toLowerCase(); }
while (isNode(node)) {
tagName = tag(node);
key = tagName + '_' + node.textContent.trim().split(' ').join('_');
switch(tagName) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
if (tagName === 'h1') {
currentTree = tree[key] = {};
} else {
currentTree = currentTree[key] = {};
}
break;
default:
currentTree[key] = node;
break;
}
// Move to the next element
node = node.nextElementSibling;
}
console.log(tree);
Is there a way to convert: $("#first").find("input").not("td td input")
(http://jsfiddle.net/4K9TG/)
...into a querySelectorAll selector?
(continuing from jQuery: find() children until a certain threshold element is encountered)
not is simply a filter over a collection that satisfies a condition. See if this helps.
var query = function(selector, element) {
element = element || document;
return [].slice.call(element.querySelectorAll(selector));
};
var not = function(selector) {
var exclude = query(selector);
return function(element) {
return exclude.indexOf(element) == -1;
};
};
var inputs = query('#first input').filter(not('td td input'));
Here's a demo: http://jsbin.com/EJAyiSux/1/edit
You can implement most jQuery methods as simple filter and map sequences with higher-order functions, as shown above. After all, jQuery collections are just arrays of elements.
I ended up making a function to do this.
http://jsfiddle.net/4K9TG/2/:
var n = get_elements_until (document.getElementById('first'), 'input', 'td')
console.log (n)
function get_elements_until (parent, tagname_to_search_for, tagname_to_stop_at) {
var element_list = []
var stack_current = [parent]
while (true) {
var stack_new = []
for (var s = 0, curlen_s = stack_current.length; s < curlen_s; s++) {
var children = stack_current[s].childNodes
for (var i = 0, curlen = children.length; i < curlen; i++) {
var child = children[i], tagname = child.tagName
if (typeof tagname == "undefined") continue
tagname = tagname.toLowerCase ()
if (tagname == tagname_to_search_for) element_list.push (child)
if (tagname != tagname_to_stop_at) stack_new.push (child)
}
}
stack_current = stack_new
if (stack_new.length == 0) break
}
return element_list
}
I have the following recursive javascript function which is looping over the children of a backbone.marionette CollectionView that has children ItemViews that are in turn CollectionViews:
findViewByCid: function(cid, children){
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
return col[cid];
}
for(child in col){
var grandChildren = col[child].children;
if(cid in grandChildren){
return grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
return this.findViewByCid(cid, grandChildren);
}
}
}
I am calling it like this:
var view = DocumentManager.Documents.treeRoot.findViewByCid(model.cid);
The problem is the line:
return this.findViewByCid(cid, grandChildren);
If I have a hierarchy like this
c1
|_c2
|_c3
|_c4
|_c5
Then te return statement will cause the function to exit after passing th3 c2 node and never get to c4 etc.
If I remove the return statement, the correct child is found but null is returned.
How can I continue parsing the hierarchy and return a value?
return will exit your function.
Try to save everything in a var and return at the very end, it can be an array if you need to return more than one value.
(and don't declare vars in a for-loop!)
Here is a suggestion :
findViewByCid: function(cid, children){
var willBeReturned=[];
var grandChildren;
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
willBeReturned[willBeReturned.length] = col[cid];
}
for(child in col){
grandChildren = col[child].children;
if(cid in grandChildren){
willBeReturned[willBeReturned.length] = grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
willBeReturned[willBeReturned.length] = this.findViewByCid(cid, grandChildren);
}
}
return willBeReturned;
}
You will need to return only if something was found, else the return statement will break your loop without having searched the other children. It is a simple depth-first-search what you want.
Assuming the function is on the prototype of each child (not only on the root node):
findViewByCid: function(cid) {
var col = this.children;
if (!col) // break if the node has no children
return false;
if (cid in col) // look for cid and return the node if one found
return col[cid];
for (var child in col) {
// search through each child and return the result if something is found
var found = col[child].findViewByCid(cid);
if (found)
return found;
}
// else nothing was found
return false;
}
Or having a function taking a node as an argument:
function findViewByCid(cid, node) {
var col = node.children;
if (!col)
return false;
if (cid in col)
return col[cid];
for (var child in col) {
var found = findViewByCid(cid, col[child]);
if (found)
return found;
}
return false;
}
However, it seems this algorithm will not be able to find the root node. It would be better if you could identify the current node by cid, instead of looking at all of its children:
if (this /*… is what we have searched for */)
return this;
findViewByCid: function(cid, children) {
var col = (arguments.length === 1) ? this.children : children;
if(cid in col){
return col[cid];
}
for(var childKey in col) {
var grandChildren = col[childKey].children,
childView;
if (grandChildren) {
childView = this.findViewByCid(cid, grandChildren);
}
if (childView) {
return childView;
}
}
return null;
}
First of all, this looks like Backbone.js, it may be helpful to tag it if it is. I feel like people might have struggled with similar problems and know better ways to store references to views.
You only want to return something if it was found... just using return on the first recursive call will force the method to stop execution upon searching the first set of grandchildren, even if nothing was found.
I would also add a var in front of the new variable you're introducing in your for loop - without it, the variable is going to be global.
This is what I ended up with and it is backbone.marionette, iterating over a collectionView with an itemView of a CollectionView:
findViewByCid: function(cid){
var self = this,
ret;
function findView(cid, children){
var col = (arguments.length === 1) ? self.children : children,
grandChildren;
if(cid in col){
ret = col[cid];
}
for(child in col){
grandChildren = col[child].children;
if(cid in grandChildren){
ret = grandChildren[cid];
}
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
findView(cid, grandChildren);
}
}
};
findView(cid);
return ret;
}
I believe the line if (cid in col) does not what you want to do. Try
findViewByCid: function(cid, children){
if (this.cid === cid) return this;
var col = (arguments.length === 1) ? this.children : children;
for(var childI in col){
var child = col[childI];
if (child.cid === cid) {
return child;
}
var grandChildren = child.children;
if(grandChildren && (!jQuery.isEmptyObject(grandChildren))){
return this.findViewByCid(cid, grandChildren);
}
}
}