I'm working on crating a function that adds data from numerous sheets in a Google Sheets document. The function creates production sheets that has a name and data slot as well as column titles for data that the function will paste into the sheet "Print". I am having an issue where a for loop seems to run an additional time even though the conditions are met as far as I can tell. Here is the code I have so far, its ugly but I'm just building this for myself.
function pasteToPrint(){
var ui = SpreadsheetApp.getUi()
var spreadsheet = SpreadsheetApp.getActive();
var sheetName;
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var lastCol;
var lastRow;
var a;
var x;
var y;
var z;
var array1 = [];
var array2 = [];
function getNextPage(){ // spreads subsequent data set to page break for printing
x = spreadsheet.getSheetByName("Print").getLastRow();
y = x/32;
y = Math.ceil(y);
z = 32*y;
return z;
}
for(var i = sheets.length-1; i > 4; i--){ // loops through the appropriate sheets and pushes to array1
lastCol = spreadsheet.getSheetByName(sheets[i].getName()).getLastColumn()+1;
lastRow = spreadsheet.getSheetByName(sheets[i].getName()).getLastRow()+1;
array1.push(spreadsheet.getSheetByName(sheets[i].getName()).getRange(1, 1, lastRow, lastCol).getValues());
}
for(var i = 0; i < array1.length; i++){
for(var j = 0; j < array1[i].length; j++){
// Looking for data that has a value grater than 0 at .slice(2,3)
if(array1[i][j].slice(2,3) > 0){ // Ref1
for(var k = 0; k < array1[i].length; k++){ // here I push that data to array2
if(array1[i][k].slice(2,3) > 0){ // ref 2
array2.push(array1[i][k]);
}
}
ui.alert(array2+" test 1"); // lets me know the content of array2
ui.alert(array2.length); // tells me the length just to be sure
for(var l = 0; l < array2.length; l++){ // decides where to paste the next data set
if(spreadsheet.getSheetByName("Print").getLastRow()>1){ // ref 3
a = getNextPage(); // returns the next 32nd line
}
else {
a = 1; // starts at row 1
}
// this next section is column formatting for the beginning of the production sheet
// ref 4
spreadsheet.getSheetByName("Print").getRange(a,1).setValue("Name:");
spreadsheet.getSheetByName("Print").getRange(a,3).setValue("Date:");
a++; // moves to the next row
spreadsheet.getSheetByName("Print").getRange(a,1).setValue("Product");
spreadsheet.getSheetByName("Print").getRange(a,2).setValue("Work Order");
spreadsheet.getSheetByName("Print").getRange(a,3).setValue("Planned Production");
spreadsheet.getSheetByName("Print").getRange(a,4).setValue("Product Produced");
spreadsheet.getSheetByName("Print").getRange(a,5).setValue("Steel Shortage");
spreadsheet.getSheetByName("Print").getRange(a,6).setValue("Notes");
a++ // moves to the next row
// ref 5
for(var m = 0; m < array2[l].length-1; m++){ // HERE IS THE PROBLEM LOOP
ui.alert(array2[m]+" test 2"); // I see this run an additional time after m = length
spreadsheet.getSheetByName("Print").getRange(a,1).setValue(array2[m][0]);
spreadsheet.getSheetByName("Print").getRange(a,2).setValue(array2[m][4]);
spreadsheet.getSheetByName("Print").getRange(a,3).setValue(array2[m][2]);
a++;
}
}
array2 = []; // array2 is emptied
break; // First if statement checking .slice(2,3) now has the function go to the next sheet
}
}
}
ui.alert("Function ended"); // just letting me know
};
imagine that there are numerous sheets in this document with data like the following except that there might be hundreds of rows after the column titles with different part numbers and so on
Material Req Planned Quantity MTs Planned Order
AI UPC300 1 1 86 12744851
The Plan Quantity column is what the .slice(2,3) is looking for. By default when I paste the original data into the sheets from my inventory system the value of Planned Quantity will be 0. After I have gone through the document and planed production, this means I have assigned a value to planned quantity greater than 0, I will run the function to paste the data to the print sheet. There could be hundred of items but I will only plan a portion of them, that's why this is handy. I can't share the full document I'm working with because it has sensitive data in it. If you create a google sheet with one sheet named print and a second sheet named whatever you want you can then paste the example data on rows 1 and 2 of the second sheet and you will see the error I get which is
TypeError: Cannot read property '0' of undefined (line 138, file "arrayTest")
You must also change for(var i = sheets.length-1; i > 4; i--) to for(var i = sheets.length-1; i > 1; i--), change the 4 to a 1. I have more code in my document but it is comprised of separate functions from this function.
I have added an image below that showcases how this would look before I utilize the function as well as how the Print sheet would look after the function has run. I had to screenshot this unfortunately so the data is condensed. Imagine that each section of data you see, green, blue, and white, are on their own sheets. For clarity the data in green would be contained in TestSheet1, the data in blue in TestSheet2, and the Print sheet would be totally blank before this function runs. Once I have entered my planned quantities in the Planned Quantity columns of TestSheet 1 and 2 I would then run the function. This would result in the data being pasted to the print page. I have added "ref" comments to the code to show where each step of the following explanation occurs. The function runs in this order: 1) The function will determine if a sheet has any values greater than zero in the Planned Quantity column, "ref 1". 2) The function would them loop through the sheet and push any row that met this criteria to array2, "ref 2". 3) The function will determine where to paste the data into the Print sheet based on data that may already by pated to the sheet, row 1, row 32, row 64, and so on, "ref 3". 4) the function will paste a header above the soon to be pasted data. This header include name, date, and column titles. 5) Once the header is placed the function will loop through array2 and paste is contents to the Print sheet, "ref 5". 6) the function will set array2 so that array2 = [] and will then repeat the process for the next sheet until all the sheets have been gone over and there is no more data to paste into Print, "ref 6".
The m loop is unnecessary. It's looping over current row(ls) columns. A mcve looks like this:
const array2d = [1, 2, 3].map(num => new Array(6).fill(num));//boilerplate to simulate array2
console.log({array2d});
for (var l = 0; l < array2d.length; l++) {
for (var m = 0; m < array2d[l].length - 1; m++) {
// HERE IS THE PROBLEM LOOP
// alert(array2d[m] + ' test 2'); // I see this run an additional time after m = length
console.log(array2d[m][0], array2d[m][2], array2d[m][4]);
}
}
Solution:
Remove the loop entirely.
a++; // moves to the next row
// for(var m = 0; m < array2[l].length-1; m++){ // HERE IS THE PROBLEM LOOP
// ui.alert(array2[m]+" test 2"); // I see this run an additional time after m = length
spreadsheet
.getSheetByName('Print')
.getRange(a, 1)
.setValue(array2[l][0]);
spreadsheet
.getSheetByName('Print')
.getRange(a, 2)
.setValue(array2[l][4]);
spreadsheet
.getSheetByName('Print')
.getRange(a, 3)
.setValue(array2[l][2]);
a++;
// }
}
Best practices:
Essential optimization
Related
1. Code description: I wrote this app script that for each row, colors the cell in column A the same color as the last cell of that row with text in it. Additionally, I use an onEdit trigger, so whenever I edit a row, the script runs. This worked alright when I had about 20 rows and 20 columns (2-3 seconds).
2. Problem: I now have a sheet with about 200 rows and 20 columns and the code is extremely slow (3-4 minutes or more).
3. QUESTION: How to make it run faster, or, given what I need, should I write this task in another way?
4. Solutions I thought about but don't like:
split my sheet into several sheets (not as helpful for my use case)
add a button to run the app only when I make an edit (not as nice as an onEdit)
5. Code:
function colorFirstCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('courseX');
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
var columnFirstCells = 1; // column of cell to be colored
var dataRange = sheet.getRange(1,1, lastRow, lastColumn + 1).getValues();
for(var i = 0; i < lastRow; i++)
{
for(var j = 0; j < lastColumn; j++) {
if(dataRange[i][j] != '' && dataRange[i][j+1] == '') { // cell not empty and cell to the right is empty
var backgroundColor = sheet.getRange(i + 1, j + 1).getBackground(); // get color
sheet.getRange(i + 1, columnFirstCells).setBackground(backgroundColor); // set color
of first col cell
}
}
}
}
I believe your goal is as follows.
You want to reduce the process cost of your script.
In this case, how about the following modification?
Modified script:
function colorFirstCell2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('courseX');
var range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());
var backgrounds = range.getBackgrounds();
var colors = range.getDisplayValues().map((r, i) => {
for (var j = r.length - 1; j >= 0; j--) {
if (r[j] != "") {
return [backgrounds[i][j]];
}
}
return [null];
});
sheet.getRange(1, 1, colors.length).setBackgrounds(colors);
}
In this case, first, the values and background colors are retrieved from the data range. And then, an array including the background colors is created. And, the created array is put to the column "A".
References:
map()
setBackgrounds(color)
I am trying to fill a matrix with data read from a text file. The matrix is simply a 2D array that has already been created with parameters. I have check and the array is being created to the correct size based on parameters. For some reason when I scan the next value (value) and then try to insert it at a certain point in the array it is successful once and then quits without filling the rest of the array. If the insertAt method is removed than the loops work perfectly going through all the needed cells. For some reason when the method is added and called it works fine for the first cell but then quits after that.
While loop that inserts the value at a specific point.
while(scan.hasNext()) {
for(int i = 0; i < m1.row; i++) {
for(int j = 0; j < m1.column; j++) {
value = scan.nextInt();
m1.insertAt(i, j, value);
}
}
}
Method for matrix that control the insertion of the value.
public void insertAt(int row, int column, int value) {
if(row >= 0 && column >= 0) {
matrix[row][column] = value;
} else {
System.out.println("Error inserting value. Row: " + row + " Column: " + column);
}
}
Discovered it does this because you cannot put a variable in at a specific point at one time, even if you try to put them in in order. If the matrix is initialized and filled with 0's or some other digit it works fine. It has to be filled creating an empty matrix with a specific size does not work. Otherwise you have to restart the entire method each time to add 1 variable.
rulejs subproject of handsontable is great. But I can't find anything that emulates the "paste special" of spreadsheets. So, if I write
afterOnCellMouseDown: function(r, c) {
var x = hotdata[c['row']][c['col']];
document.getElementById("valorCelCorrente").innerHTML = x;}
I get the formula, not the calculated value that the cell is displaying. Same happens with a manual copy & paste.
A fiddle with example: https://jsfiddle.net/zota/j2a04w83/3/
Any clue? Thanks a lot
Julio
I had the same issue, but solved it with the help of this question.
Firstly you can use hot.plugin.helper.cellValue('CELL REF') to get the actual value, but when using getData() you're looking at the whole set. To solve this problem I basically iterated through the array replacing any formula cells with the value (I only needed columns up until 'K'):
var aCols = ['A','B','C','D','E','F','G','H','I','J','K'];
var data = hot.getData();
for (i = 0; i < data.length; i++){
for(j = 0; j < data[i].length; j++){
if(data[i][j].toString().indexOf('=') > -1){
data[i][j] = hot.plugin.helper.cellValue (aCols[j] + (i+1));
}
}
}
Need to animate a sorting algorithm, with source code line by line visualization.
INTRO:
For the begining, there is a FORM (see it in the picture attached). On top of that form is displayed a dinamicaly created array of Edit components, containing the array to sort.
A little below, on the right is placed a Memo component, containing the algorithm. At the left of each line of that algorithm, dinamicaly is placed a Label, that indicates the line number in algorithm.
The idea is to highlight line by colouring that label, where is the execution at the moment. Sorting starts when "Start" button is clicked. The action for it is following:
int n = 10;
bool swapped = true; hl(1);
int j = 0; hl(2);
int tmp; hl(3);
while (swapped) { hl(4);
swapped = false; hl(5);
j++; hl(6);
for (int i = 0; i < n - j; i++) { hl(7);
if (arr[i] > arr[i + 1]) { hl(8);
tmp = arr[i]; hl(9);
arr[i] = arr[i + 1]; hl(10);
arr[i + 1] = tmp; hl(11);
swapped = true; hl(12);
} hl(13);
} hl(14);
} hl(15);
The hl function must colour labels and pause execution by using Sleep() function
void TForm2::hl(int l)
{
for (int i = 0; i < 24; i++) {
Form2->lines[i]->Font->Color = clGray;
}
Form2->lines[l-1]->Font->Color = clRed;
Sleep(300);
}
PROBLEM:
Code execution is pausing (sleep function works properly), but the labels are still gray, with no visible changes, except the last one, when event finishes. The 15th line is red.
QUESTION:
Can anybody tell me, where I'm wrong, and how to do it right?
http://i.stack.imgur.com/crGyC.jpg
You need to allow the paint message to be processed in order to visually update the display. You can do that with either the Refresh or Update procedures:
Form2->Lines[l-1]->Font->Color = clGray;
Form2->Update(); // or Form2->Refresh();
Is it possible to filter a Slickgrid without using the DataView?
In case it isn't possible, how should the data array be structured in order to display correctly?
I don't have a working example atm. Thanks
Later edit:
After doing some more homework, a filterable datagrid is all about getting matching indexes in a nested array... to get a live sorted result-set that gets updated with grid.setData(filterData);grid render; one should do the following
function intersect(a, b) // find an intersection of 2 arrays (google result on SO
{
var ai=0, bi=0;
var a = a.sort();
var b = b.sort();
var result = new Array();
while( ai < a.length && bi < b.length )
{
if (a[ai] < b[bi] ){ ai++; }
else if (a[ai] > b[bi] ){ bi++; }
else /* they're equal */
{
result.push(a[ai]);
ai++;
bi++;
}
}
return result;
}
// given results sets are arrays of indexes matching search criteria
a = [1,2,3,4];
b = [2,3,4,5];
c = [3,4,5,6];
d = [4,5,6,7];
// should reunite in a nested array
array = [a,b,c,d];
// check intersections for each array[k] and array[k+1]
k = array[0];
for (var i = 0; i<array.length-1; i++){
k = intersect(k,array[i+1]);
}
console.log(k) // returns 4
// k array is the index array that
// is used to build filterData[i] = data[j]
// depends if id is stored in data or in case
// of a database, it is stored in data
// tested in firebug
// thanks
Filter the underlying data array and call grid.setData(filteredData).