I'm new to javascript and am not real capable with the asynchronous aspects, closures, etc. I have done a few days research on this and lots of trial & error but can't seem to get past my issue, which is:
I am trying to walk a tree structure, gathering all the bottom level nodes (those without child nodes). This node data gets loaded into global arrays (not optimal but needed). The walk function I'm using is recursive. But the asynchronous nature cause the first call to the function to return before the recursive calls return, so the complete tree doesn't get interrogated. I tried putting it in an anonymous function which seems to get the entire tree traversed, but then the global arrays are not being loaded (inaccessible?).
BTW, the real code is on a separate, isolated network so a direct cut & paste to here is not possible. Below is functional equivalent of the relevant parts (unless I've made a typo). Apologies for that. Any help would be appreciated.
var nodeList = new Array(); // global variable
function someFunction(rootNode) {
// unrelated processing here
walkTree(rootNode); // gather the childless nodes
return;
}
function walkTree(node) {
return function() { // required in order traverse the entire tree
// but with it, nodeList does not get populated
var num = node.numChildren();
var childNodes = node.getChildNodes();
for (var i=0; i<num; i++) {
var currentNode = childNodes.item(i);
if (currentNode.numChildren() > 0) {
walkTree(currentNode);
}
else {
var obj = new Object();
/// extract certain attributes of current node here
/// and make a variant
nodeList[nodeList.length] = obj;
}
} // END for
} // close anonymous function
} // END FUNCTION
If you don't need asynchronous execution, your code can be simplified:
var nodeList = [];
function someFunction(rootNode) {
// unrelated processing here
walkTree(rootNode); // gather the childless nodes
return;
}
function walkTree(node) {
var num = node.numChildren(),
childNodes = node.getChildNodes();
for (var i=0; i<num; i++) {
var currentNode = childNodes.item(i);
if (currentNode.numChildren() > 0) {
walkTree(currentNode);
}
else {
var obj = new Object();
/// extract certain attributes of current node here
/// and make a variant
nodeList.push(obj);
}
}
}
If you do need asynchronous execution, the implementation would depend on what asynchronous mechanism you used (web workers, simulation using setTimeout, a framework like Clumpy, etc.).
With Clumpy, for instance, you might code it like this (untested):
var nodeList = [],
clumpy = new Clumpy();
function someFunction(rootNode) {
// unrelated processing here
walkTree(rootNode); // gather the childless nodes
return;
}
function walkTree(node) {
var num = node.numChildren(),
childNodes = node.getChildNodes(),
i;
clumpy.for_loop(
function() { i = 0; },
function() { return i < num; },
function() { i++; },
function() {
var currentNode = childNodes.item(i);
if (currentNode.numChildren() > 0) {
walkTree(currentNode);
}
else {
var obj = new Object();
/// extract certain attributes of current node here
/// and make a variant
nodeList.push(obj);
}
}
);
}
Related
I got the following code which works perfectly. What it does is: in a table it highlights the corresponding table header cell and table first column cell when you hover over any table cell.
// Row & Column Highlight
(function() {
var gridCellRow = null,
gridCellCol = null,
tableElement = document.getElementsByClassName('inner_table');
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
var gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
}
}
}());
However, JSHint tells me, that
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
function gridCellParents(currentCell) {
return gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
is not best practice "Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function."
as well as
gridCell[j].addEventListener('mouseover', (function() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}));
gridCell[j].addEventListener('mouseout', (function() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}));
}
is not best practice "Don't make functions within a loop."
So how am I correctly and according to best practice building this whole function?
Function deceleration shouldn't be within loops because it makes no
sense to re-create the same function over and over again, in a
"continuous flow" (unlike other situation where the same function
might be created again, in a more complex code). The main reason is
because of hoisting and it strongly goes against javascript
principles to write functions declarations inside loops.
A good starting point, with a more ordered code:
// Row & Column Highlight
(function() {
var gridCellRow,
gridCellCol,
gridCell,
tableElement = document.getElementsByClassName('inner_table');
function gridCellParents(currentCell) {
gridCellRow = currentCell.parentNode.firstElementChild,
gridCellCol = currentCell.parentNode.parentNode.rows[0].cells[currentCell.cellIndex];
}
function onMouseEnter() {
gridCellParents(this);
gridCellRow.classList.add('highlight');
gridCellCol.classList.add('highlight');
}
function onMuoseLeave() {
gridCellRow.classList.remove('highlight');
gridCellCol.classList.remove('highlight');
}
for (var i = 0, len_i = tableElement.length; i < len_i; i++) {
if (tableElement[i].getElementsByClassName('row_label_cell').length > 0) {
gridCell = tableElement[i].getElementsByClassName('input_cell');
for (var j = 0, len_j = gridCell.length; j < len_j; j++) {
gridCell[j].addEventListener('mouseenter', onMouseEnter);
gridCell[j].addEventListener('mouseleave', onMuoseLeave);
}
}
}}());
As you can see, I've modified your events to mousenter and mouseleave which might better suit your needs and be better for overall performance.
Update - delegated version:
// Row & Column Highlight
(function() {
var gridCell,
tableElement = document.querySelectorAll('.inner_table');
function getCellParents(cell){
return {
row : cell.parentNode.firstElementChild, // row
col : cell.parentNode.parentNode.rows[0].cells[cell.cellIndex] // col
};
}
function updateGridCellParents(cell, state) {
state = state ? 'add' : 'remove';
var parents = getCellParents(cell);
parents.row.classList[state]('highlight');
parents.col.classList[state]('highlight');
}
funciton checkTarget(target){
// make sure the element is what we expected it to be
return target.className.indexOf('input_cell') != 0;
}
function onMouseEvents(e){
checkTarget(e.target) && updateGridCellParents(e.target, e.type == "mouseover");
}
document.body.addEventListener('mouseover', onMouseEvents);
document.body.addEventListener('mouseout', onMouseEvents);
})();
In addition to the previous answer, I think it is important to also state why it is a bad practice.
The issue when creating functions inside loops is that they often use values that depends on the loop's iteration. Let's have an example.
// Create three function, that writes their number
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(function(){
document.write(i);
});
}
// Call them.
funcs.forEach(function(f){
f();
});
One may expect the above code to write 1 then 2 then 3. However, because variables in JS are not block-scoped but function-scoped (except for the new let and const), the closure of all three of these functions will actually use the exact same i: 3, the last value it had been given (and thus the value it still has).
Because of this behaviour, this is very easy to make mistakes. Hence, it is not recommended.
If you need to create a function that depends of the value of a loop, you can use a factory.
// Create a factory function that returns a
// function that writes the argument.
function writerFactory(msg){
return function(){
document.write(msg);
}
}
// Create three functions, that write their number.
var funcs = [];
for(var i=0; i<3; i++){
funcs.push(writerFactory(i));
}
// Call them.
funcs.forEach(function(f){
f();
});
This time, each function has a different closure: the one that is created by each call of the factory. They all have access to a different msg.
I am trying to implement localStorage with my simple OOP todo list.
The fiddle is here: https://jsfiddle.net/b81t2789/
I thought I could just treat the local storage like an array and copy the logic I used with my actual array but that doesn't work.
Here, right after pushing the task into the array, I added a line that stores the task in the local storage and stringifies it:
// function that adds new task to the array
function pushArray(){
var newtask = new Task(toDo.value, "No note yet");
taskItems.push(newtask);
var storedTask = localStorage.setItem(newtask, JSON.stringify(newtask));
displayStorage(result2, storedTask);
displayArray(result, newtask.Name);
appendNote(result, newtask);
}
Then right below the function that displays the new array element I added one that retrieves the item from local storage, parses it, then creates a DOM element with the new task and appends it to another container.
//function that displays array elements
function displayArray(parent,obj){
var task = make("div","class","taskitem",obj);
parent.appendChild(task);
fadeIn(task);
}
//function that displays storage elements
function displayStorage(parent,obj){
var retrieveObject = localStorage.getItem(obj);
var parseTask = JSON.parse(retrieveObject);
var newDiv = make("div", "class", "newdiv", parseTask);
parent.appendChild(newDiv);
fadeIn(newDiv);
}
This doesn't work at all, not sure why, and then if I were to be able to get this to work how would I continue to go about storing and updating notes like I did in the array with local Storage? I thought this would be easy as I figured out how to make a todo with objects and arrays pretty quickly (when I thought it would be super difficult, but it's been a week now and I've made no progress!)
I guess these are the pitfalls of learning to code by yourself, any help would be much appreciated thank you!
Here is the full javascript code:
//getElementById shortcut
function grab(id) {
return document.getElementById(id);
}
// add eventlistener shortcut
var when = function() {
return function(obj, event, func) {
obj.addEventListener(event, func, false);
};
}();
//Custom function to create DOM elements and set their contents
function make(el,type,name,content){
var theElement = document.createElement(el);
theElement.setAttribute(type, name);
theElement.innerHTML = content;
return theElement;
}
//compute style shortcut
function setStyle(theElement){
return window.getComputedStyle(theElement);
}
//fade in shortcut.
function fadeIn(theElement){
var compute = setStyle(theElement).opacity;
theElement.style.opacity = 1;
}
/*****************************************************/
var toDo = grab("todo");
var result = grab("demo");
var demolist = grab("demolist");
var button = grab("btn");
// submit input on enter which fires function that pushes task into the array.
when(toDo, "keypress", function(event){
if (event.key == "Enter" || event.keyCode == 13) {
pushArray();
toDo.value = "";
}
});
// "SHOW ARRAY" FUNCTION to verify that the array is being updated (I like this better than using the console);
when(button, "click", function(event){
demolist.innerHTML = "";
for(i=0; i< taskItems.length; i++){
demolist.innerHTML += taskItems[i].Name + " " + taskItems[i].Note + "<br>";
}
});
function showNotes(theNote){
var defaultNote = "No note yet";
if(theNote){
}
}
var taskItems = [];
/*********************************************************/
//create Task object
function Task(name, note){
this.Name = name;
this.Note = note;
this.completed = false;
}
// function that adds new task to the array
function pushArray(){
var newtask = new Task(toDo.value, "No note yet");
taskItems.push(newtask);
displayArray(result, newtask.Name);
appendNote(result, newtask);
}
//function that displays array elements
function displayArray(parent,obj){
var task = make("div","class","taskitem",obj);
parent.appendChild(task);
fadeIn(task);
}
//function that displays notes
function appendNote(theElement,obj){
var newClassItem = make("input","class","tasknote");
theElement.appendChild(newClassItem);
when(newClassItem, "keypress", submitNote.bind(null, obj, newClassItem));
}
//function for submitting notes
function submitNote(task,noteInput){
if (event.key == "Enter" || event.keyCode == 13) {
task.Note = noteInput.value;
var newNote = make("div", "class", "hasNote", task.Note);
noteInput.parentNode.replaceChild(newNote, noteInput);
fadeIn(newNote);
when(newNote,"dblclick", function(){
newNote.parentNode.replaceChild(noteInput, newNote);
});
}
}
Being localStorage a key-value storage, depending on your needs, you are better off serializing (stringifying, whatever) the array and saving in a single index.
var tasks = [
'post the question on SO',
'describe it carefully',
'get a nice reply',
'implement the suggested solution'
];
If you really need to split it for performance reasons, you have to index them by a arbitrary index. If you have reordering it gets tricky and you can either reflush the whole set of tasks every time someone adds/edits/deletes/reorder the tasks (memory-efficient, but very CPU intensive) or save the indexes in a different key so you can reconstruct the order later, like:
var tasks = {
'task1': 'implement the suggested solution',
'task2': 'describe it carefully',
'task4': 'get a nice reply',
'task9': 'post the question on SO'
};
var tasksOrder = [9, 2, 4, 1];
The first idea is very simple to implement, but will give you problems with arbitrarily long lists, the second one is much more easy on the CPU but much harder to implement (and uses more memory). It depends on the specifics of your case.
My code:
function BayesNet(vars) {
this.variables = {};
this.numVars = Object.keys(this.variables).length;
for (v in vars) {
this.variables[v] = new BayesNode(vars[v]);
this.variables[v].CPTorder = this.generateDomainRows(this.variables[v].parents);
this.variables[v].fullCPT = {}
for (var i = 0; i < this.variables[v].CPTorder.length; i++) {
this.variables[v].fullCPT[this.variables[v].CPTorder[i]] = this.variables[v].CPT[i];
}
this.variables[v].blocks = false;
}
}
function BayesNode(obj) {
this.parents = obj.parents;
this.children = obj.children;
if (typeof obj.domain == 'undefined')
this.domain = ['T','F'];
else
this.domain = obj.domain;
this.observation = obj.observation;
this.CPT = obj.CPT;
this.sampleDistribution = [];
for (var i = 0; i < this.CPT.length; i++) {
var s = [];
if(this.CPT[i].length == this.domain.length - 1)
this.CPT[i].push(1 - sumArray(this.CPT[i]));
s.push(this.CPT[i][0]);
for (var j = 1; j < this.domain.length - 1; j++) {
s.push(this.CPT[i][j]+s[j-1]);
}
s.push(1.0);
this.sampleDistribution.push(s);
}
//TODO: Check if CPT is valid
}
My problem is that BayesNode.parent is copied incorrectly.
BayesNode.parent should be an array containing items, and when I run the debugger through the constructor, this.parents is the correct value . However, once I go back to the BayesNet constructor, parents is an empty array. What could be causing this? All other variables in the object behave as expected.
Javascript executes function calls asynchronously. This is the root cause of your issue. You should use callbacks to execute code that is dependent on results of function calls.
Let me explain this using your code:
this.variables[v] = new BayesNode(vars[v]);
this.variables[v].CPTorder = this.generateDomainRows(this.variables[v].parents);
When you call the constructor, JS does NOT wait for the function to finish executing before moving onto the next line of code. When JS comes across "this.variables[v].parents", it is empty, because, the function call in the previous line is still executing asynchronously.
Javascript code design requires a different approach as compared to most other languages.
I don't see any issues in your code, its strange why its becoming empty. but to solve the problem there is a way. change the code as follow.
after this line
this.variables[v] = new BayesNode(vars[v]);
Add the follow
this.variables[v].parents = vars[v].parents;
I see you are not modifying the parents in the constructor, it will work for time being before you find out whats happening. you might have done this already :)
I'm working on this assignment: http://www.cs.colostate.edu/~anderson/ct310/index.html/doku.php?id=assignments:assignment_2
I'm building a binary tree in Javascript. Basically it's a relational tree, we have this tree class that takes 3 arguments: data,left child,right child. Left & right child are just new tree objects stored in var.
Here's the tree class:
function Tree( data, left, right )
{
// pravite data
var data = data;
var leftChild = left;
var rightChild = right;
// public functions
this.getData = function()
{
return data;
}
this.left = function()
{
return leftChild;
}
this.right = function()
{
return rightChild;
}
}
Here's the toString() method
Tree.prototype.toString = function(indent)
{
var spaces = '';
if (!indent)
{
indent = 0;
}
else{
spaces = spaces*indent;
}
// if the left tree isn't void
if(this.tree().left())
{
this.tree().left().toString(indent+5);
}
if(this.tree().right())
{
this.tree.right().toString(indent+5);
}
print(spaces + this.data);
}
Here's the data I get passed into. We're using Rhino in the command line to test.
var abc = new Tree('a', new Tree('b'), new Tree('c'));
abc.toString()
I get a stack over flow on the toString method. My professor says to use the this.Left() in the if statement because when you recurse it will fail when it's undefined.
Any ideas what's wrong?
Well, your last reference to the right branch is missing some parentheses...
this.tree.right().toString(indent+5) // <-- right here
That asid, I don't see this.tree() defined anywhere. I think it should be this.left() and this.right() in all those places.
Also, for a slight optimisation, consider something like:
var l = this.left();
if( l) l.toString(indent+5);
This avoids an extra function call.
Your recursive function has no base case. It will just keep going forever.
If your node doesn't have any children, than don't call ToString on them()
I was wondering if it was possible to assign values to an element object. In this case, I wish to assign the returns from the setTimeout() function to an object within an element object.
For example:
var elem = document.getElementById('target');
elem.timeouts = new Object();
elem.timeouts.sometimeout = setTimeout('...', 1000);
So I could then do:
clearTimeout(elem.timeouts.sometimeout);
I know this might seem bad practice etc, but is it possible or will it cause browsers to catch fire and turn on their user etc.
Thanks.
DOM elements are Host objects (aka non-native) and as such they can do almost anything they want. It's not guaranteed that your expandos will work. In particular IE used to have problems with them. It's highly recommended to read this article:
What’s wrong with extending the DOM by #kangax
(it is from one of the Prototype.js developers who experienced the drawbacks of such bad habits. They've rewritten the whole library just to save themselfs from more headaches)
Now if you add uniqueID to elements in non-IE browsers (IE has it by default) and then your data function becomes a simple lookup ~ O(1). The real information will be stored in a closure.
It's 2-4x faster than jQuery.data (test)
data(elem, "key", "value");
1.) Hash table
var data = (function () {
var storage = {};
var counter = 1;
return function (el, key, value) {
var uid = el.uniqueID || (el.uniqueID = counter++);
if (typeof value != "undefined") {
storage[uid] || (storage[uid] = {});
storage[uid][key] = value; // set
} else if (storage[uid]) {
return storage[uid][key]; // get
}
};
})();
2.) Array
If you want to avoid expandos all together you can use an array to hold elements (but it's slower)
var data = (function () {
var elements = [];
var storage = [];
return function (el, key, value) {
var i = elements.indexOf(el);
if (typeof value != "undefined") {
if (i == -1) {
i = elements.length;
elements[i] = el;
storage[i] = {};
}
storage[i][key] = value; // set
} else if (storage[i]) {
return storage[i][key]; // get
}
};
})();
Array.prototype.indexOf (fallback)
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (item) {
var len = this.length >>> 0;
for (var i = 0; i < len; i++) {
if (this[i] === item) {
return i;
}
}
return -1;
};
}
You're welcome! :)
It's possible, DOM elements retrieved by JS, are JS variables :) ..BTW it's not a common practice do that stuff in that way. I think the answer of #galambalazs is more deep and complete ;)
if you are using jquery, you can story it in the "data"
http://api.jquery.com/jQuery.data/
No, actually that should work just fine. From what I understand, adding that return to the object should be fine. It's better than storing it in a global container IMO.