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.
Related
I'm writing a Google Sheets Macros without having a lot of knowledge about syntax.
What I want to do is the following:
I want to copy the values which are matching in a source matrix into another table. However, I don't know how to write that as a Macros.
I've written the following code:
function CalcularCruces() {
var spreadsheet = SpreadsheetApp.getActive();
var sourceSheet = spreadsheet.getSheetByName("Cruces Activo-Amenazas");
var destinationSheet = spreadsheet.getSheetByName("AnĂ¡lisis de Riesgos");
/** Total number of left column values from source table **/
const maxAmenazas = 29;
for(var i = 0; i < maxAmenazas; i++) {
/** Now I need to get the column and row values which are matching with the checkbox
and paste them into another table **/
}
};
Here is an example of the input table and how the output table should look like after executing the macros.
Input Table Sheet
Output Table Sheet
Edit:
I need the data to be written next to this static columns:
Actual Output
Desired Output
You can do the following:
Retrieve the data from the source sheet via getDataRange and getValues.
For each row in this data (excluding the headers row, that has been retrieved and removed from the array with shift), check which columns have the checkbox marked.
If the corresponding checkbox is marked, write the corresponding values to the destination sheet with setValues.
It could be something like this:
function CalcularCruces() {
var spreadsheet = SpreadsheetApp.getActive();
var sourceSheet = spreadsheet.getSheetByName("Cruces Activo-Amenazas");
var destinationSheet = spreadsheet.getSheetByName("AnĂ¡lisis de Riesgos");
destinationSheet.getRange("A2:B").clearContent();
var values = sourceSheet.getDataRange().getValues(); // 2D array with all data from source sheet
var headers = values.shift(); // Remove and retrieve the headers row
for (var i = 1; i < values[0].length; i++) { // Iterate through each column
for (var j = 0; j < values.length; j++) { // Iterate through each row
var activo = values[j][0]; // Activo corresponding to this row
if (values[j][i]) { // Check that checkbox is marked
// Get the row index to write to (first row in which column A and B are empty):
var firstRow = 2;
var firstCol = 1;
var numRows = destinationSheet.getLastRow() - firstRow + 1;
var numCols = 2;
var firstEmptyRow = destinationSheet.getRange(firstRow, firstCol, numRows, numCols).getValues().filter(function(row) {
return row[0] !== "" && row[1] !== "";
}).length + firstRow;
// Write data to first row with empty columns A/B:
destinationSheet.getRange(firstEmptyRow, firstCol, 1, numCols).setValues([[headers[i], activo]]);
}
}
}
};
Notes:
All data is added to the target sheet every time the script is run, and this can lead to duplicate rows. If you want to avoid that, you can use clearContent at the beginning of your script, after declaring destinationSheet, to remove all previous content (headers excluded):
destinationSheet.getRange("A2:B").clearContent();
In this sample, the number of amenazas is not hard-coded, but it dynamically gets the number of rows in the source sheet with getValues().length. I'm assuming that's a good outcome for you.
UPDATE: Since you have other columns in your target sheet, you cannot use appendRow but setValues. First, you have to find the index of the first row in which columns A and B are empty. This is achieved with filtering the array of values in columns A-B and filtering out the elements in which the two values are empty (with filter).
Reference:
Sheet.getDataRange
Range.getValues
Array.prototype.shift()
Sheet.appendRow(rowContents)
Array.prototype.filter()
Range.clearContent()
I'm having some troubles while using the Javascript Excel API to create an Excel AddIn.
First issue:
Adding rows to an existing table with the Excel Js library: I create a table and add some rows; then the user can update table content with new data coming from a REST service (so resulting table rows can change: increase / decrease, or be the same).
tl;dr; I need to replace table rows with new ones.
That seems pretty simple: there's a addRows method in Table namespace (reference).
But this won't work as expected: if the table already contains rows new ones will be added to the end, not replacing the existing ones.
Here the code:
const currentWorksheet = context.workbook.worksheets.getItemOrNullObject(
"Sheet1"
)
let excelTable = currentWorksheet.tables.getItemOrNullObject(tableName)
if (excelTable.isNullObject) {
excelTable = currentWorksheet.tables.add(tableRange, true /* hasHeaders */)
excelTable.name = tableName
excelTable.getHeaderRowRange().values = [excelHeaders]
excelTable.rows.add(null, excelData)
} else {
excelTable.rows.add(0, excelData)
}
I also tried to delete old rows, then adding new ones.
if (!excelTable.isNullObject) {
for (let i = tableRows - 1; i >= 0; i--) {
// Deletes all table rows
excelTable.rows.items[i].delete()
}
excelTable.rows.add(0, excelData)
}
But .. it works fine only if there isn't content below the columns of the table (no functions, other tables and so on).
I tried another method: using ranges.
The first time I create the table, next ones I delete all rows, get the range of new data and insert the values:
if (excelTable.isNullObject) {
excelTable = currentWorksheet.tables.add(tableRange, true /* hasHeaders */)
excelTable.name = tableName
excelTable.getHeaderRowRange().values = [excelHeaders]
excelTable.rows.add(null, excelData)
} else {
let actualRange, newDataRange
const tableRows = excelTable.rows.items.length
const tableColumns = excelTable.columns.items.length
const dataRows = excelData.length
const dataColumns = excelData[0].length
actualRange = excelTable.getDataBodyRange()
for (let i = tableRows - 1; i >= 0; i--) {
// Deletes all table rows
excelTable.rows.items[i].delete()
}
newDataRange = actualRange.getAbsoluteResizedRange(dataRows, tableColumns)
newDataRange.values = excelData
}
But there are still drawbacks with this solution.
It needs to be so hard to add/edit/remove rows in an Excel table?
Second issue:
Using the same table, if the user decides to add some 'extra' columns (with a formula based on table values e.g.), do I need to fill this new columns with null data?
const tableColumns = excelTable.columns.items.length
const dataRows = excelData.length
const dataColumns = excelData[0].length
if (tableColumns > dataColumns) {
let diff = tableColumns - dataColumns
for (let i = 0; i < diff; i++) {
for (let j = 0; j < dataRows; j++) {
excelData[j].push(null)
}
}
}
Excel API can't handle this scenario?
Please, could you help me?
Thank you in advance.
Thanks for your reporting.
Add table row API is just adding row to the table rows, not replace it.
What's more. I can't repro the issue with delete rows. Can you show me more details?
Having a weird issue with the Excel Online JS Api. I'm creating a new sheet and adding a table with data from an API call. There is a specific case where the first column of the table has a hyperlink function ('=HYPERLINK("somelink", "Go to Wherever")') and after it is being added to the table.rows, one of the rows is being created as the total row.
It always picks the same row to be the total row for the same set of data, but will pick a different row between sets of data. So, it's not always the same index being used.
Here is a snippet of how I'm adding the rows. It's pretty straight forward:
.then(function(){
var dataTable = ctx.workbook.tables.add(topLeftCell.address + ':' + topRightCell.address.split('!')[1], true);
var headers = data[0];
var headerRange = dataTable.getHeaderRowRange();
headerRange.values = [headers];
for (i = 1; i < data.length; i++) {
if (!data[i] || data[i].length !== data[i - 1].length) {
break;
}
dataTable.rows.add(-1, [data[i]]);
}
dataTable.getRange().format.autofitColumns();
return util.ctxSync(ctx);
})
.then(function(){
worksheet.activate();
return util.ctxSync(ctx);
})
The worksheet and the table create just fine, but having any formula as the first value in the row will cause one row to be created as the table's total row instead of a normal row. It can get gotten with dataTable.getTotalRowRange(), but is not in dataTable.rows. It's strange.
I've assumed this is an Excel bug and I can fix it by adding a dummy column with any value that isn't a formula as the first column and then deleting it before the autofitColumns().
Would anyone have any ideas towards a better fix? Any insight is appreciated.
I have a gridview in which there is a row in which I add integer values like 7000.
So if there are 2-3 rows added, I want to add all those rows values and show it in the textbox
So for that, I have written the below code
if (document.getElementById('txtTotalExp').value == "") {
var Expense = 0;
} else {
var Expense = document.getElementById('txtTotalExp').value;
}
for (var i = 0; i < GridExpInfo.Rows.length; i++) {
var totAmount = GridExpInfo.Rows[i].Cells[7].Value;
var totval = totAmount;
Expense += parseInt(totval);
}
document.getElementById('txtTotalLandVal').value = parseInt(TotalPayableValue) + parseInt(Expense);
But In the textbox it is coming something like this
200010001512
The addition operation is not working.
I interpret your question to mean that you have a table with rows of values, you want to sum each row, and then you want to get a total sum-of-the-row-sums for the entire table.
I wanted to provide a working code snippet to demonstrate a solution to your problem. However, because you didn't provide all the necessary code, I invented some of it. In the process of doing so, I provided an alternative, and perhaps more modern, approach to solving the overall problem I think you were trying to solve. Yes, it's providing significantly more than what you were asking for (...I think some of the other answers that discuss parseInt might solve your initial problem) but hopefully this gives you some further insight.
The example below shows how to do this using many recent features of JavaScript. Hopefully the comments explain what is happening at every step in enough detail that you get a sense of the logic. To understand each step, you are probably going to have dig deeper into learning more JavaScript. A good reference is the Mozilla Developer Network (MDN), which contains documentation on all these features.
// when the button is clicked, do the following...
document.querySelector('button').onclick = () => {
// calculate the total sum across the table
const rowSums =
// get a nodeList of all table rows in the entire document
// and convert that array-like nodeList to a true array
// of table row elements
[...document.querySelectorAll('tr')]
// remove the first row, i.e. ignore the row of column headers
.slice(1)
// from the original array of table rows (minus the first row)
// create a new array in which each element is derived
// from the corresponding table row from the original array
.map(row => {
// calculate the sum of values across this row
const rowSum =
// get a nodeList of all table cells in this row
// and convert that array-like nodeList to a true array
// of table cell elements
[...row.querySelectorAll('td')]
// remove the last cell, i.e. ignore the cell that will eventually
// hold the sum for this row
.slice(0,-1)
// from the array of table cells for this row (minus the last one)
// derive a new single value by progressively doing something
// for each cell
.reduce(
// for each table cell in this row, remember the cell itself
// as well as the accumulating value we are gradually deriving from all
// the cells in this row, i.e. the sum of all values across this row
(accumulatingRowSum, cell) =>
// for each cell in this row, add the numerical value
// of the current cell to the sum of values we are accumulating
// across this row
accumulatingRowSum + parseInt(cell.innerHTML, 10),
// start our accumulating sum of values across this row with zero
0
);
// get all the cells in this row
const rowCells = row.querySelectorAll('td');
// put the sum of values from this row into the last cell of the row
rowCells[rowCells.length - 1].innerHTML = rowSum;
// place the sum of values in this row into the accumulating array
// of all such values for all rows
return rowSum;
});
// calculate the total sum for the whole table
const totalExpenses =
// start with the array of sums for each row
rowSums
// similar to `reduce` above, reduce the array of multiple row sums
// into a single value of the total sum, calculated by adding together
// all the individual row sums, starting with zero
.reduce((accumulatingTotalSum, rowTotal) => accumulatingTotalSum + rowTotal, 0);
// place the total sum into the appropriate span element
document.querySelector('#txtTotalExp').innerHTML = totalExpenses;
};
table {
border: solid black 1px;
border-collapse: collapse;
}
th, td {
border: solid black 1px;
padding: 0.5em;
}
<table>
<tr><th>col1</th><th>col2</th><th>row total</th></tr>
<tr><td>3500</td><td>1200</td><td></td></tr>
<tr><td>2700</td><td>4500</td><td></td></tr>
<tr><td>3100</td><td>1300</td><td></td></tr>
</table>
<p>The total expenses are: <span id="txtTotalExp"></span></p>
<button>Calculate</button>
you can used below code:
var total=parseInt(TotalPayableValue,10) + parseInt(Expense,10);
document.getElementById('txtTotalLandVal').value=total;
try this:
var Expense = 0;
if (document.getElementById('txtTotalExp').value != "") {
var Expense = parseInt(document.getElementById('txtTotalExp').value);
}
for (var i = 0; i < GridExpInfo.Rows.length; i++) {
var totAmount = GridExpInfo.Rows[i].Cells[7].Value;
Expense += parseInt(totAmount);
}
The problem: Expense initially can be a string. Have you tried if with "Expense = 0" the error occurs?
May I know what are the ways to limit the number of columns of a Html table (e.g. 3 columns per row)?
FYI, I'm using row.insertCell() to add cells to a particular row with matching the row id. I wish to limit the cell number to only 3 per row in the table.
"Limit"? There's no natural limit. You'll have to enforce it yourself on your own code.
Check if the row you're inserting into already has 3 cells, and don't add a new one if it does.
Use row.cells collection to check, how many cells a row contains.
var row = document.getElementById('row_id'),
cells = row.cells, max = 3;
if (cells.length < max) {
// Add cell(s) to #row_id
}
There is no such limit in the javascript or html standard. you have to enforce it yourself as a rule during the insertion.
A simple counter does the trick.
var items = ['c00', 'c01', 'c02', 'c10', 'c11', 'c12']; //sample data
var table = document.getElementById("myTable");
var row;
for(var i = 0; i < items.length; i++){
if(i % 3 == 0) { //after every third cell add a new row and change the row variable to point to it
row = table.insertRow(-1);
}
var cell = row.insertCell(-1); //simply insert the row
cell.innerHTML = items[i];
}
there are a number of ways of doing it. it will really depend on how you structure your code.
for(i=0;i<3;i++)
row.insertCell()