Hightlight differences between items in table row - javascript

I'm trying to add to a HTML table a feature that highlights all those values that, compared to others, are different. Comparison is made row by row.
With great effort I managed to achieve the following JQuery/Javascrit code. I'm pretty sure this is not an efficient/elegant/fast way to do it but it's the only way I work it out.
The HTML table is quite big and complex so it's hard to publish it here.
The issue I'm encountering is that the script works fine out of a loop, but it hangs if I put it inside a FOR - LOOP and I don't understand why.
var numRows = $('.ctable tbody tr').length, numCols = $('.ctable tbody tr:first th').length, v, undefined;
var values = new Array(numRows);
var noDuplicates = new Array(numCols);
var result = new Array(numCols);
for (i = 1; i = numRows; i++) {
// Get a row and copy into an array the values of each VISIBLE cell
$(".ctable tbody tr:eq(" + i + ") td.values:visible").each(function(){
v = $(this).text();
values.push(v.trim());
});
// Remove from the array the 'undefined' values
values = values.filter(function(item){
return item !== undefined;
});
// Push into new array duplicate values
noDuplicates = return_duplicates(values);
// Compare the two arrays and get the differences (uses underscore.js)
result = _.difference(values, noDuplicates);
// This is a 'highlight' plugin and you may pass to it an array
$(".ctable tbody tr:eq(" + i + ") td.values:visible").highlight(values);
}
function return_duplicates(arr) {
var len=arr.length, out=[], counts={};
for (var i=0;i<len;i++) {
var item = arr[i];
counts[item] = counts[item] >= 1 ? counts[item] + 1 : 1;
}
for (var item in counts) {
if(counts[item] > 1)
out.push(item);
}
return out;
}

Try
for (i = 1; i < numRows; i++) {
instead of
for (i = 1; i = numRows; i++) {

Related

How to shorten these duplicate JavaScript code into a loop?

I had ten rows which each rows contain 4 column, now I want to get the value which I had import using localStorage. I find a way to put all these value independently but the code is all the repeat one. These will cause to redundancy of code. I wonder if there are a way to shorten the code using loop?
Here is my code
var res = {};
$(function(){
$('#subbtn').click(function() {
console.log($('#tab').find('tr'))
$('tr').each(function(){
var tmp = [];
var cl ;
$(this).find('select').each(function(){
cl = $(this).attr('class');
//console.log(cl);
tmp.push($(this).val());
})
res[cl] = tmp
})
console.log(res);
localStorage.setItem("testingvalue",JSON.stringify(res));
document.getElementById("results__display").innerHTML = (localStorage.getItem("testingvalue"));
})
})
$( document ).ready(function(){
var res = {};
try {
console.log('existed');
res = JSON.parse(localStorage.getItem("testingvalue"));
//alert(res.r1[2]);
document.getElementsByClassName("r1")[0].selectedIndex=res.r1[0];
document.getElementsByClassName("r1")[1].selectedIndex=res.r1[1];
document.getElementsByClassName("r1")[2].selectedIndex=res.r1[2];
document.getElementsByClassName("r1")[3].selectedIndex=res.r1[3];
document.getElementsByClassName("r2")[0].selectedIndex=res.r2[0];
document.getElementsByClassName("r2")[1].selectedIndex=res.r2[1];
document.getElementsByClassName("r2")[2].selectedIndex=res.r2[2];
document.getElementsByClassName("r2")[3].selectedIndex=res.r2[3];
document.getElementsByClassName("r3")[0].selectedIndex=res.r3[0];
document.getElementsByClassName("r3")[1].selectedIndex=res.r3[1];
document.getElementsByClassName("r3")[2].selectedIndex=res.r3[2];
document.getElementsByClassName("r3")[3].selectedIndex=res.r3[3];
document.getElementsByClassName("r4")[0].selectedIndex=res.r4[0];
document.getElementsByClassName("r4")[1].selectedIndex=res.r4[1];
document.getElementsByClassName("r4")[2].selectedIndex=res.r4[2];
document.getElementsByClassName("r4")[3].selectedIndex=res.r4[3];
document.getElementsByClassName("r5")[0].selectedIndex=res.r5[0];
document.getElementsByClassName("r5")[1].selectedIndex=res.r5[1];
document.getElementsByClassName("r5")[2].selectedIndex=res.r5[2];
document.getElementsByClassName("r5")[3].selectedIndex=res.r5[3];
document.getElementsByClassName("r6")[0].selectedIndex=res.r6[0];
document.getElementsByClassName("r6")[1].selectedIndex=res.r6[1];
document.getElementsByClassName("r6")[2].selectedIndex=res.r6[2];
document.getElementsByClassName("r6")[3].selectedIndex=res.r6[3];
document.getElementsByClassName("r7")[0].selectedIndex=res.r7[0];
document.getElementsByClassName("r7")[1].selectedIndex=res.r7[1];
document.getElementsByClassName("r7")[2].selectedIndex=res.r7[2];
document.getElementsByClassName("r7")[3].selectedIndex=res.r7[3];
document.getElementsByClassName("r8")[0].selectedIndex=res.r8[0];
document.getElementsByClassName("r8")[1].selectedIndex=res.r8[1];
document.getElementsByClassName("r8")[2].selectedIndex=res.r8[2];
document.getElementsByClassName("r8")[3].selectedIndex=res.r8[3];
document.getElementsByClassName("r9")[0].selectedIndex=res.r9[0];
document.getElementsByClassName("r9")[1].selectedIndex=res.r9[1];
document.getElementsByClassName("r9")[2].selectedIndex=res.r9[2];
document.getElementsByClassName("r9")[3].selectedIndex=res.r9[3];
document.getElementsByClassName("r10")[0].selectedIndex=res.r10[0];
document.getElementsByClassName("r10")[1].selectedIndex=res.r10[1];
document.getElementsByClassName("r10")[2].selectedIndex=res.r10[2];
document.getElementsByClassName("r10")[3].selectedIndex=res.r10[3];
}
catch (error){
console.log(error.message);
}
});
Looking at this repeated line:
document.getElementsByClassName("r1")[0].selectedIndex=res.r1[0];
...a simple first pass improvement would be to just use a nested for loop with variables instead of "r1" and 0:
for (var r = 1; r <= 10; r++) {
for (var i = 0; i < 4; i++) {
document.getElementsByClassName("r" + r)[i].selectedIndex = res["r" + r][i];
}
}
Notice, though, that this means the .getElementsByClassName("r" + r) call happens four time for each value of r, which is not very efficient - it would be better to move that into the outer loop:
var els;
for (var r = 1; r <= 10; r++) {
els = document.getElementsByClassName("r" + r);
for (var i = 0; i < 4; i++) {
els[i].selectedIndex = res["r" + r][i];
}
}
In the second version the inner loop could say i < els.length rather than i < 4, although note that either way you need to be sure you match the number of HTML elements to the number of items in your res object.
You've seem to have the jQuery library loaded. Using jQuery makes this much easier.
Here is an example:
var res = JSON.parse(localStorage.getItem("testingvalue"));
$("tr select").each(function(){
$(this).val(res[$(this).attr("class")][$(this).index()]);
});
Of course, this will only work if the select elements have only one class name and the res object contains values for all the select elements that are inside tr elements. Based on the jQuery code in your question that seems to be the case.
And this is a safer approach
Object.keys(res).forEach(function(key){
res[key].forEach(function(val, index){
$("tr select." + key).eq(index).val(val);
});
});
Code below will work regardless the size of your data in storage:
res = JSON.parse(localStorage.getItem("testingvalue"));
// Let's start with checking 'res' type.
// - if it's an Array, get the the length from .length
// - if it's Object, get the the length from Object.keys().length
var resLength = Array.isArray(res) ? res.length : typeof res === 'object' ? Object.keys(res).length : 0;
// loop throw the rows.
for (var i = 0; i < resLength; i++) {
// Do the same as above: get type of the row and calculate it length for the loop.
var rowLength = Array.isArray(res[i]) ? res.length : typeof res[i] === 'object' ? Object.keys(res[i]).length : 0;
// loop throw the columns on the row.
for (var j = 0; j < rowLength; j++) {
document.getElementsByClassName('r'+i)[j].selectedIndex=res['r'+i][j];
}
}

Put a line break in a for loop that is generating html content

I have a for loop that is generating some HTML content:
var boxes = "";
for (i = 0; i < 11; i ++) {
boxes += "<div class=\"box\"><img src=\"unlkd.png\"/></div>";
}
document.getElementById("id").innerHTML = boxes;
I want to display 3 boxes in one row, then below them 2 boxes in one row, then 1, then 3 again, 2, and 1.
First i thought of using the if statement to check whether i > 2 to add a line break, but it will also add a line break after every box past the third one. Nothing comes to mind, and my basic knowledge of javascript tells me I'll have to make a loop for each row I want to make. Any advice?
I would use a different approch :
Use a array to store the number of item per row :
var array = [3, 2, 1, 3, 2];
Then, using two loops to iterate this
for(var i = 0; i < array.length; i++){
//Start the row
for(var j = 0; j < array[i]; ++j){
//create the item inline
}
//End the row
}
And you have a pretty system that will be dynamic if you load/update the array.
PS : not write javascript in a while, might be some syntax error
Edit :
To generate an id, this would be simple.
create a variable that will be used as a counter.
var counter = 0;
On each creating of an item, set the id like
var id = 'boxes_inline_' + counter++;
And add this value to the item you are generating.
Note : This is a small part of the algorithm I used to build a form generator. Of course the array contained much more values (properties). But this gave a really nice solution to build form depending on JSON
You can try something like this:
Idea
Keep an array of batch size
Loop over array and check if iterator is at par with position
If yes, update position and index to fetch next position
var boxes = "";
var intervals = [3, 2, 1];
var position = intervals[0];
var index = 0;
for (i = 0; i < 11; i++) {
boxes += "<div class=\"box\"><img src=\"unlkd.png\"/></div>";
if ((position-1) === i) {
boxes += "<br/>";
index = (index + 1) % intervals.length;
position += intervals[index]
}
}
document.getElementById("content").innerHTML = boxes;
.box{
display: inline-block;
}
<div id="content"></div>
var boxes = "",
boxesInRow = 3,
count = 0;
for (i = 0; i < 11; i ++) {
boxes += "<div class=\"box\"><img src=\"unlkd.png\"/></div>";
count++;
if(count === boxesInRow) {
boxes += "<br/>";
boxesInRow -= 1;
count = 0;
if (boxesInRow === 0) {
boxesInRow = 3;
}
}
}
document.getElementById("id").innerHTML = boxes;
var i;
var boxes = "";
for (i = 0; i < boxes.length; i++) {
boxes += "<div class=""><img src=""/></div>";
function displayboxes() {
"use strict";
for (i = 0; i < boxes.length; i++) {
out.appendChild(document.createTextNode(boxes[i] + "<br>"));
}
}
displayboxes(boxes);

How do you use a script to delete a row off of a spreadsheet that is linked to a form?

I have this script that does several things with data once it is entered into a google form, but I need to make sue that when two entrants have the exact same name that it deletes the previous entry entirely.
function formChanger() {
var doc = DocumentApp.openById('THIS WAS MY ID');
var body = doc.getBody();
var date = body.getListItems();
var dates = [];
for(var i = 0; i<date.length;i++)
{
dates.push(date[i].getText());
}
var form = FormApp.openById('THIS WAS MY ID');
var items = form.getItems();
var ss = SpreadsheetApp.openById("THIS WAS MY ID");
Logger.log(ss.getName());
var sheet = ss.getSheets()[0];
var values = sheet.getSheetValues(2, 4, sheet.getLastRow() , 1);
Logger.log(values);
var names = sheet.getSheetValues(2, 2, sheet.getLastRow(), 1);
var item = items[2].asMultipleChoiceItem();
var choices = item.getChoices()
for(var i=names.length; i>-1; i--){
for(var j=names.length; j>-1; j--){
if(names[i]==names[j] && i != j)
sheet.deleteRow(i);
}
}
var h = -1;
var j = -1;
var k = -1;
var l = -1;
for(var o = 0; o<values.length; o++){
if(choices[0].getValue().equals(values[o].toString()))
h++;
if(choices[1].getValue().equals(values[o].toString()))
j++;
if(choices[2].getValue().equals(values[o].toString()))
k++;
if(choices[3].getValue().equals(values[o].toString()))
l++;
}
if(h>3)
dates.splice(0,1);
if(j>3)
dates.splice(1, 1);
if(k>3)
dates.splice(2, 1);
if(l>3)
dates.splice(3, 1);
emptyDocument();
Logger.log(h);
Logger.log(j);
Logger.log(k);
Logger.log(l);
item.setChoices([
item.createChoice(dates[0]),
item.createChoice(dates[1]),
item.createChoice(dates[2]),
item.createChoice(dates[3])
]);
for(var i = 0; i<dates.length; i++)
body.appendListItem(dates[i]);
Logger.log(doc.getName()+" Contains:");
Logger.log(dates);
}
Yes the code is a mess, and I'm sure that it could be done a better way, but the important part is that I could be able to delete the line of information that is repeated. The compiler will not allow me to do this because the Spread Sheet is linked to the form. is there a way around this?
The following attempts at deletion are blocked in sheets receiving form data:
deletion of columns with form data
deletion of the row with form questions - that is, row 1
Other rows can be deleted at will. This behavior is exactly the same for scripts as it is for user actions.
Your script attempts to delete row 1 because it's buggy. I quote the relevant part:
var names = sheet.getSheetValues(2, 2, sheet.getLastRow(), 1);
for(var i=names.length; i>-1; i++){
for(var j=names.length; j>-1; j++){
if(names[i]==names[j] && i != j)
sheet.deleteRow(i);
What row is names[i] in? It's in row i+2, because i=0 corresponds to row 2. Yet, you attempt to delete row numbered i, two rows above the intended one.
Besides, i>-1; i++ is absurd; you want i-- there.
Here is a simple script that deletes row with duplicates; it's tested with my form responses. It traverses the contents of "Form Responses 1" sheet from bottom to top; if two rows have the same value in column C, the older one gets deleted. I do take care not to attempt deletion of row 1.
(The reason to do this in bottom-up order is to avoid dealing with rows that moved up because others were deleted.)
function deleteDupes() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Form Responses 1');
var values = sheet.getDataRange().getValues();
for (var i = values.length - 1; i > 1; i--) {
if (values[i][2] == values[i-1][2]) {
sheet.deleteRow(i);
}
}
}

How to make this table sorting script treat different tables differently?

I found an in itself good table content sorting script. It can sort the contents of multiple tables and is lightweight. However, it makes the default sorting column, determined by one particular value in the script which has to match the HTML sorting, the same for every table. While I have different tables with different characteristics, and thus with different default sorting columns.
Is it possible to make this script assign different tables different default sorting column numbers? This is the -- two-part -- script (see below for what I tried):
first part, separate file:
function TSorter(){
var table = Object;
var trs = Array;
var ths = Array;
var prevSortCol = '3';
var curSortCol = Object;
var sortType = Object;
function get(){}
function getCell(index){
return trs[index].cells[curSortCol]
}
/*----------------------INIT------------------------------------*/
// Initialize the variable
// #param tableName - the name of the table to be sorted
/*--------------------------------------------------------------*/
this.init = function(tableName)
{
table = document.getElementById(tableName);
ths = table.getElementsByTagName("th");
for(var i = 0; i < ths.length ; i++)
{
ths[i].onclick = function()
{
sort(this);
}
}
return true;
};
/*----------------------SORT------------------------------------*/
// Sorts a particular column. If it has been sorted then call reverse
// if not, then use quicksort to get it sorted.
// Sets the arrow direction in the headers.
// #param oTH - the table header cell (<th>) object that is clicked
/*--------------------------------------------------------------*/
function sort(oTH)
{
curSortCol = oTH.cellIndex;
sortType = oTH.abbr;
trs = table.tBodies[0].getElementsByTagName("tr");
//set the get function
setGet(sortType)
// it would be nice to remove this to save time,
// but we need to close any rows that have been expanded
for(var j=0; j<trs.length; j++)
{
if(trs[j].className == 'detail_row')
{
closeDetails(j+2);
}
}
// if already sorted just reverse
if(prevSortCol == curSortCol)
{
oTH.className = (oTH.className != 'descend' ? 'descend' : 'ascend' ); // reversed from original
reverseTable();
}
// not sorted - call quicksort
else
{
oTH.className = 'descend'; // reversed from original
if(ths[prevSortCol].className != 'exc_cell'){ths[prevSortCol].className = '';}
quicksort(0, trs.length);
}
prevSortCol = curSortCol;
}
/*--------------------------------------------------------------*/
// Sets the GET function so that it doesnt need to be
// decided on each call to get() a value.
// #param: colNum - the column number to be sorted
/*--------------------------------------------------------------*/
function setGet(sortType)
{
switch(sortType)
{
case "link_column":
get = function(index){
return getCell(index).firstChild.firstChild.nodeValue;
};
break;
default:
get = function(index){ return getCell(index).firstChild.nodeValue;};
break;
};
}
/*-----------------------EXCHANGE-------------------------------*/
// A complicated way of exchanging two rows in a table.
// Exchanges rows at index i and j
/*--------------------------------------------------------------*/
function exchange(i, j)
{
if(i == j+1) {
table.tBodies[0].insertBefore(trs[i], trs[j]);
} else if(j == i+1) {
table.tBodies[0].insertBefore(trs[j], trs[i]);
} else {
var tmpNode = table.tBodies[0].replaceChild(trs[i], trs[j]);
if(typeof(trs[i]) == "undefined") {
table.appendChild(tmpNode);
} else {
table.tBodies[0].insertBefore(tmpNode, trs[i]);
}
}
}
/*----------------------REVERSE TABLE----------------------------*/
// Reverses a table ordering
/*--------------------------------------------------------------*/
function reverseTable()
{
for(var i = 1; i<trs.length; i++)
{
table.tBodies[0].insertBefore(trs[i], trs[0]);
}
}
/*----------------------QUICKSORT-------------------------------*/
// This quicksort implementation is a modified version of this tutorial:
// http://www.the-art-of-web.com/javascript/quicksort/
// #param: lo - the low index of the array to sort
// #param: hi - the high index of the array to sort
/*--------------------------------------------------------------*/
function quicksort(lo, hi)
{
if(hi <= lo+1) return;
if((hi - lo) == 2) {
if(get(hi-1) > get(lo)) exchange(hi-1, lo);
return;
}
var i = lo + 1;
var j = hi - 1;
if(get(lo) > get(i)) exchange(i, lo);
if(get(j) > get(lo)) exchange(lo, j);
if(get(lo) > get(i)) exchange(i, lo);
var pivot = get(lo);
while(true) {
j--;
while(pivot > get(j)) j--;
i++;
while(get(i) > pivot) i++;
if(j <= i) break;
exchange(i, j);
}
exchange(lo, j);
if((j-lo) < (hi-j)) {
quicksort(lo, j);
quicksort(j+1, hi);
} else {
quicksort(j+1, hi);
quicksort(lo, j);
}
}
}
second part, in the table page:
function init() {
var Table1Sorter = new TSorter;
var Table2Sorter = new TSorter;
Table1Sorter.init('score-x-year-for-one-patho');
Table2Sorter.init('score-x-patho-in-one-year');
I tried putting this in several places, but that doesn't work:
if (table.id == 'score-x-year-for-one-patho')
var prevSortCol = '0';
if (table.id == 'score-x-patho-in-one-year')
var prevSortCol = '3';
Anyone know how the script should be altered?
It seems that this table sorter is from Terill Dent. See link HTML Table Sorting JavaScript and you can sort table on each column.
How to initialize sorting on each column is described here: Object Oriented .JS QuickSort - Documentation: "Before we can get start working with the actual table, we need to initialize the variables inside our TSorter object and add the onclick behaviours to the cells in the table's header row. Caution must be taken when referencing this from inside the onclick handler where this is referring to the cell that is clicked, not TSorter."
As I understand you have to define onclick event handler for each column in TSorter() function.
EDIT - Wrong: It seems that default sort column is set to 3 at the beginning of function:
var prevSortCol = '3';
How to sort on default column? I'd just trigger click event in init() function for specific column. You can set an ID for column in header, get that ID in init() function and trigger click event on it. For example:
In table section:
<th id='defaultCol'>Col. name</th>
Init section:
function init() {
var TableSorter1 = new TSorter;
TableSorter1.init('housing_table_1');
var elem = document.getElementById("defaultCol");
if (typeof elem.onclick == "function") {
elem.onclick.apply(elem);
}
}
window.onload = init;
This will work if index of your default sort column is not same as prevSortCol. If they are the same you have to change prevSortCol and it cannot be set to -1 or number greater of number of columns - 1.

Deleting rows with 0 from a multidimentional array javascript

i have two sets of arrays that is joined to make it a two dimentional..what i like to do is to delete any rows that have 0 values in price after its joined then sort desending and display in an html table
var desc = new Array();
var desc = ["Water","Heating","Electric","Gas"];
var price = new Array();
var price=["824","325","0","245"];
var sortdesc;
var sortdesc = new Array (2);
for (i = 0; i < desc . length; ++ i)
{
for (var i=0; i < price.length; i++)
{
sortdesc[i] = Array(desc[i], price[i]);
if (price[i] == 0 )
{
sortdesc.splice(i,1);
}
}
}
sortdesc.sort(function(a,b){ return b[1] - a[1]; });
function sortedtable (array)
{
document . write("<table border>");
var row;
for (row = 0; row < array . length; ++ row)
{
document . write(" <tr>");
var col;
for (col = 0; col < array [row].length; ++ col)
document . write(" <td>" + array [row] [col] + "</td>");
document . write(" </tr>");
}
document.write("</table>");
}
sortedtable(sortdesc);
the questions are i thought the .splice() will restructure the array what did i do wrong?
and is there a better way to do this
i saw other question but they all said use .splice() instead of delete.array[element] but the .splice is not working for me
please pardon my code beginner here.
splice successfully removes the item from the array, yet in the next loop turn you are assigning to sortdesc[i] again so the the removed index will stay undefined - you created a sparse array.
Apart from that, you have a big problem with your nested loops which use the same count variable. It does not end up in an infinite loop at least since price.length >= desc.length, but the construct is highly questionable.
To solve you problems, just add new elements (and only if you really want to add them) to the end of the sortdesc array by using push():
// no need to double initialize
var desc = ["Water","Heating","Electric","Gas"];
var price = ["824","325","0","245"];
// the Array constructor does not take dimensions. You just want an empty array here
var sortdesc = [];
// … to fill it with other arrays:
for (var i = 0, l = Math.min(desc.length, price.length); i < l; i++) {
if (price[i] != 0) {
sortdesc.push( [desc[i], price[i]] );
}
}
Short answer - you shouldn't need to splice/delete from the array, just do a check beforehand and do not add items if the price is zero.
Another quick comment - from your data, it looks like both the desc and price arrays will have the same length. In this case, you can just use one for loop, no need to iterate through both.
Working code:
var desc = ["Water","Heating","Electric","Gas"];
var price = ["824","325","0","245"];
var sortdesc = [];
// Presuming both arrays with be the same length
for (i = 0; i < desc.length; ++ i) {
if(price[i] !== "0") {
sortdesc.push([desc[i], price[i]]);
}
}
sortdesc.sort(function(a,b){ return b[1] - a[1]; });
function sortedtable (array)
{
document . write("<table border>");
for (row = 0; row < array.length; ++ row)
{
document . write(" <tr>");
for (col = 0; col < array[row].length; ++ col)
document . write(" <td>" + array [row] [col] + "</td>");
document . write(" </tr>");
}
document.write("</table>");
}
sortedtable(sortdesc);

Categories

Resources