Looping through siblings of a specific row/setting up function - javascript

Trying to work through my javascript book I am referencing to learn the language and got stuck on looping through siblings of a specific row. W3schools and W3 didnt have what i was looking for. Below is a function walk-through...
It reads: Create the countRecords() function. The purpose of this function is to count the number of visible rows in the data table after the table headings. The total is then displayed in the table cell with the id "records". Add the follow commands to the function:
a. Create a object named headRow that points to the table row with the id "titleRow". Create a variable named rowCount, setting its initial value to 0.
b. Create a for loop that uses familial references starting with the first sibling of headRow and moving to the next sibling until there are no siblings left. Within the for loop. test whether the node name of the currentnext sibling until there are no sibilings left. Within the for loop test whether the node name of the current node is equal to "TR". If it is, test wheter the value of its display style is equal to an empty text string. If it is (indicating that it is visible in the document) increate the value of the rowCount variable by 1.
c. Change the text of the "records" table cell to the value of the rowCount variable. Don't use innerHTML. Create a text node that contains the value of the rowCount variable and assign it to a variable called txt. Create a variable called record to store the reference to the element "records" table cell.
d. Insert an if condition that test whether the "records" cell has any child nodes. If it does, replace the replace the text node of the "record" table cell with the created text node (txt). If it doesn't append the text node to the cell.
var headRow; // part a
var rowCount = 0;
//part b this is where I get lost. I know I need to access the id titleRow but unsure how to set my loop up specifically for this
headRow = document.getElementById("titleRow");
for(var i=0; i<headrow.length; i++)
{
if (something is not equal == "TH")
{
make code happen here
}
if (is "TR" == ""){
rowCount = +1;
}
//part c
var txt = document.createTextNode(rowCount);
var record = document.getElementsById("records")
//part d holding off on this part until I get a,b,c figured out.
The HTML supporting snippet:
<table id="filters">
<tr><th colspan="2">Filter Product List</th></tr>
<tr>
<td>Records: </td>
<td id="records"></td>
</tr>
<table id="prodTable">
<tr><th colspan="8">Digital Cameras</th></tr>
<tr id="titleRow">
<th>Model</th>
<th>Manufacturer</th>
<th>Resolution</th>
<th>Zoom</th>
<th>Media</th>
<th>Video</th>
<th>Microphone</th>
</tr>
Thanks for the help!

You should check out the nextSibling property. I'm not sure how strictly you want to follow your book but a while loop seems more appropriate than a for:
var headRow = document.getElementById("headRow");
var rowCount = 0;
var cRow = headRow; // used as a reference to the current row
while (cRow = cRow.nextSibling) // While there is a next sibling, loop
{
if (cRow.tagName == "TR" && cRow.style.display === "")
rowCount++; // increment rowCount by 1
}
If you're insistent on using a for loop, I suppose you could do something like this:
var headRow = document.getElementById("headRow");
var rowCount = 0;
for (var cRow = headRow.nextSibling; cRow = cRow.nextSibling;)
{
if (cRow.tagName == "TR" && cRow.style.display === "")
rowCount++; // increment rowCount by 1
}

/**
* Schedules a command to retrieve the count of WebElements
* this function can be called from your Helper Control to your page object
* method
*/
console.log('Driver: ' + this.driver);
let test = await this.driver.findElements({css: this.cssLocator})
console.log('--test: ' + test);
console.log('--test length: ' + test.length);
return test.length;

Related

HTML Table and JavaScript addRows and addColumns without jQuery?

I'm having a problem getting the buttons on my student grades table working, I have a button to calculate the average of the grades using a function called getAverage(), I have one to insert rows to the table using a function called insert_Rows, and finally one to add columns using a function called insert_Column().
My problem is that none of them seem to be working and I can't see why the getAverage function was working until I added the other two buttons.
This is for an assignment where I'm not allowed to use jQuery.
Also, this is the brief for the two buttons:
A CSS styled button that inserts a new table row suitable for recording new student data. You can insert after the last row of the table. Students should provide on button that saves the table in its current state i.e. if there are 5 rows and 6 cells, the cookie should reflect that.
A CSS style button that inserts a new table column suitable for recording new Assignment grade data. This column requires a title. You can decide how you wish to accomplish the title allocation (automatic, content-edit, etc.). There should be another button that then retrieves that data and fills it back to the table in the state that it previously held. If extra rows or columns have been added, the table should revert back to its previous state when the cookie was saved (5 rows and 6 cells).
Also, for extra credit, I have to use JavaScript and any method of my choosing to delete a data row selected by a user, and another on to delete an assignment column selected by the user, the function should ensure that the final grade column totals are updated following this deletion.
// get the average
function getAverage()
{
let table = document.getElementById("gradesTable");
//Loop over the rows array directly
let rows = Array.prtotype.slice.call(table.rows); //let is block scoped - can only be used in this block
rows.froEach(function(row)
{
let cells = array.protoype.slice.call(row.querySelectorAll(".Assignment")); // Get all the Assignment cells into an array
// declairing sum and gradeAverage with let and by defining them in the row loop keeps the values unique for each row
let sum = 0;
let gradeAverage = 0;
// Now just loop the cells Array
cells.forEach(function(cell,index){
//.textContent instead for strings that dont contain any values
var currentValue = parseInt(cell.textContent);
if(currentValue >= 0 && currentValue <=100){
sum += currentValue;
}
// If the cell has "-" for content
if(cell.textContent === '"-"'){
// Apply a pre-made CSS class
cell.classList.add("noGrade");
} else {
// Remove a pre-made CSS class
cell.classList.remove("noGrade");
}
// If this is the last cell in the row
if(index === cells.length-1){
gradeAverage = sum/5;
cell.nextElementSibling.textContent = Math.round(gradeAverage) + "%";
// There is a grade, so check it for low
if(gradeAverage >= 0 && gradeAverage < 40) {
cell.nextElementSibling.classList.add("lowGrade");
} else {
cell.nextElementSibling.classList.remove("lowGrade");
}
}
});
});
}
// add a row to the table
function insert_Row() {
let table = document.getElementById("gradesTable"); //assign table id to a variable
let tableRows = table.rows.length; // gives how many rows in the table
let row = table.insertRow(tableRows); //insert after the last row in the table
let cellsInTable = document.getElementById("gradesTable").rows[0].cells
let columnTotal = cellsInTable.length; //assign the columnTotal the number of columns that the first row has
//loop through each column
for(let i = 0; i < columnTotal; i++)
{
//add a new cell for each column
let cell = row.insertCell(i);
//assign each new cell the default value "-"
cell.innerHTML = "-";
}
}
// add a column to the HTML table
function appendColumn()
{
let table = document.getElementById("gradesTable"); // table reference
// open loop for each row and append cell
for(let x = 0; x < table.rows.length; x++)
{
createCell(tbale.rows[x].insertCell(table.rows[x].cells.lenght), x, "col");
}
}
function insert_Column()
{
}
function deleteColumn()
{
let allRows = document.getElementById("gradesTable").rows;
for (var i=0; i < allRows.length; i++)
{
if (allRows[i].cells.length > 1)
{
allRows[i].deleteCell(-1);
}
}
}
Correction, the Insert row function seems to be working right, but the grades average function isn't and I don't know where to begin writing the other functions.
If anyone can offer advice or best places to learn? Because my lecturer has just informed us to use W3Schools and he's not teaching us the language, I just feel out of my depth.

Variable not changing inside function once changed for next use

I am trying to remove columns from a table on the click of a button, however; when I use the button and it removes the last column, if I then want to remove another column I run into an issue where a critical variable isn't updated.
JS:
function deleteCol(){
debugger;
var tds = document.getElementById("icol").value;
var cold = document.getElementsByClassName(tds-1);
for (var i = 0; i < tds; i++) {
var inc = 0;
var lastCol = cold[inc];
lastCol.remove();
}
tds--;
}
Here I am trying to alter the value of tds after the loop has taken place, but when I next use the button it uses the initial tds value over again and not the updated version, can someone explain to me why it doesn't re assign the tds variable for when I use it next time? edit the function I have here is basically removing the last cell on each row.
You're only updating a variable rather than the live attribute value, instead do this:
var input = document.getElementById("icol");
var tds = input.value;
.
.
.
input.value = --tds

Cant set the value of a cell within a row because it was converted to object or array with no methods

I am trying to write a function that will iterate over each row in a master spreadsheet and check the value of the 3rd column against every row in a second spreadsheet and compare it with the value in the 6th column, then change the value in the 4th column of the master spreadsheet based on the result. The script is a standalone script because its part of a larger project.
The problem is that when I try to set the value of the 4th column in the spreadsheet, I am getting an error that reads "Cannot find function setValue in object SENT". SENT is the value that is in that cell, and I do not understand how it became an object or how I would need to change to code to get at the value to change it. I checked this post about someone having a problem not being able to act on a date value, but I could not figure out how to take that feedback and apply it to this problem.
function eFormCheck() {
var masterSS = SpreadsheetApp.openById("mastersheet");
var masterSheet = masterSS.getSheets()[0];
var masterRange = masterSheet.getDataRange();
var masterData = masterRange.getValues();
var workersCompSent = "SENT";
var workersCompRec = "RECEIVED";
var workersCompSS = SpreadsheetApp.openById("formsheet");
var wcSheets = workersCompSS.getSheets()[0];
var wcRange = wcSheets.getDataRange();
var wcData = wcRange.getValues();
for (var i = 1; i < masterData.length; i++) {
var row = masterData[i];
var email = row[2];
var rowLog = row[3]; //on my spreadsheet this value is "SENT"
for (var j = 1; j < wcData.length; j++) {
var wcRow = wcData[j];
var wcEmail = wcRow[5];
if (email == wcEmail) {
rowLog.setValue(workersCompRec);//having an issue with the value of the third column being an object, but I don't understand how its an object
}
}
}
}
The setValue() method only works on the Range Class. You can't chain the setValue() method to a variable or a cell value. You must first define a range. There are 4 variations of the getRange() method. If you want to set a value in a single cell, then you must get a range that is a single cell. You may need to use your variable i in the getRange() method to get the row. If the column is always the same, then you can "hard code" the column value.
sheet.getRange(i, 4).setValue(workersCompRec);

Remove all child nodes but the first one

I have an HTML table. The first row contains a checkbox. There is a javascript method associated to the checkbox change. If the checkbox is checked, the code adds a few rows to the table and fills them. If the checkbox is unchecked, the code removes all rows but the first one (the one that contains the checkbox).
The first part of the code works fine : the rows are properly added.
I have an issue with the second part. Here is my code :
if (checkboxValue) {
//Add first row
var tr1 = document.createElement("tr");
var td1_1 = document.createElement("td");
....
tr1.appendChild(td1_1);
var td1_2 = document.createElement("td");
...
tr1.appendChild(td1_2);
table.appendChild(tr1);
//Add second row
var tr2 = document.createElement("tr");
var td2_1 = document.createElement("td");
...
tr2.appendChild(td2_1);
var td2_2 = document.createElement("td");
...
tr2.appendChild(td2_2);
table.appendChild(tr2);
} else {
//Remove all rows but the first
var rows = table.getElementsByTagName("tr");
var nbrRows = rows.length;
if (nbrRows > 1) {
for (var i = 1; i < nbrRows; i++) {
var row = rows[i];
row.parentNode.removeChild(row);
}
}
}
The issue always come from rows[2] being undefined. I have no idea why!
If, instead of using removeChild, I write row.innerHTML = "", I have the visual effect I am looking for (all rows gone), but this is inelegant, since the table now contains several empty rows (their number increasing everytime I check/uncheck the checkbox).
A clue? Thank you very much for your time!
Don't use for-loop to remove DOM elements like this. The problem is that rows is a live collection, meaning that it updates every time you remove elements from DOM. As the result, i counter shifts and eventually you hit "undefined" element spot.
Instead, use while loop. For example, to remove all rows except the first one you could do:
var rows = table.getElementsByTagName("tr");
while (rows.length > 1) {
rows[1].parentNode.removeChild(rows[1]);
}
Also note, that it's getElementsByTagName without s.
UPD. Or iterate backward if you like for-loops better:
var rows = table.getElementsByTagName("tr");
for (var i = rows.length - 1; i > 0; i--) {
rows[i].parentNode.removeChild(rows[i]);
}
Demo: https://jsfiddle.net/9y03co6w/
you remove a row from the array you are iterating over. This is always a bad idea and probably the reason for your error.
solution: start iterating from the end instead of the beginning.
also see for example: example
try to replace this line
var rows = table.getElementsByTagNames("tr");
by
var rows = table.find("tr");

Accessing a dynamically created variable in a dynamic way

This may take a bit of explaining, so please bear with me.
I'm trying to create X number of tables, each of a varying length (columns) Y, with 4 rows each, X and Y being determined by the user. This part I have solved.
The problem is that every cell in every table must contain it's own text box, which will contain numbers derived from a database. If the user changes the number in the text box, the change must then be reflected in the database.
Given that the number of tables and the length of the tables are unknown, I can't set up the names of the text boxes ahead of time and must do so dynamically.
I ran across this code:
var pageNumber = 1;
eval("var text" + pageNumber + "=123;");
alert(text1);
//alert shows '123'
Which (with modification), I believe would allow me to dynamically create a unique name for each text box. What I am having trouble with is how do I then access this new text box since I don't know what it's called(after the table has been created)?
I plan on using a 3d array to hold the numbers that I want displayed in each cell in the table and plan to use this array to name the cell.
As an example the array could be array[i][j][k], and lets say i=1, j=2, k=3, which would hold the number 8.
The dynamically created text box name would be something like:
textBox123
How do I associate that cell in the array (containing 8) with that new text box and then retrieve and store a new value when I don't know it's name ahead of time?
The way I'm thinking of it, how do I write something like
<input type="text" name="textBox123" value=array[i][j][k]>
to the html?
Then again, maybe I'm over thinking this and there is a better way of doing it. If so, please let me know.
HTML:
<div id="tables"></div>
JavaScript:
var tables = [
// Table #1
[
[111, 112, 113], // Row #1
[121, 122, 123] // Row #2
],
// Table #2
[
[211, 212, 213, 214], // Row #1
[221, 222, 223, 224], // Row #2
[231, 232, 233, 234] // Row #3
],
// Table #3 (empty!)
[]
],
nr, nc, i, j, k,
name, value, textBox, cell, row, table,
$tables = $("#tables");
for (i = 0; i < tables.length; ++i) {
nr = tables[i].length;
nc = nr > 0 ? tables[i][0].length : 0;
table = "<table>";
for (j = 0; j < nr; ++j) {
row = "<tr>";
for (k = 0; k < nc; ++k) {
name = "textBox" + (i + 1) + (j + 1) + (k + 1);
value = tables[i][j][k];
textBox = "<input type='text' name='" + name + "' value='" + value + "'>";
cell = "<td>" + textBox + "</td>";
row += cell;
}
row += "</tr>\n";
table += row;
}
table += "</table>\n";
$tables.append(table);
}
See the jsfiddle. The 3rd, empty table will be inserted as an empty table tag, but this is not necessary.
I would like to mention that since you need to connect these tables to a database, an elegant solution would be using a client-side MVC framework, like Backbone.js. Here you find a good intro book. The View could use a code similar to the one above to render the tables. The tables array could be stored in a Collection.
You don't have to pre-tag every cell in the table with its index because you can calculate it based on the DOM hierarchy upon demand.
If you have an arbitrary cell in a table, you can get the row and column number of that cell in the table like this where cell as passed to this function can either be the td object or any descendant of it (such as your <input> tag that's in the table):
// Pass any td object or descendant of a td object in a <table> and it will return
// an object with {row: x, col: y}
// or return null if the cell passed wasn't inside a <tr> and <td>
function findRowColInTable(cell) {
function findParent(obj, parentTagName) {
parentTagName = parentTagName.toUpperCase();
while (obj && obj.tagName != parentTagName) {
obj = obj.parentNode;
}
return obj;
}
findChildCnt(obj) {
var cnt = 0;
while (obj = obj.prevSibling) {
++cnt;
}
return cnt;
}
var td, tr, col, row;
// get table cell
td = findParent(cell, "TD");
if (td) {
col = findChildCnt(td);
tr = findParent(td, "TR");
if (tr) {
row = findChildCnt(tr);
return {row: row, col: col};
}
}
return null;
}
FYI, this is quite a bit easier in jQuery where you can use .closest(tag) and .index() to find appropriate parents and get your local sibling count, but I've offered you a pure javascript version.
You can actually do arrays on forms. Take my old answer for example, you can create arrays in form inputs with such naming scheme. On the back-end, you get them as arrays (Assuming PHP).
If you are grabbing the values via JS instead, you can base it off this answer, which takes advantage of the naming scheme, grabs the form values and turns them into JS objects and arrays similar to how PHP would receive them on the back-end. Basically it runs through the form and gets the form values. It's partly jQuery, but you can get the source from jQuery if you don't want to incorporate the entire library.
You can make use of javascript object oriented concept
Each table has its corresponding table object, so does its rows and cells.
With this technique, every object has its "element" in the real DOM.
The trick is, attach the event listener (e.g. when user types something in the textbox) to the object, so that that event listener will only correspond to that specific cell, not messing up with other cells in other column, row, and tables.
Here is possible solution. Sorry for my jQuery heavy dependent. I am just not sure how to create element with document.createElement() (my bad knowledge), you can of course change them with pure javascript implementation (no jQuery).
values=new Array();
function Cell(id,values_row){ //Cell class
this.id=id;
this.element=$("<td></td>");
this.textbox=$("<input type='text' name='"+this.id+"' id='"+this.id+"'></input>");
values_row[id]="" //initiate default value == ""
//here is the trick
this.textbox.change(function(){ //you can use onchange if you dont like jQuery
values_row[id]=$(this).val();
//other tasks need to be done when user changes the value of a textbox (typing)
});
this.textbox.appendTo(this.element);
}
function Row(id,number_of_columns,values_table){ //Row class
this.id=id
this.element=$("<tr></tr>")
values_table[id]=new Array(); //make array "values" becomes 3D
for(var col =0;col<number_of_columns;col++){
var the_cell=new Cell(col,values_table[col]);
the_cell.element.appendTo(this.element);
}
}
function Table(id,number_of_columns){ //Table class
this.id=id
this.element=$("<table></table>")
values[id]=new Array(); //make array "values" becomes 2D
for(var row=0;row<4;row++){
var the_row=new Row(row,number_of_columns,values[id]);
the_row.element.appendTo(this.element)
}
}
// creating the tables
for(var t=0;t<number_of_tables_needed;t++){
the_table=new Table(t,number_of_column_for_this_table);
the_table.element.appendTo(table_container_element);
}
//do input some text into some cells then
alert(values)
The alert will display a 3D array containing the values of all cells indexed by table number, row number, and of course the column that points exactly to a cell. The default value for cell's value is ""
With this technique, you do not need find which the correct array element for a corresponding cell whose value just been inputted. Instead, attach the event listener prior to the element creation in DOM.This is an object oriented way / approach to the problem.
Rather than having O(n^3) algorithm complexity when you need to find the corresponding array cell each time user makes an input, this solution is a taking a bit more time during object initialization, but afterwards, it is O(1).

Categories

Resources