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);
Related
I'm trying to write a simple function that iterates through text and replaces any href's it comes across with text instead;
var REPLACE = [
{expression: www.anyhref.com, value: 'website'}
];
function(instance) {
instance = function {
var insteadURL;
insteadURL: = REPLACE.match(function(expression) {
return element.classList.contains(expression);
});
return(
insteadURL ? insteadURL.value : getElementText(expression)
);
}
}
I feel as though I may not be using the match method or the conditional operator properly but from what I understand this should work. But of course it doesn't.
If you're trying to replace links (i guess) in your text, try this regex:
/<a.*?href="www.anyhref.com".*?\/a>/g
Then you would have to add for each href you want to replace an entry in your array.
If you are in DOM context you can do the following:
To iterate through the DOM you can use this function:
function domReplace(node, iterator) {
switch (node && node.nodeType) {
case 1: case 9: case 11: {
const newNode = iterator((node.nodeName || '').toLowerCase(), node);
if (newNode && newNode != node && node.parentNode) {
node.parentNode.insertBefore(newNode, node);
node.parentNode.removeChild(node);
}
for (let child = newNode.firstChild; child; child = child.nextSibling)
domReplace(child, iterator);
} break ;
case 3: {
const newNode = iterator('#text', node);
if (newNode && newNode != node && node.parentNode) {
node.parentNode.insertBefore(newNode, node);
node.parentNode.removeChild(node);
}
} break ;
}
}
Then you can replace a if pattern matches .href with custom text:
domReplace(document, (type, node) => {
if (type == 'a') {
for (let i = 0; i < REPLACE.length; i += 1)
if (~(node.href || '').indexOf(REPLACE[i].expression))
return document.createTextNode(REPLACE[i].value);
return document.createTextNode(node.href);
}
return node;
});
Note you should not give document to domReplace but the right dom node to avoid full page replacement
I'm new to nodeJS javascript and I have a simple problem here.
I have a Binary Search Tree (BST) in javascript. Each Node has a value, and a count. We're inserting words into the BST such that each Node represents a word. Upon insertion, if the word is already in the BST, we want to increase the count of that word, where count is a property on the Node.
My problem comes when I want to display the Nodes and their counts. Displaying the counts is not working correctly. That is, the BST.prototype.showWords = function (node) is not correct.
Thank you for your help and insight!!!
files:
bstnode.js - the 'node class'
BST.js - the Binary Search Tree Class
wordCount.js - reads in a text file, splits on spaces to get words, and creates nodes. I want each node to represent a word, and if that word appears multiple times, count++ on that node each time.
// wordCount.js
var BST = require('./bst.js');
var fs = require('fs');
var countNodes = require('./countNodes.js');
// THIS RIGHT HERE DOES NOT WORK CORRECTLY.
BST.prototype.showWords = function (node) {
if (node !== null) {
this.inOrder(node.left);
console.log(node.showCount());
this.inOrder(node.right);
}
};
// get the file, and get it into an array of string-words
var filePath = './simpleTest.txt';
var contents = fs.readFileSync(filePath).toString();
var stringArr = contents.split(' ');
var myBST = new BST();
for (var i=0; i<stringArr.length; i++){
var word = stringArr[i].trim();
var aNode = myBST.find(word);
console.log(aNode);
if ( aNode !== null ) { // then word exists in BST already,
aNode.count++; // node count ++ on that one
}
else { // then word dne in BST, so add it now!
myBST.insert(word);
}
}
myBST.showWords(myBST.root);
// bstnode.js
'use strict';
var Node = function (data, left, right) {
this.data = data;
this.count = 1;
this.left = left;
this.right = right;
};
Node.prototype.show = function () {
return this.data;
};
Node.prototype.show2 = function () {
return (this.data + ' ' + this.count);
};
module.exports = Node;
'use strict';
// bst.js - has the BST CTor function, and requires the bstnode in
var Node = require('./bstnode');
// BST CTor function
var BST = function() { // Binary Search Tree class
this.root = null;
};
BST.prototype.insert = function (data) {
var n = new Node(data, null, null);
if (this.root === null) {
this.root = n;
} else {
var current = this.root;
var parent;
while (true) {
parent = current;
if (data < current.data) {
current = current.left;
if (current === null) {
parent.left = n;
break;
}
} else {
current = current.right;
if (current === null) {
parent.right = n;
break;
}
}
}
}
};
// inOrder: log VALUES in order starting from node param
BST.prototype.inOrder = function (node) {
if (node !== null) {
this.inOrder(node.left);
console.log(node.show() + " ");
this.inOrder(node.right);
}
};
BST.prototype.find = function (data) {
var current = this.root;
while (current && current.data !== data) {
if (data < current.data) {
current = current.left;
} else {
current = current.right;
}
}
return current;
};
module.exports = BST;
a friend helped me out, here's the soln
// THIS RIGHT HERE -- - --
// That's because it should call itself recursively, not the inOrder function!
BST.prototype.showWords = function (node) {
if (node !== null) {
this.showWords(node.left);
console.log(node.showCount());
this.showWords(node.right);
}
};
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.
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
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;
}