I'm working on some code to export a DataTable to Excel, and I'm trying to make it as fast as possible. Right now, I'm looping through each row and each column for each row (nested for loops). When I've got a large DataTable, this process can take some time. Can I assign a range of cells to an array in Javascript instead of looping through the columns?
Here's a sample of what I'm doing.
for (var rowIndex = 0; rowIndex < json.worksheets.raw.rows.count; rowIndex++) {
row = rowIndex + 2;
for (var columnIndex = 0; columnIndex < json.worksheets.raw.rows.values[rowIndex].length; columnIndex++) {
column = columnIndex + 1
XLWorksheet.Cells(row, column) = json.worksheets.raw.rows.values[rowIndex][columnIndex];
}
}
I get the JSON data using an AJAX call to a web service in my ASP.NET project.
You can do XLWorksheet.Range("A1:C100").value = arr, if arr is an array of VARIANT.
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 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.
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?
I'm trying to extract an array of rows from my datatable. My problem is that I have some fields of the json that populates the table that I don't show in the table. When I use
$('#myTable').DataTable().rows().data().toArray()
I get those fields that I don't need.
¿How can I get that array of the shown fields or columns?
Thanks in advance.
You need to use a selector-modifier.
$('#myTable').DataTable().rows({search:'applied'}).data().toArray();
-------------------------------------
EDIT
A possible way to accomplish what you are asking for is to check first what columns are visible. Then, process each result row and get only the fields you want.
var columns = $('#myTable').DataTable().columns().visible();
var rows = $('#myTable').DataTable().rows().data().toArray();
var result = []; // this array will contain only the visible fields of each row
for (var i = 0; i < rows.length; ++i) {
var row = [];
for (var j = 0; j < columns.length; ++j)
if (columns[j]) // is visible
row.push(rows[i][j]);
result.push(row);
}
I am using UI Grid for displaying large table (30-40 columns) and thousands of rows. I am getting my data through websocket which is basically json array.
The Incoming data is in two parts, one is "add" array which contains rows which are directly pushed to $scope.gridOptions.data. Another part of incoming data is
"edit" array which consists of rows which will replace/modify existing $scope.gridOptions.data.
The Add part is pretty straightforward, just push to existing gridOptions.data array. But Edit part is tricky. Currenty I am looping through incoming array
on gridoptions array. This is the worst but working solution, can there be a better way to do this?
I thought that using "indexOf" function on array, we can directly find the array index at which it can be modified. But as the UI Grid is angular based, all the three arrays
(add, edit and scope.gridOptions) contain unique $$hashKey, so we can not use indexOf.
Can I get rid of $$hashkey by angular.toJson function without any side effect?
I have field to identify unique rows, can I replace it with $$hashKey by using something like 'track by' in UI grid?
here's my code:
$scope.$on('test',function(event, data){
$scope.add(data.ADD);
$scode.edit(data.EDIT);
$scope.$apply();
});
$scope.edit=function(data){
for(var i = 0; i < data.length; i++) {
//var j = $scope.gridOptions.data.indexOf(data[i]);
//$scope.gridOptions.data[j] = data[i];
for(var j = 0; j < $scope.gridOptions.data.length; j++) {
if(data[i].iRowID == $scope.gridOptions.data[j].iROWID ) {
$scope.gridOptions.data[j] = data[i];
}
}
}
};
$scope.add=function(data){
for(var i = 0; i < data.length; i++) {
$scope.gridOptions.data.push(data[i]);
}
};