Merging multiple columns as the table structure is written - javascript

I am trying to group multiple columns on my table by collapsing them into a single column and then use a UL list to separate the categories:
For this, what I am doing is adding a boolean to toggle the start/stop stacking, like this:
cols[0][0]="Name";
cols[0][1]=true; //<--toggler
cols[1][0]="Age";
cols[1][1]=false;
cols[2][0]="[M/F]";
cols[2][1]=false;
cols[3][0]="E-mail";
cols[3][1]=true;//<--toggler
However, using this method I have some problems:
I haven't managed to make two consecutive groups: [A][B+C][D+E][F]
the code is pretty hard to read, also to understand exactly what the toggle does
My code to write the head is the following:
document.writeln("<table><thead><tr>");
tmp = "<th>#";
flag = false;
for(i = 0; i < cols_len; i++){
if(cols[i][1]){ //if stack-toggler
if(flag){
tmp += "</th><th>-";
}
flag = !flag;
}
if(!flag){
tmp += "</th><th>" + cols[i][0];
}
}
if(flag){
tmp += "</th><th>-";
}
document.writeln(tmp + "</th></tr></thead><tbody>");
And then for the body of the table:
for(i = 0; i < 20; i++){ //some number of rows
if(i){
document.writeln("</tr>");
}
document.writeln("<tr><td>" + i + "</td>");
tmp = "";
flag = false;
for(j = 0; j < cols_len; j++){
if(cols[j][1]){ //if stack-toggler
if(flag){
document.writeln("<td><ul>" + (tmp.replace(/<td/g, "<li").replace(/td>/g, "li>")) + "</ul></td>");
tmp = "";
}
flag = !flag;
}
if(flag){
tmp += "<strong>" + cols[j][0] + ":</strong><br>";
}
tmp += "<td>...</td>";
if(!flag){
document.writeln(tmp);
tmp = "";
}
}
if(flag){
document.writeln("<td><ul>" + (tmp.replace(/<td/g, "<li").replace(/td>/g, "li>")) + "</ul></td>");
}
}
document.writeln("</tr></tbody></table>");
»The full code and demo can be found in this jsFiddle.
I feel this is the wrong approach, it sucks in every way and more importantly, I can't have two or more consecutive groups!, after I start stacking columns, whenever I want to stop, the next column must be alone (and not another group).
I have played around with the booleans and it is simply impossible, I can't figure it out, I already reduced the code to the most readable way and tried to rewrite parts of it but I keep getting the same results.

I made a slight change to the data model. Instead of "toggle" the boolean says if the next guy stacks or not (that is if it is true then put the next one below me.)
So the data looks like this:
/*0:caption, 1:stack-toggler*/
cols[0][0]="Name";
cols[0][1]=true; //<--stack next below
cols[1][0]="Age";
cols[1][1]=true; //<--stack next below
cols[2][0]="[M/F]";
cols[2][1]=false;
cols[3][0]="E-mail";
cols[3][1]=false;
cols[4][0]="Website";
cols[4][1]=false;
cols[5][0]="Notes";
cols[5][1]=false;
And then the code can be simple -- like this (a trick I used, you can change the loop variable internal to the loop -- so we only loop on the outer loop when we change columns.)
for(i = 0; i < 20; i++){ //some number of rows
buildHTML("<tr><td>" + i + "</td>");
for(j = 0; j < cols_len; j++){
buildHTML("<td>");
if (cols[j][1]) {
buildHTML("<ul>"+cols[j][0]+"</ul>");
// loop till we are at the penultimate
while (cols[j+1][1]) {
j++;
buildHTML("<ul>"+cols[j][0]+"</ul>");
}
j++;
buildHTML("<ul>"+cols[j][0]+"</ul>");
}
else {
buildHTML(cols[j][0]);
}
buildHTML("</td>");
}
buildHTML("</tr>");
}
buildHTML("</tbody></table>");
I did not bother with the header, you can figure that out I'm sure.
Here is the fiddle:
http://jsfiddle.net/eajQy/3/
Of course with javascript you can get fancy. Since what you really have is an array of arrays for the columns you could represent that like this:
// array of arrays -- one element std, 2 or more a list
newcols = [ ["Name","Age","[M/F]"] , ["E-Mail"] , ["Website"], ["Notes"] ];
Then to make your table you could use map and join like this:
buildHTML("<tr><td>" + i + "</td>");
strArray = $.map(newcols, function (anArray) {
if (anArray.length == 1) {
return "<td>"+anArray[0]+"</td>";
}
else {
return "<td><ul>"+anArray.join("</ul><ul>")+"</ul></td>";
}
});
buildHTML(strArray.join(""));
buildHTML("</td></tr></tbody></table>");
Here is a fiddle:
http://jsfiddle.net/eajQy/4/
Single line solution (because every question should have a single line solution):
http://jsfiddle.net/eajQy/5/

Related

Parsing some JSON

I have a complex JSON file that needs parsing and my loop skills (or more precisely, the lackthereof), are really failing me.
I have the following xml file, and I am trying to get all elements on one row. In my perfect world (in no particular order)...
sku #, length, width, image, description, attribute value 1, attribute value 2, attribute value 3, etc.
The JSON file is as follows:
var json = {
"product":[
{
"shipdata":{
"_length":"2in",
"_width":"2in",
},
"sku":"90245",
"brand":"Brandy",
"image":"shirt.jpg",
"description":"description",
"attributes":{
"attribute":[
{
"_name":"Color",
"_value":"Black",
},
{
"_name":"Gender",
"_value":"Mens",
},
{
"_name":"Size",
"_value":"L",
},...
So, my intended result is:
90245, Brandy, Black, Men's, L, shirt.jpg, 2in, 2in
But when I loop like the following, I only get the first result for "name". Admittedly, I'm a newb, but if anyone can push me in the right direction or show a proof of concept, it would be so so appreciated. Thanks in advance / feel horrible to even ask such a low level question.
for(var l = 0; l < json.product[i].attributes.attribute.length; l++) {
var xxx = (json.product[i].attributes.attribute[l]['_name']);
}
$('body').append(xxx);
if you don't mind using lodash, this should help you:
var res=[];
_.each(json.product, function(p) {
res.push(p.brand);
res.push(p.sku);
_.each(p.attributes.attribute, function(at) {
res.push(at._value);
});
});
console.log(res.join(','));
//Brandy,90245,Black,Mens,L
working fiddle
EDIT: My solution is obviously not as good as scottjustin5000 's. I'm trying to explain the detailed steps on analyzing this problem.
You want to output a string from the JSON data. So we should break the parts of the string and process one by one.
90245, Brandy, Black, Men's, L, shirt.jpg, 2in, 2in
"sku", "brand", attribute, attribute, attribute, "image", "_length", "_width"
Let's start.
function parseJSONToLine(product) {
var line = "";
line = line + product["sku"] + ", ";
line = line + product["brand"] + ", ";
line += getAllAttributes(product);
line = line + product["image"] + ", ";
line = line + product["shipdata"]["_length"] + ", ";
line = line + product["shipdata"]["_width"];
return line;
}
products = json["product"];
for (var i = 0; i < products.length; i++) {
console.log(parseJSONToLine(products[i]));
}
This part is just assembling the line your want part by part. For the attributes, we need another loop:
function getAllAttributes(product) {
var attrStr = "";
var attrsDict = {};
var attrsOrder = ["Color", "Gender", "Size"];
var attrList = product["attributes"]["attribute"];
// loop through every attribute and put it in dictionary
for (var i = 0; i < attrList.length; i++) {
attrsDict[attrList[i]["_name"]] = attrList[i]["_value"];
}
for (var i = 0; i < attrsOrder.length; i++) {
attrStr = attrStr + attrsDict[attrsOrder[i]] + ", ";
}
return attrStr;
}
The last part is to put the line produced into your HTML. Just the $(body') line with:
$('body').append('<p>' + line + '</p>');
That's it. The point to solve this problem is to know what the line is consisted of. Then try to get the values in the JSON object one by one. When meeting something seems to be complicated, just try to write out the code and modify according to the output. console.log() is very helpful on this.
The reason of why your code doesn't work is, your JSON data contains not only arrays but also objects. You have to take them apart.
If you need further explanation on the snippet, comment me.
JSFiddle: https://jsfiddle.net/aresowj/g9wuLg28/
According to your JSON structure and the output you want, I'll suggest to do the following:
var output = Array(json.product.length); // will be an array of string
for(var i = 0; i < json.product.length; i++) {// loop on each product
output[i] = json.product[i].sku +', '+json.product[i].brand; // according to your question, seems that you want these 2 things first
for(var j = 0; j < json.product[i].attributes. attribute.length; j++){ // then we loops on the attributes
output[i] += ', ' +json.product[i].attributes. attribute[j]._name;
}
output[i] += ', ' +json.product[i].shipdata._length + ', ' + json.product[i].shipdata._width; // last we append to the string the with and height data
}
$('body').append(output)
var json = {
"product":[
{
"shipdata":{
"_length":"2in",
"_width":"2in",
},
"sku":"90245",
"brand":"Brandy",
"image":"shirt.jpg",
"description":"description",
"attributes":{
"attribute":[
{
"_name":"Color",
"_value":"Black",
},
{
"_name":"Gender",
"_value":"Mens",
},
{
"_name":"Size",
"_value":"L",
}
]
}
}
]
};
var output = Array(json.product.length); // will be an array of string
for(var i = 0; i < json.product.length; i++) {// loop on each product
output[i] = json.product[i].sku +', '+json.product[i].brand; // according to your question, seems that you want these 2 things first
for(var j = 0; j < json.product[i].attributes. attribute.length; j++){ // then we loops on the attributes
output[i] += ', ' +json.product[i].attributes. attribute[j]._name;
}
output[i] += ', ' +json.product[i].shipdata._length + ', ' + json.product[i].shipdata._width; // last we append to the string the with and height data
}
$('body').append(output)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
A solution using Array.map:
var res = json.product.map(function (p) {
return [p.sku, p.brand].concat(p.attributes.attribute.map(function (at) {
return at._value;
}))
});
res.forEach(function (r) { console.log(r.join(', ')) });
https://jsfiddle.net/xve4agp6/1/

How to make this JavaScript/jQuery code shorter

Is there a way to loop this four times to make it shorter? I am trying to change the class from standing to sitting and then back again one at a time.
if(sitting > 0) {
$('.standing:first-of-type').removeClass('standing').addClass('sitting');
} else {
$('.sitting:first-of-type').removeClass('sitting').addClass('standing');
}
if(sitting > 1) {
$('.standing:nth-of-type(2)').removeClass('standing').addClass('sitting');
} else {
$('.sitting:nth-of-type(2)').removeClass('sitting').addClass('standing');
}
if(sitting > 2) {
$('.standing:nth-of-type(3)').removeClass('standing').addClass('sitting');
} else {
$('.sitting:nth-of-type(3)').removeClass('sitting').addClass('standing');
}
if(sitting > 3) {
$('.standing:nth-of-type(4)').removeClass('standing').addClass('sitting');
} else {
$('.sitting:nth-of-type(4)').removeClass('sitting').addClass('standing');
}
You can use :lt and :gt selectors.
:lt(index) select all elements at an index less than index within the matched set. :gt(index) select all elements at an index greater than index within the matched set.From jQuery Docs
As the class sitting should be added to all the elements having class .standing whose index is less than the sitting variable value, :lt selector can be used with the variable sitting to select such elements. Then addClass() and removeClass() can be used on the jQuery set of elements to add and remove the passed classes respectively.
$('.standing:lt(' + sitting + ')').removeClass('standing').addClass('sitting');
$('.sitting:gt(' + sitting + ')').removeClass('sitting').addClass('standing');
Well, you can do with an ugly for-loop:
function toggleSitting(sitting){
var initial = 0;
var final = 3;
for(var i = initial; i <= final; i++){
$('.standing:nth-of-type(' + (i+1) +')')
.toggleClass('standing', sitting < i)
.toggleClass('sitting', sitting > i);
}
}
toggleSitting(sitting);
This is just a draft and it's untested, but there is a logic in what you are trying to do. Once you find the logic, you just have use it in a loop. Like that :
var condition;
for(var i = 0; i < 4; i++){
condition = sitting > i;
$('.standing:nth-of-type(' + (i + 1) + ')').toggleClass('standing', !condition).toggleClass('sitting', condtion);
}
Maybe Something like this:
var numberOfPlaces = 4;
for(var i=0; i<sitting && i<numberOfPlaces ; i++){
$('.standing:nth-of-type(' + (i+1) + ')').removeClass('standing').addClass('sitting');
}
for(var i=sitting; i<numberOfPlaces ; i++){
$('.sitting:nth-of-type(' + (i+1) + ')').removeClass('sitting').addClass('standing');
}
or this:
var numberOfPlaces = 4;
for(var i=0; i<numberOfPlaces; i++){
if(i<sitting){
$('.standing:nth-of-type(' + (i+1) + ')').removeClass('standing').addClass('sitting');
}else if(i>=sitting){
$('.sitting:nth-of-type(' + (i+1) + ')').removeClass('sitting').addClass('standing');
}
}
Do you have HTML and CSS to accompany that ?
You can use a variable to specify the 'nth' of type:
$('.standing:nth-of-type(' + i + ')')
although not sure that that works for the case where i = 1. You might need first-of-type there.
Without the CSS and HTML it isn't clear exactly what you want to do.
You might want to look at this also:
https://css-tricks.com/almanac/selectors/n/nth-of-type/

How do I use innerHTML inside a loop using JavaScript?

Here is the code and snippet:
var amount = prompt("How many list items would you like?");
if(isNaN(amount) || amount < 1) {
alert("Please enter a positive whole number");
} else {
for(i = 0; i <= amount; i++) {
document.getElementById("content").innerHTML = "Loop: " + i + "<br>";
}
}
<div id="content"></div>
Hi, I'm a new to Javascript and I can't figure this out. How can I write into the div tag "content" using the loop to display values inside the div tag per loop?
Change to += instead of = and start the for loop with 1 unless you want to print out as loop 0, loop 1 and so on...
document.getElementById("content").innerHTML += "Loop: " + i + "<br>";
var amount = prompt("How many list items would you like?");
if(isNaN(amount) || amount < 1) {
alert("Please enter a positive whole number");
} else {
for(i = 1; i <= amount; i++) {
document.getElementById("content").innerHTML += "Loop: " + i + "<br>";
}
}
<div id="content"></div>
Your code looks basically correct but you need to understand the context in which a browser executes javascript. For a given computation (event), the browser usually executes all of that computation before it does any redraws of the actual page. What this means in your case is that only the last value of innerHTML will be used. One approach to this is to accumulate the entire innerHTML value before returning (I see IsabelHM just posted that). The second would be to use something like setTimeout to spread the computation out over multiple "sessions" - something like
var i = 0;
count = function() {
document.getElementById("content").innerHTML = "Loop: " + i + "<br>";
i++;
if (i < amount) {
window.setTimeout(count, 100);
}
};
count();
Note - I haven't run that but the idea is there. I basically count at 100ms intervals.

Making individual letters in a string different colours

I'm trying to make the colour different for certain letters (if found) in a string eg. the letter i. The search count is working I just can't figure out the changing html colour of the individual letter.
I know if it was a whole word then I could just use split strings, but can't figure out how to do it for a single letter. I've found some examples, one that I have tried is at the bottom that is not working either.
//getMsg is another function, which passes in a user inputted string
function searchMsg(getMsg) {
alert (getMsg);
var msgBoxObject = document.getElementById('msgBox');
var pos = getMsg.indexOf('i')
var txtToFind = (document.getElementById('txtToFind').value);
var count = 0;
while (pos !== -1){
count++;
pos = getMsg.indexOf('i', pos + 1);
document.writeln (+count);
msgBoxObject.innerHTML = (count);
}
getMsg = getMsg.replace('/i/g<span class="red">i</span>');
document.writeln (getMsg);
}
Edit; I've added in this, but can't get the loop to work correctly so it displays all instances of the letter found instead of just one: /*while (pos !== -1){
count++;
pos = getMsg.indexOf('i', pos + 1);
document.writeln (+count);
msgBoxObject.innerHTML = (count);
}
*/
var count = 0; // Count of target value
var i = 0; // Iterative counter
// Examine each element.
for(i=0; i<arr.length; i++)
{ if(arr[i] == targetValue)
count++;
}
return count;
}
searchIndex = txtMsg.indexOf(txtToFind);
if (searchIndex >=0 ) {
// Copy text from phrase up till the match.
matchPhrase = txtMsg.slice(0, searchIndex);
matchPhrase += '<font color="red">' + txtToFind + '</font>';
matchPhrase += txtMsg.slice(searchIndex + txtToFind.length);
} else {
matchPhrase = "No matches"
}
displayProcessedMsg(matchPhrase);
document.writeln(matchPhrase);
You either need to add the corresponding css for that class or change the tag like #john_Smith specified
Adding the CSS
span.red {
color: red;
}
Changing the tag
On your code replace this
getMsg = getMsg.replace('/i/g<span class="red">i</span>');
for
getMsg = getMsg.replace('/i/g<span style:"color:red">i</span>');
Some example of inline css
Some advice on color palettes
Try looking into d3 color scales(https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors) or apply a principle similar to incrementing an RGB value instead of using names of colors.
Hope this helps.

Search Box Function Not Eliminating Correct Value

I am trying to make a simple website where the user types input into a search box, and every time a key is press, their input is compared against the first row of a 2 dimensional array which checks for character matches. If the character they input doesn't match anything, I want it to remove that specific bucket of the array. I have attempted to write basic code for this I thought would work, and have it up at the demo site linked. (Sorry I am just using a free host and havn't optimized the equation table at all so bear with it)
http://fakefakebuzz.0fees.net/
As you can see, the function is not eliminating the appropriate table rows. For example, typing "A" should not eliminate the "Average Current Equation" row because the first letter of that is A, which means matches should not = 0.
I have been looking through this code all morning, and cannot find where I went wrong. I also want to stick to vanilla js.
Any help?
Thanks so much.
I just debugged your code, and the function you use is narrowTable. first remove onkeypress from body node
<body onload="printTable()" onkeypress="narrowTable()">
and add onkeyup instead to you input, like this:
<input type="search" name="equationSearch" id="equationSearch"
placeholder="Equation Search" autofocus="" onkeyup="narrowTable()">
because when you use onkeypress the key value hasn't been added to the input box and your input value has no value in your function, which is:
function narrowTable() {
var newTableContent = "";
var matches = 0;
var input = document.getElementById("equationSearch").value;
//input has no value
for (var i = 0; i < tableData.length; i++) {
for (var j = 0; j < tableData[i][0].length; j++) {
if (input == tableData[i][0].charAt(j)) {
matches++;
}
}
if (matches == 0) {
tableData.splice(i, 1);
}
matches = 0;
}
for (var i = 0; i < tableData.length; i++) {
newTableContent += "<tr><td>" + tableData[i][0] + "</td><td>" + tableData[i][1] + "</td></tr>";
}
document.getElementById("table").innerHTML = newTableContent;
}
the other problem your code has is after printing your table, your tableData variable has changed because you have removed some of indexes. you should reset the tableData to its original value or you can do:
function narrowTable() {
//create a copy of your original array and use currenttableData instead
var currenttableData = tableData.slice();
var newTableContent = "";
var matches = 0;
//your code
}
the other problem here is the way you search for your input value:
for (var j = 0; j < tableData[i][0].length; j++) {
if (input == tableData[i][0].charAt(j)) {
matches++;
}
}
if (matches == 0) {
tableData.splice(i, 1);
}
you can easily do this, instead:
if(tableData[i][0].search("input") == -1){
tableData.splice(i, 1);
}
First, to check if a string is a substring of another string, you can use indexOf. It will return -1 if the string is not found in the other string.
Second, you shouldn't alter the array while you are still looping through it, unless you make sure to alter the counter variable (i in this case) appropriately.
var dataToRemove = [],
i;
for (i=0; i<tableData.length; i++) {
if(tableData[i][0].indexOf(input) == -1) {
// add the index to the to-be-removed array
dataToRemove.push(i);
}
// remove them in reverse order, so the indices don't get shifted as the array gets smaller
for(i = dataToRemove.length - 1; i >= 0; i--) {
tableData.splice(i, 1);
}
dataToRemove = [];
for (i=0; i<tableData.length; i++) {
newTableContent += "<tr><td>" + tableData[i][0] + "</td><td>" + tableData[i][1] + "</td></tr>";
}
I haven't tested this code, but it should at least give you a better idea of how to make this work.

Categories

Resources