Recursive "parsing" function in javascript - javascript

I'm trying to create a kind of recursive parsing function, but it does not work as it should.
The array to be parsed:
gridArray = [
{
type:"row",
text:"row1",
cols: [
{
type:"col",
text:"col1",
rows: [
{
type:"row",
text:"row1 nested",
cols: [{}]
}
]
},
{
type:"col",
text:"col2",
rows: [{}]
}
]
}
]
The function:
function createHtmlCode(gridArray,level){
for(var y=0; y<gridArray.length; y++){
obRow = gridArray[y];
r+="<div class='row'>";
arCol = obRow.cols;
for(var x=0; x<arCol.length; x++){
obCol = arCol[x];
r+="<div class='col'>";
if(obCol.rows){
createHtmlCode(obCol.rows,level++);
}
r+="</div>";
}
r+="</div>";
}
}
r="";
createHtmlCode(gridArray,1);
At the moment the result (r) is:
<div class="row">
<div class="col">
<div class="row">
<div class="col">
</div>
</div>
</div>
</div>
... but it shoud be:
<div class="row">
<div class="col">
<div class="row">
<div class="col">
</div>
</div>
</div>
<div class="col">
<div class="row">
</div>
</div>
</div>
What am I doing wrong?
Thank you in advance for your tips!

Hahaha! You have a very subtle (but very common) error.
See here:
arCol = obRow.cols;
That's the bug, if you look closely.
Got it? Or you want me to spoil it?
The error is: in Javascript, if you don't declare a variable as local, it defaults to global. arCol gets reset in the recursive call!
All the variable declarations should be explicit:
var arCol = obRow.cols;
Other tips:
x and y should be used to indicate spatial information (on the "x-axis" and "y-axis"); use i and j (and if necessary k) for array indices. And declare them as local.
do not use global mutable values -- in this example r. Instead have the function return the string
never use the same name for a global variable and a formal parameter (gridArray in this case) or a local variable. The computer will not be confused, but you will be.
don't put a type name ("array") in a variable name without a specific reason.
don't put a simple expression a variable if you are only going to use it once without a good reason
So the corrected code would be
function createHtmlCode(grid,level){
var r = "";
for(var i=0; i<grid.length; i++){
r+="<div class='row'>";
var arCol = grid[i].cols;
for(var j=0; j<arCol.length; j++){
r+="<div class='col'>";
var rows = arCol[j].rows;
if(rows){
r += createHtmlCode(rows,level++);
}
r+="</div>";
}
r+="</div>";
}
return r;
}
console.log(createHtmlCode(myGrid,1));

Related

How can I access classes within arrays and style them using javascript

So let's say I have 3 different classes: one, two, and three. Each class has 3 div's like this:
<div class="one"></div>
<div class="one"></div>
<div class="one"></div>
<div class="two"></div>
<div class="two"></div>
<div class="two"></div>
<div class="three"></div>
<div class="three"></div>
<div class="three"></div>
Then I give each class a variable:
var _1 = document.getElementsByClassName("one");
var _2 = document.getElementsByClassName("two");
var _3 = document.getElementsByClassName("three);
Then I put them all in an array call nums:
var nums = [_1,_2,_3];
If I wanted to then go through and change the text color of every single div in the classes: one, two, and three. How would I go about doing that without doing something like this:
function textColor() {
var i;
for (i = 0; i < _1.length; i++) {
_1[i].style.color = "red";
}
for (i = 0; i < _2.length; i++) {
_2[i].style.color = "red";
}
for (i = 0; i < _3.length; i++) {
_3[i].style.color = "red";
}
}
I would really like to only have one for loop that goes through and gets all the items in the array nums, and then goes through and gets every div from every item in nums and changes the text color.
Use concat when putting them in nums (and convert the NodeLists to arrays)
var nums = Array.from(_1).concat(Array.from(_2)).concat(Array.from(_3));
Or use the spread operator
var nums = [..._1,..._2,..._3];
Then you can do
function textColor() {
nums.forEach(node => node.style.color = 'red');
}
You can flatten your nums array like this:
var flatNums = [].concat.apply([],nums)
and then go through it:
for (i = 0; i < flatNums.length; i++) {
flatNums[i].style.color = "red";
}
I would just do something like this:
$(".one, .two, .three").prop("style","color: red;");
Or add a second class for all nine div:s.
If you will apply same style to all divs, you can simplify things even more:
divs=document.querySelectorAll('.one, .two, .three');
divs.forEach(function(el) {
el.style.color='red';
})
divs=document.querySelectorAll('.one, .two, .three');
divs.forEach(function(el) {
el.style.color='red';
})
<div class="one">1</div>
<div class="one">2</div>
<div class="one">3</div>
<div class="6">
skip
</div>
<div class="two">4</div>
<div class="two">5</div>
<div class="two">6</div>
<div class="three">7</div>
<div class="three">8</div>
<div class="three">9</div>

How to replace numbers in a div?

With the following code, I'm getting the values of "id"(almost 35), and then add 1 to each "id", so 1 will be 2 and so on. Where I'm stock, it is on how to replace that id number in the html.
This is the code that use to get the values of each id, then I push them into an array, then I run another "for loop" to add 1 to each value, but I don't how to return them to the html.
var x = document.getElementsByClassName('p-divs');
var portfolio = new Array;
for (var i = 0; i < x.length; i++)
{
var y = document.getElementsByClassName('p-divs')[i].getAttribute('id');
portfolio.push(y);
}
console.log(portfolio);
var portfolio2 = new Array;
for (var i = 0; i<portfolio.length; i++)
{
var newId;
newId = parseInt(portfolio[i]) + 1;
portfolio2.push(newId);
}
console.log(portfolio2);
<div class="col-lg-3 col-md-3 col-sm-6 col-xs-12 p-divs" id="1">
<div class="portfolio">
<center>
<img src="images/pace.png" width="230" height="190" alt="" class="img-responsive">
</center>
</div>
</div>
Since you're using jQuery library the code could be simple than what you've so far using .each() method :
$('.p-divs').each(function(){
$(this).attr('id', Number(this.id) + 1);
});
Or shorter using using .attr() method callback like :
$('.p-divs').attr('id', function(){
return Number(this.id) + 1;
});
The more clear version could be :
$('.p-divs').each(function(){
var current_id = Number(this.id); //Get current id
var new_id = current_id + 1; //Increment to define the new one
$(this).attr('id', new_id); //Set the new_id to the current element 'id'
});
Hope this helps.
$(function(){
$('.p-divs').attr('id', function(){
return Number(this.id) + 1;
});
//Just for Debug
console.log( $('body').html() );
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="p-divs" id="1">
<div class="portfolio">
<center>Image 1</center>
</div>
</div>
<div class="p-divs" id="2">
<div class="portfolio">
<center>Image 2</center>
</div>
</div>
<div class="p-divs" id="3">
<div class="portfolio">
<center>Image 3</center>
</div>
</div>
<div class="p-divs" id="4">
<div class="portfolio">
<center>Image 4</center>
</div>
</div>
Using native javascript, just use getattribute's opposite: setAttribute
for (var i = 0; i < x.length; i++)
{
var y = document.getElementsByClassName('p-divs')[i].getAttribute('id');
y++;
document.getElementsByClassName('p-divs')[i].setAttribute("id",y);
}
var j = document.getElementsByClassName('p-divs');
for (var i = 0; i < x.length; i++) {
j[i].id = portfolio2[i];
}
Add this to the end of your code. Vanilla JS.
j will be an array of your divs, i will keep count of which div we're on, and we are simply accessing the "id" of each element in the "j" array and updating it to the corresponding value in your pre-populated "portfolio2" array.
Hope this helps!
P.S.- I would also recommend that instead of using 'new Array' to instantiate your arrays, you use the array literal notation '[]'. This is more concise and also avoids needing to put (); after Array.
I'd suggest, assuming I'm not missing something, and that you're able to us ES6 methods:
// converting the NodeList returned from document.querySelectorAll()
// into an Array, and iterating over that Array using
// Array.prototype.forEach():
Array.from( document.querySelectorAll('.p-divs') ).forEach(
// using an Arrow function to work with the current element
// (divElement) of the Array of elements,
// here we use parseInt() to convert the id of the current
// element into a number (with no sanity checking), adding 1
// and assigning that result to be the new id:
divElement => divElement.id = parseInt( divElement.id, 10 ) + 1
);
Note that updating, changing or otherwise modifying an id shouldn't be necessary in most circumstances, and having a purely numeric id may present problems for CSS selecting those elements (it's valid, but only in HTML 5, but will still be problematic).
for(i=0;i<$('.p-divs').length;i++){
newId= parseInt($($('.p-divs')[i]).attr('id'))+1;
$($('.p-divs')[i]).attr('id',newId)
}
Using Jquery attr

Sort child div based on data attribute

Trying to sort children div based on data attributes
The html code below is being generated by a CM and the data can be retrieved in any random order.
the html code is
<section class="box explore">
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>B</h>
<p>Category_A</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>A</h>
<p>Category_A</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>C</h>
<p>Category_A</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>E</h>
<p>Category_B</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>D</h>
<p>Category_B</p>
</div>
</div>
java
$('div').sort(function (a, b) {
var contentA = $(a).attr('data-name');
var contentB = $(b).attr('data-name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
})
Jsfiddle http://jsfiddle.net/w8gkshue/
if someone can point me in the right direct on how to best sort either by Product Name or Category.
Updated hope this gives better explination
EDIT: I missed the jQuery tag... leaving the answer still.
var productCt = document.getElementById('ProductContainer'),
reInsertProductCt = tempRemove(productCt);
[].slice.call(productCt.children)
.sort(function (a, b) {
var aName = a.dataset.name,
bName = b.dataset.name;
return aName < bName? -1 : +(aName > bName);
})
.forEach(productCt.appendChild.bind(productCt));
reInsertProductCt();
function tempRemove(el) {
var parent = el.parentNode,
nextSibling = el.nextSibling;
parent.removeChild(el);
return function () {
if (nextSibling) parent.insertBefore(el, nextSibling);
else parent.appendChild(el);
};
}
<div id="ProductContainer" class="row">
<div id="1232132" data-name="B" data-category="Category_A" class="explore-cell">
<h>TEST NAME B</h>
<p>TEST</p>
</div>
<div id="123" data-name="A" data-category="Category_A" class="explore-cell">
<h>TEST NAME A</h>
<p>TEST</p>
</div>
<div id="1232152351" data-name="C" data-category="Category_A" class="explore-cell">
<h>TEST NAME C</h>
<p>TEST</p>
</div>
<div id="12342341" data-name="E" data-category="Category_B" class="explore-cell">
<h>TEST NAME E</h>
<p>TEST</p>
</div>
<div id="1325321" data-name="D" data-category="Category_B" class="explore-cell">
<h>TEST NAME D</h>
<p>TEST</p>
</div>
</div>
You can use .sort method like this
var $wrapper = $('#ProductContainer');
$wrapper.find('.explore-cell').sort(function (a, b) {
return a.getAttribute('data-name') > b.getAttribute('data-name');
})
.appendTo( $wrapper );
But I don't sure about the cross browsing support
Calling only sort on them won't actually visually change the DOM, it just returns a sorted collection. So basically you just need to get the collection, sort it, then return it. Something like this should work:
$('#ProductContainer > div').detach().sort(function (a, b) {
var contentA = $(a).data('name');
var contentB = $(b).data('name');
return (contentA < contentB) ? -1 : (contentA > contentB) ? 1 : 0;
}).appendTo('#ProductContainer');
You'll want to make sure that you use the detach() method and not remove(), as detach() will retain all of the data and events associated with the collection items.
Why choose to sort by category or by name when you can sort by both?
I tried to write a generic multisort function generator, which should also work with the native array sort function.
JSFIDDLE HERE
A function that generates the multisort, it takes two parameters.
The column priority list order (first by category or by name? You decide).
I also wanted a way to provide values for columns (since you might not retrieve them the same way for each of them), it is an object that describes for each column a function to retrieve data.
Here it is
function getMultisortFn(columns, provideColumnData) {
return function (a, b) {
for (var i = 0, l = columns.length; i < l; i++) {
var column = columns[i];
var aColumnData = provideColumnData[column.name](a, column.name);
var bColumnData = provideColumnData[column.name](b, column.name);
if (aColumnData !== bColumnData) {
if (column.asc) {
return String.prototype.localeCompare.call(aColumnData, bColumnData);
}
return String.prototype.localeCompare.call(bColumnData, aColumnData);
}
}
};
}
Now this is the part where you actually use the multisort generated
function retrieveDataAttribute(item, attribute) {
return $(item).data(attribute);
}
var $container = $('#ProductContainer');
var $products = $container.find('div');
var multisort = getMultisortFn([{
name: 'category',
asc: false
}, {
name: 'name',
asc: true
}], {
name: retrieveDataAttribute,
category: retrieveDataAttribute
});
$products.sort(multisort);
And finally the DOM manipulation to apply the new order
$products.detach().appendTo($container);
EDIT thanks to plalx:
$container.detach().append($products).appendTo('section.box.explore');

modify the data-donut attribute of the divs from javascript

My javascript
var currentMonth= new Date().getMonth();
if (demo.length >= currentMonth){
var d3data = demo[currentMonth];
// output will be ["23", "19"]
now i need to update output values to #donut and #donut1 (data-donut="")
values are coming from json it may change according to month
for reference i have added FIDDLE
http://jsfiddle.net/Qh9X5/3166/
<div class="zipper">
<div class="current">
<div class="title_text">current</div>
<div id="donut" data-donut="42"></div>///here in the data-donut value
</div>
<div class="target">
<div class="title_text">Target</div>
<div id="donut1" data-donut="62"></div>
</div>
</div>
Any help is Appreciated
You can use this to assign the values from the array (assuming the array is the result after parsing it from json)
var d3data = demo[currentMonth];
// assuming that this results in d3data = ["23", "19"];
$(document).ready(function() {
$("div[data-donut]").each(function(i) {
$(this).attr('data-donut', d3data[i]);
});
});
The first value of the array will be assigned to the first div and the second value to the second one.
See JSFiddle
Looks like you want to modify the data-donut attribute of the divs. Hope this will help
document.getElementsById("donut").setAttribute("data-donut","<your value>");
document.getElementsById("donut1").setAttribute("data-donut","<your value>");
This will work
obj = ["23", "19"];
var keys = Object.keys(obj);
for (var i = 0,j=1; i < keys.length; i++,j++) {
var val = obj[keys[i]];
document.getElementById("donut"+j).setAttribute("data-donut", val);
}
<div class="zipper">
<div class="current">
<div class="title_text">current</div>
<div id="donut1" data-donut="42"></div>///here in the data-donut value
</div>
<div class="target">
<div class="title_text">Target</div>
<div id="donut2" data-donut="62"></div>
</div>
</div>
This is not json format ["23", "19"]
e.g this is an array
var d3data = ["23", "19"];
$('#donut').data('donut', d3data[0]);
$('#donut1').data('donut',d3data[1]);

Is javascript namespace polluted?

I do not have a good grasp of the js namespace and am WAGing* re the title, but that's one of my guesses about what's happening.
WAG = Wild Guess
My app is crashing (dramatically); trying to figure out why. In fact, after 3 Q/A pairs, it blows up the entire Chrome tab..! I'm beginning to suspect I've done something wrong in my code...
Warning: Save your browsing session before running these jsFiddles. (In Chrome, the jsFiddle only blows up its own tab but I can't comment on other browsers)
jsFiddle One
jsFiddle Two - dupe in case jsFiddle One blown away
Please help me to understand exactly which spectacular moronism I've committed today.
HTML:
<div id="result">
<div class="truth tr0"><h2>---</h2></div>
<div class="truth tr1"><h2>answer to one</h2></div>
<div class="truth tr2"><h2>answer to two</h2></div>
<div class="truth tr3"><h2>answer to three</h2></div>
<div class="truth tr4"><h2>answer to four</h2></div>
</div>
<div id="replaceLink">
<div class="youcould yc1">
<h2>QUESTION ONE</h2>
</div>
<div class="youcould yc2">
<h2>QUESTION TWO</h2>
</div>
<div class="youcould yc3">
<h2>QUESTION THREE</h2>
</div>
<div class="youcould yc4">
<h2>QUESTION FOUR</h2>
</div>
<div class="youcould yc5">
<h2>THANK YOU</h2>
</div>
</div>
<div id="response"></div>
<input type="button" id="mybutt" value="Start Test" />
Javascript/jQuery:
var cnt = 0;
var window = {};
window.arrDone = [];
function nextQues() {
if (window.arrDone.length == 4) return 5;
success = 0;
while (success == 0) {
nn = Math.floor(Math.random() * 3) + 1;
if (window.arrDone.indexOf(nn) == -1 && nn != 5) {
success++;
window.arrDone.push(nn);
}
}
return nn;
}
$('.youcould, .truth').hide();
$('.tr0').show();
$('.youcould').click(function() {
$(this).hide();
thisA = window.arrDone[window.arrDone.length -1];
$('.tr'+thisA).show();
});
$('.truth').click(function() {
$(this).hide();
nextQ = nextQues();
$('.yc'+nextQ).show();
});
$('#mybutt').click(function () {
$(this).hide();
$('.tr0').hide();
nextQ = nextQues();
$('.yc'+nextQ).show();
});
My guess would be
var window = {};
window is special, so creating a global variable named window is begging for trouble.
Your while loop runs infinitely on the third pass because it doesn't meet the condition.
At some point, arrDone will contain the numbers 1, 2, and 3, as produced by your random generator (which will never produce 5, btw). In that case, nextQues() does not abort and return five (as arrDone.lenght == 3), and will enter the loop. Your random generator produces nothing but the numbers 1, 2, and 3, which always are already in the array, so the if-condition (that would end the loop) is never fulfilled. You have an infinite loop generating random numbers.
I guess you want
function nextQues() {
var l = 4;
if (window.arrDone.length >= l)
return l+1;
while (true) {
var nn = Math.floor(Math.random() * l) + 1; // generate 1, 2, 3 or 4
if (window.arrDone.indexOf(nn) == -1) {
window.arrDone.push(nn);
return nn;
}
}
}

Categories