Google Spreadsheet script to choose a column based on two values - google-apps-script

Dear Wonderful Scripters,
Apologies for not knowing all the lingo, I'm a novice at best but I've hit the wall. I am trying to hide/show columns based on two values in different cells.
I have a sheet named "Start" which has a value that changes (Year) in O2. This corresponds to a column on a sheet named "Subjects" (see pic). Subjects has a list of subjects and columns labelled "Prep, Year 1-2, Year 3-4 and Year 5-6". I would like to script a solution that checks the value on Start and then reads the column on Subjects for 1s and 0s and shows/hides columns on another sheet named "Reports".
For example, if O2 on Start says Year 1-2, then the script looks at Subjects (D1) and then shows or hides (1 or 0) columns on Reports that correspond with those subjects (eg Preps on have IPDIP so if the value is Year 1-2, then the column on Reports will be hidden. There is more than one column on Reports that will be hidden or shown.
I'm currently using this script:
function ShowHideSubjects() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Reports");
sheet.showColumns(1, sheet.getMaxColumns())
var condition = sheet.getRange("Subjects!C2").getValue();
if (condition = 1) {
sheet.showColumns(10,1)
}
var condition = sheet.getRange("Subjects!C2").getValue();
if (condition = 0) {
sheet.hideColumns(10,1)
}
}
Here's some pics to explain.
[Value on Start][1]
Subjects Sheet
Columns on Reports sheet
Your help would be greatly appreciated. This Google Spreadsheet currently saves our school $1000s a year and it would be awesome to get this sorted out.
Peace and thanks,
Wayne

The Column Chooser
Here's the code
function onOpen()
{
SpreadsheetApp.getUi().createMenu('My Tools')
.addItem('ShowHideSubjects','ShowHideSubjects')
.addToUi();
}
function ShowHideSubjects()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var reportsSheet = ss.getSheetByName('Reports');
var subjectsSheet = ss.getSheetByName('Subjects')
var startSheet = ss.getSheetByName('Start');
var startSubjectsRange = startSheet.getRange('O2');
var reportsSheetDataRange = reportsSheet.getDataRange();
var reportsSheetDataArray = reportsSheetDataRange.getValues();
var reportsSheetDataRangeLastRow = reportsSheetDataRange.getLastRow();
var reportsSheetDataRangeLastColumn = reportsSheetDataRange.getLastColumn();
for(var i = 0;i < reportsSheetDataRangeLastRow;i++)
{
for(var j = 0;j < reportsSheetDataRangeLastColumn;j ++)
{
if(reportsSheetDataArray[i][j] == 'IPDIP')
{
var ipdipRow = i + 1;
var ipdipCol = j + 1;
break;
}
}
}
var reportsNumColumns = reportsSheetDataRange.getLastColumn() - ipdipCol + 1
//check that its the right reports range
//reportsSheet.setActiveSelection(reportsSheet.getRange(ipdipRow, ipdipCol, 1 , reportsNumColumns));
var reportsHeaderRange = reportsSheet.getRange(ipdipRow, ipdipCol, 1 , reportsNumColumns);
var subjectsSheetDataRange = subjectsSheet.getDataRange();
var subjectsSheetDataArray = subjectsSheetDataRange.getValues();
var subjectsSheetDataRangeLastRow = subjectsSheetDataRange.getLastRow();
var subjectsSheetDataRangeLastColumn = subjectsSheetDataRange.getLastColumn();
for(var i = 0;i < subjectsSheetDataRangeLastRow;i++)
{
for(var j = 0;j < subjectsSheetDataRangeLastColumn;j ++)
{
if(subjectsSheetDataArray[i][j] == 'Subjects')
{
var subjectsRow = i + 1;
var subjectsCol = j + 1;
break;
}
}
}
//check that its the right subjects range
//subjectsSheet.setActiveSelection(subjectsSheet.getRange(subjectsRow, subjectsCol,subjectsSheetDataRange.getLastRow() - subjectsRow + 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1));
var subjectsShowHideHeaderRange = subjectsSheet.getRange(subjectsRow, subjectsCol, 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1);
var subjectsShowHideHeaderArray = subjectsShowHideHeaderRange.getValues();
var subjectsShowHideRange = subjectsSheet.getRange(subjectsRow, subjectsCol,subjectsSheetDataRange.getLastRow() - subjectsRow + 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1);
//checked subjectsShowHideRange
//subjectsSheet.setActiveSelection(subjectsShowHideRange);
var w = subjectsShowHideHeaderRange.getWidth();
var startString = startSubjectsRange.getValue();
for(var i = 0;i < w;i++)
{
if(subjectsShowHideHeaderArray[0][i] == startString)
{
var subjectsShowHideColumn = subjectsCol + i;
break;
}
}
//checked the subjectsShowHideColumn
//SpreadsheetApp.getUi().alert( startSubjectsRange.getValue() + ' is column ' + subjectsShowHideColumn + ' on the subjects page. And subjects is on column ' + subjectsCol + '.');
//Checked the subjectsShowHideRange
//subjectsSheet.setActiveSelection(subjectsShowHideRange);
var subjectsShowHideArray = subjectsShowHideRange.getValues();
var subjectsShowHideRangeHeight = subjectsShowHideRange.getHeight();
//for(var i = 0;i < subjectsShowHideRangeHeight;i++)
for(var i = subjectsShowHideRangeHeight -1;i >= 0;i--)
{
var subject = subjectsShowHideArray[i][0];
var show = subjectsShowHideArray[i][subjectsShowHideColumn-subjectsCol];
var showhidecolsS=reportsColumnHeaderMatch(ipdipRow,ipdipCol,reportsNumColumns,subject);
if(showhidecolsS)
{
var showhidecolsA=showhidecolsS.split(',');
if(showhidecolsA)
{
for(var j = 0; j < showhidecolsA.length; j++)
{
columnsShowHide(showhidecolsA[j],show);
}
}
}
}
}
function columnsShowHide(column, show)
{
var column = (typeof(column) !== 'undefined')? column: 0;
var show = (typeof(show) !== 'undefined')? show: 1;
if(column == 0)
{
SpreadsheetApp.getUi().alert('Error: The column parameter passed to columnsShowHide is undefined.');
return;
}
else
{
switch(show)
{
case 1:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').showColumns(column);
//SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').getRange(1,column,10,1).setBackground('yellow');
break;
case 0:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').hideColumns(column);
//SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').getRange(1,column,10,1).setBackground('white');
break;
default:
SpreadsheetApp.getUi().alert('Show parameter is out of range not 0 or 1 in function columnsShowHide');
}
}
}
function reportsColumnHeaderMatch(row, col, numcolumns, subject)
{
var sht = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports');
var rng = sht.getRange(row, col, 1, numcolumns);
var rngA = rng.getValues();
var result = '';
var firsttime = true;
for(var i = 0;i < numcolumns;i++)
{
//var s = rngA[0][i].toString();
var s = rngA[0][i];
var re = new RegExp('^' + subject);
if(s.match(re))
{
if(!firsttime)result += ',';
result += col + i;
firsttime = false;
}
}
return result;
}
function dispStatus(title,html,width,height)
{
// Display a modeless dialog box with custom HtmlService content.
var title = typeof(title) !== 'undefined' ? title : 'No Title Provided';
var width = typeof(width) !== 'undefined' ? width : 250;
var height = typeof(height) !== 'undefined' ? height : 300;
var html = typeof(html) !== 'undefined' ? html : '<p>No html provided.</p>';
var htmlOutput = HtmlService
.createHtmlOutput(html)
.setWidth(width)
.setHeight(height);
SpreadsheetApp.getUi().showModelessDialog(htmlOutput, title);
}
Slight Problem
I have a working version but I ran into a problem. I'm using a regular expression RegExp('^' + subject). What this allows me to do is to build the regular expression on the fly. When the subject is IPDIP the regular expression ^IPDIP matches all of the characters of the IPDIP column header and since the second row of Year 1-2 is 0 then column IPDIP is turned off but then the next subject is IPD and the regular expression ^IPD also matches IPDIP and the third row of Year 1-2 now turns off IPDIP which is probably a problem. I was planning on using this technique to match the rest of the columns because the subjects are only the first three letters of the rest of the column headers and that would be a great way to catch them. So I guess the question is do you want IPDIP on or off in for the Year 1-2 reports.
Are all of the Reports column header names six characters or greater? I could take advantage of that because IPDIP is only 5 characters. So I could apply my regular expression only to headers that are 6 characters or greater. But I need your input on that because I'm guessing that the actual Reports sheet is probably a little wider than the one I see.
I guess another solution could be to work my way up through from the bottom of the subjects column to the top then IPDIP would always be the 5 character match.
I just noticed that we have the same problem with ICT and ICTICT.
For now I decided to just reverse the order of going through the subjects column from bottom to top. As long as you always put the longest strings that have overlapping matches on the top you should be okay.
Just so that you know
One of things I did with this code is to make the origin of the tables be the location of the Subjects Header on the subjects page and the IPDIP header on the Reports pages. So this makes possible for you to move the tables (i.e. add columns to the left and above the current data). The program figures out where the data is based up these two key locations and readjusts itself automatically. Another way of saying that is to say that it doesn't assume that the first row/column in your table is row/column 1/A

Works exactly as requested, first try :-) Thanks to Cooper for the hard work.
function onOpen()
{
SpreadsheetApp.getUi().createMenu('My Tools')
.addItem('ShowHideSubjects','ShowHideSubjects')
.addToUi();
}
function ShowHideSubjects()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var reportsSheet = ss.getSheetByName('Reports');
var subjectsSheet = ss.getSheetByName('Subjects')
var startSheet = ss.getSheetByName('Start');
var startSubjectsRange = startSheet.getRange('O2');
var reportsSheetDataRange = reportsSheet.getDataRange();
var reportsSheetDataArray = reportsSheetDataRange.getValues();
var reportsSheetDataRangeLastRow = reportsSheetDataRange.getLastRow();
var reportsSheetDataRangeLastColumn = reportsSheetDataRange.getLastColumn();
for(var i = 0;i < reportsSheetDataRangeLastRow;i++)
{
for(var j = 0;j < reportsSheetDataRangeLastColumn;j ++)
{
if(reportsSheetDataArray[i][j] == 'IPDIP')
{
var ipdipRow = i + 1;
var ipdipCol = j + 1;
break;
}
}
}
var reportsNumColumns = reportsSheetDataRange.getLastColumn() - ipdipCol + 1
//check that its the right reports range
//reportsSheet.setActiveSelection(reportsSheet.getRange(ipdipRow, ipdipCol, 1 , reportsNumColumns));
var reportsHeaderRange = reportsSheet.getRange(ipdipRow, ipdipCol, 1 , reportsNumColumns);
var subjectsSheetDataRange = subjectsSheet.getDataRange();
var subjectsSheetDataArray = subjectsSheetDataRange.getValues();
var subjectsSheetDataRangeLastRow = subjectsSheetDataRange.getLastRow();
var subjectsSheetDataRangeLastColumn = subjectsSheetDataRange.getLastColumn();
for(var i = 0;i < subjectsSheetDataRangeLastRow;i++)
{
for(var j = 0;j < subjectsSheetDataRangeLastColumn;j ++)
{
if(subjectsSheetDataArray[i][j] == 'Subjects')
{
var subjectsRow = i + 1;
var subjectsCol = j + 1;
break;
}
}
}
//check that its the right subjects range
//subjectsSheet.setActiveSelection(subjectsSheet.getRange(subjectsRow, subjectsCol,subjectsSheetDataRange.getLastRow() - subjectsRow + 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1));
var subjectsShowHideHeaderRange = subjectsSheet.getRange(subjectsRow, subjectsCol, 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1);
var subjectsShowHideHeaderArray = subjectsShowHideHeaderRange.getValues();
var subjectsShowHideRange = subjectsSheet.getRange(subjectsRow, subjectsCol,subjectsSheetDataRange.getLastRow() - subjectsRow + 1 ,subjectsSheetDataRange.getLastColumn() - subjectsCol + 1);
//checked subjectsShowHideRange
//subjectsSheet.setActiveSelection(subjectsShowHideRange);
var w = subjectsShowHideHeaderRange.getWidth();
var startString = startSubjectsRange.getValue();
for(var i = 0;i < w;i++)
{
if(subjectsShowHideHeaderArray[0][i] == startString)
{
var subjectsShowHideColumn = subjectsCol + i;
break;
}
}
//checked the subjectsShowHideColumn
//SpreadsheetApp.getUi().alert( startSubjectsRange.getValue() + ' is column ' + subjectsShowHideColumn + ' on the subjects page. And subjects is on column ' + subjectsCol + '.');
//Checked the subjectsShowHideRange
//subjectsSheet.setActiveSelection(subjectsShowHideRange);
var subjectsShowHideArray = subjectsShowHideRange.getValues();
var subjectsShowHideRangeHeight = subjectsShowHideRange.getHeight();
//for(var i = 0;i < subjectsShowHideRangeHeight;i++)
for(var i = subjectsShowHideRangeHeight -1;i >= 0;i--)
{
var subject = subjectsShowHideArray[i][0];
var show = subjectsShowHideArray[i][subjectsShowHideColumn-subjectsCol];
var showhidecolsS=reportsColumnHeaderMatch(ipdipRow,ipdipCol,reportsNumColumns,subject);
if(showhidecolsS)
{
var showhidecolsA=showhidecolsS.split(',');
if(showhidecolsA)
{
for(var j = 0; j < showhidecolsA.length; j++)
{
columnsShowHide(showhidecolsA[j],show);
}
}
}
}
}
function columnsShowHide(column, show)
{
var column = (typeof(column) !== 'undefined')? column: 0;
var show = (typeof(show) !== 'undefined')? show: 1;
if(column == 0)
{
SpreadsheetApp.getUi().alert('Error: The column parameter passed to columnsShowHide is undefined.');
return;
}
else
{
switch(show)
{
case 1:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').showColumns(column);
//SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').getRange(1,column,10,1).setBackground('yellow');
break;
case 0:
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').hideColumns(column);
//SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports').getRange(1,column,10,1).setBackground('white');
break;
default:
SpreadsheetApp.getUi().alert('Show parameter is out of range not 0 or 1 in function columnsShowHide');
}
}
}
function reportsColumnHeaderMatch(row, col, numcolumns, subject)
{
var sht = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Reports');
var rng = sht.getRange(row, col, 1, numcolumns);
var rngA = rng.getValues();
var result = '';
var firsttime = true;
for(var i = 0;i < numcolumns;i++)
{
//var s = rngA[0][i].toString();
var s = rngA[0][i];
var re = new RegExp('^' + subject);
if(s.match(re))
{
if(!firsttime)result += ',';
result += col + i;
firsttime = false;
}
}
return result;
}
function dispStatus(title,html,width,height)
{
// Display a modeless dialog box with custom HtmlService content.
var title = typeof(title) !== 'undefined' ? title : 'No Title Provided';
var width = typeof(width) !== 'undefined' ? width : 250;
var height = typeof(height) !== 'undefined' ? height : 300;
var html = typeof(html) !== 'undefined' ? html : '<p>No html provided.</p>';
var htmlOutput = HtmlService
.createHtmlOutput(html)
.setWidth(width)
.setHeight(height);
SpreadsheetApp.getUi().showModelessDialog(htmlOutput, title);
}

Related

Google Apps Script Substring

In my spreadsheet I have a file path to an image stored in a sub folder. The image is stored like this because I am making an app for my employer using appsheet.com. They want this app developed using AppSheet, which does not allow images to be stored directly into the spreadsheets that are used to build these app.
The data stored in the spreadsheet must also be extracted and applied to a template and I am making a Google Apps Script to do this. I have a functional script which finds the template and extract the users chosen data row, but I cannot extract the image needed.
I have been trying to make a substring of the file path to get the file name, but I have been unable to do this.
In the block of code where I am trying to create the substring, I am getting an error on the line sig = signature.getText();. The error is TypeError: signature.getText is not a function (line 176, file "Code").
This is what the file path looks like Signatures/FT101.Signed (%SIGNED%).103735.png, and the substring I need would look like this FT101.Signed (%SIGNED%).103735.png
I have tried multiple methods that were provided by other questions asked on stack overflow as well as any potential methods available in the Google Apps Script reference.
The following function takes the users input from a prompt and uses it to find the desired row number. It then takes the data from that row and applies it to a template based on the category the data falls under.
var response = {};
var sign = "";
function chooseRowMethod(templateId){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var data = sheet.getRange(2, 2, 11, 18).getValues();//starting with row 2 and column 1 as our upper-left most column, get values from cells from 1 row down, and 15 columns along - hence (2,1,1,15)
var docTitle = sheet.getRange(2, 2, 11, 1).getValues();//this is grabbing the data in field B2
var docTitleTagNumber = sheet.getRange(2, 3, 11, 1).getValues();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
today = dd + '/' + mm + '/' + yyyy;
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
Logger.log(i);
var row = data[i - 1];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
body.replaceText("%SITEID%", row[0]);
body.replaceText("%TAG%", row[1]);
...
body.replaceText("%SAT%", row[14]);
var signature = sheet.getRange(2, 18, 11, 1).getValues();
var sig;
var sign = {};
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
sig = signature.getText();
sign[i][0] = sig.substring(sig.indexOf("/") + 1);
}
}
}
var sigFolder = DriveApp.getFolderById("1LiJKGjTbpvRZ5RrMTQoyTuAjrozA14FN");
var file = sigFolder.getFilesByName(sign);
var image = file.getId();
body.appendImage(image);
doc.saveAndClose();
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("16wRGBVdV0OZ5YfKhqEQSFMsux-ekGCCa");
newFolder.addFile(file);
var newDocTitle = docTitle[i - 1][0];
var newDocTagNumber = docTitleTagNumber[i - 1][0];
doc.setName(newDocTitle + " " + newDocTagNumber + " " + today);
}
}
}
}
This is where I have been attempting to get the image.
var signature = sheet.getRange(2, 18, 11, 1).getValues();
var sig;
var sign = {};
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response){
sig = signature.getText();
sign[i][0] = sig.substring(sig.indexOf("/") + 1);
}
}
}
var sigFolder = DriveApp.getFolderById("1LiJKGjTbpvRZ5RrMTQoyTuAjrozA14FN");
var file = sigFolder.getFilesByName(sign);
var image = file.getId();
body.appendImage(image);
This next function gives the user the prompt and applies the correct template.
function chooseRow(){
var ui = SpreadsheetApp.getUi(); // Same variations.
var result = ui.prompt('Please enter the Tag number of the row you wish to print.', ui.ButtonSet.OK_CANCEL);
var button = result.getSelectedButton();
response = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK".
ui.alert('Your tag number is' + response + '.');
} else if (button == ui.Button.CANCEL) {
// User clicked X in the title bar.
ui.alert('You closed the dialog.');
return 'the end';
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var category = sheet.getRange(2, 4, 11, 1).getValues();
var templateId = {};
for(var i = 0; i < values.length; i++){
for(var j = 0; j < values[i].length; j++){
if(values[i][j] == response && category[i - 1][0] == "Instrument"){
templateId = "1cx2-6ju-o7DaRPnbuYxxdvVVFeGQzpTXaXV3wMuRpqo";
chooseRowMethod(templateId);
return "";
} else if(values[i][j] == response && category[i][0] == "Motor" || values[i][j] == response && category[i][0] == "Valve"){
templateId = "1sYx_JcoDHY-pzjEDlxMMa3dtdzOOE8CyyLGQk8WHg7s";
chooseRowMethod(templateId);
return "";
}
}
}
}
The expected result is a substring of the file path that can be used to retrieve an image that can be appended to the body of a document.
Here is a link to the spreadsheet.
I did several changes to your code, including the edit proposed by Cooper. It's retrieving the substring and adding the image to the file successfully. I hope it works for you:
function chooseRow(){
var ui = SpreadsheetApp.getUi(); // Same variations.
var result = ui.prompt('Please enter the Tag number of the row you wish to print.', ui.ButtonSet.OK_CANCEL);
var button = result.getSelectedButton();
response = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK".
ui.alert('Your tag number is' + response + '.');
} else if (button == ui.Button.CANCEL) {
// User clicked X in the title bar.
ui.alert('You closed the dialog.');
return 'the end';
}
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var tags = sheet.getRange(2, 4, 11, 1).getValues();
var category = sheet.getRange(2, 3, 11, 1).getValues();
for(var i = 0; i < tags.length; i++){
if(tags[i][0] == response && category[i][0] == "Instrument"){
var templateId = "my_template_id";
chooseRowMethod(templateId, i);
return ""; // You don't need to return empty string, just return null
} else if(tags[i][0] == response && category[i][0] == "Motor" || tags[i][0] == response && category[i][0] == "Valve"){
var templateId = "my_template_id_bis";
chooseRowMethod(templateId, i);
return ""; // You don't need to return empty string, just return null
}
}
}
The function chooseRowMethod gets the row index chosen by the user in chooseRow so that it doesn't have to be looked for again:
function chooseRowMethod(templateId, rowNumber){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getRange(2, 2, 11, 18).getValues();
var today = new Date();
var dd = today.getDate();
var mm = today.getMonth() + 1;
var yyyy = today.getFullYear();
today = dd + '/' + mm + '/' + yyyy;
var row = data[rowNumber];
var docTitle = row[1];
var docTitleTagNumber = row[2];
var docId = DriveApp.getFileById(templateId).makeCopy().getId();
var doc = DocumentApp.openById(docId);
var body = doc.getActiveSection();
body.replaceText("%SITEID%", row[0]);
body.replaceText("%TAG%", row[1]);
// ...
body.replaceText("%SAT%", row[14]);
var signature = row[17];
var sign = signature.substring(signature.indexOf("/") + 1);
var sigFolder = DriveApp.getFolderById("my_sigfolder_id");
var files=sigFolder.getFilesByName(sign);
var n = 0;
while(files.hasNext()) {
var file=files.next();
n++;
} if(n>1) {
SpreadsheetApp.getUi().alert('There is more than one file with this name: ' + sign);
}
body.appendImage(file);
doc.saveAndClose();
var file = DriveApp.getFileById(doc.getId());
var newFolder = DriveApp.getFolderById("my_newfolder_id");
newFolder.addFile(file);
doc.setName(docTitle + " " + docTitleTagNumber + " " + today);
}

Cell reference is out of range - But I use GetDataRange()

I've tried a bunch of solution but it always end with Cell ref is out of range.
I thought that GetDataRange() & GetLastRow() would cover the last non-blank cell but I'm stuck. I've tried to hardcode my cell values (from X to Y) but nothing seems to change.
var course;
var searchingCol;
function CalculateFurnitureList()
{
GetCourseString();
FindColumnNumber(course);
GetSpecialFurnituresValue();
}
function GetCourseString()
{
var activeSheet = SpreadsheetApp.getActiveSheet();
var courseType = activeSheet.getRange("A2").getValue();
Browser.msgBox('Generate furniture list for ' + courseType + ' course');
course = courseType;
}
function FindColumnNumber(ct)
{
var furnitureSheet = SpreadsheetApp.getActive().getSheetByName("MaterielCours");
var range = furnitureSheet.getDataRange();
var width = range.getWidth();
//Search every cell in the designated area to find the course name
for (var i = 1; i <= width; i++)
{
var data = range.getCell(1,i);
if (data.getValue() == ct)
{
Browser.msgBox('Trouvé ! ' + data.getValue() + ' se trouve en : ' + i +':1');
searchingCol = i;
}
}
}
function GetSpecialFurnituresValue()
{
Browser.msgBox(searchingCol);
var furnitureSheet = SpreadsheetApp.getActive().getSheetByName("MaterielCours");
var range = furnitureSheet.getDataRange();
var length = range.getLastRow();
for (var i = 1; i < length; i++)
{
var data = range.getCell(searchingCol, i);
if (data.getValue() !== "")
{
// Do nothing
}
else
{
if (data == "Abs")
{
Browser.msgBox('NTM');
}
else
{
// Calculate value
}
}
}
}
It's
var data = range.getCell(searchingCol, i);
in GetSpecialFurnituresValue() which causes the problem.
The weird thing is that the same process in FindColumnNumber is working perfectly.
(editing my original answer - I must have been tired!) In the function GetSpecialFurnitureValues, there are a few things.
The for loop uses i < length, but it should be <= or you will miss the last row:
// for (var i = 1; i < length; i++)
for (var i = 1; i <= length; i++)
Your column/rows were simply reversed. :)
// var data = range.getCell(searchingCol, i);
var data = range.getCell(i, searchingCol);
Hope that helps!

Set Value of checkbox

I want to uncheck a checkbox if an adjacent cell has a value input in it.
function onEdit(event) {
var eventRange = event.range;
if (eventRange.getColumn() == 3) { // 3 == column C
var columnFRange = SpreadsheetApp.getActiveSheet().getRange(eventRange.getRow(), 6, eventRange.getNumRows(), 6);
var values = columnFRange.getValues();
for (var i = 0; i < values.length; i++) {
values[i][0] = 'FALSE';
}
columnFRange.setValues(values);
}
}
This looks at a sheet called "Testing" and if there is a value in column A it will put "TRUE" in the adjacent cell to the right. I believe this is what you're going for?
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Testing");
var hasValue = sheet.getRange("A:A").getValues();
var value = '';
for(var i = 0; i < hasValue.length; i++){
if(hasValue[i][0].length > 0) {
sheet.getRange("B" + (i + 1)).setValue("TRUE");
}
else {
sheet.getRange("B" + (i + 1)).setValue("");
}
}
}

compare two spreadsheet and output the difference using google app scripts

well, i'm trying to do what described in title. Both spreadsheets have only one sheet that are the ones i'm comparing. One spreadsheet is and update of the other, so i'm trying to get only new content. (if it were a fc (dos command) like function this would be easy...)
After doing some search, i have the folloing script that should work on most cases, that uses arrays for each sheet.
function test() {
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId =Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
var newarray = getNewData(array,old_array);
Logger.log('there are ' + newarray.length + 'different rows');
}
function getNewData(array1,array2){
var diff =array2;
for (var i = 0; i<array1.length; i++){
var duplicate = false;
for (var j = 0;j<diff.length;j++){
if (array1[i].join() == diff[j].join()){
Logger.log('duplicated line found on rows ' + i + ':' + j);
diff.splice(j,1);
var duplicate= true;
break;
}
}
if (duplicate==false) {
Logger.log('not duplicated line found on row ' + i);
diff.push(array1[i]);
}
}
return diff;
}
The thing is that the files are too big, almost 30000 rows, so the scripts exceed 5 minutes limit for execution.
Is there a way to improve this, like for instance, eliminate the inner for loop?
Or there is a way to do it in parts? like first the first 5000 rows, and so on.
Regards,
EDIT: after analizing the spreadsheet a little, i found out that there is a ID for every row, so now i can concentrate the search only in one column of each spreadsheet. So here is my new implementation:
function test(){
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId =Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
//The COlumn has an indicator, so i search for that. I don't control the formatting of the files, so i search in both spreadsheet for the indicator
var searchString = 'NAME';
for (var i = 0; i < old_array.length; i++) {
for (var j = 0; j < old_array[i].length; j++) {
if (old_array[i][j] == searchString) {
var Row_old = i+1;
var Column_old = j;
break;
}
}
if (Row_old != undefined){
break;
}
}
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < array[i].length; j++) {
if (array[i][j] == searchString) {
var Row = i+1;
var Column = j;
break;
}
}
if (Row != undefined){
break;
}
}
Logger.log(Row_old+':::'+Column_old+'\n'+Row+':::'+Column);
var diff_index =[];
var row_ind = 0;
for (var i=Row;i<array.length;i++){
Logger.log(i);
var existe = ArrayLib.indexOf(old_array, Column_old, array[i][Column]);
if (existe==-1){
Logger.log(row_ind+'!!!');
diff_index[row_ind]=i;
row_ind++;
}
}
Logger.log(diff_index);
}
This still run out of time... I will now try to incorporate your comments.
Your script has a few major bottlenecks that slow it down massively:
Starting both loops at 0 every time makes its runtime explode
splicing every time you find a duplicate requires to move the array around
string concatenating an array on every iteration
We can circumvent these issues by:
sorting the second range once
I'm sure there's something clever to be done by iteratively binary searching through every column but we'd have to resort every time so we'll binary search the first column and then do a linear search.
We will use ArrayLib for the sorting (I hope it's a fast sorting algorithm).
Let's start with a function to find the first row where the first column matches a value (the first column of the current row):
function firstRowMatchingCol1(target, lookupRange) {
var min = 0;
var max = lookupRange.length - 1;
var guess;
var guessVal;
while(min <= max) {
guess = (min + max) / 2 | 0;
guessVal = lookupRange[guess][0];
if (guessVal < target) {
min = guess + 1;
} else if (guessVal > target) {
max = guess - 1;
} else {
while (guess > 0 && lookupRange[guess - 1][0] === target) {
guess -= 1;
}
return guess;
}
}
return -1;
}
Now we can go linearly go through every row and check if the columns match until the first column doesn't match anymore.
function matchExists(row, lookupRange) {
var index = firstRowMatchingCol1(row[0], lookupRange);
if (index === -1) {return false;}
while (index < lookupRange.length && lookupRange[index][0] === row[0]) {
for (var col = 1; col < row.length; col++) {
if (row[col] !== lookupRange[index][col]) {break;}
if (col === row.length - 1) {return true;} // This only works if the ranges are at least two columns wide but if they are one column wide you can just check if index > -1
}
index += 1;
}
return false;
}
And finally we can get the duplicates like this:
function getNonDuplicates(r1, r2) {
r2 = ArrayLib.sort(r2, 0, true);
return r1.filter(function(row) {return !matchExists(row, r2);});
}
Like mTorres' code this is untested
The solution I'm proposing is a "hack" around the time limit. But if you want a cleaner solution, you could, if possible, reorganize and make your code more efficient by having the arrays ordered somehow.
You don't specify the data inside array1 and array2, if rows had some sort of ID field you could order by this ID and check row i on array1 and row i on array2 instead of comparing every row in array1 with every row in array2 (which is extremely inefficient with 30000 rows).
If your data does not have an ID field to order the rows, then what you could is something based on my proposed solution: add a track for every compared row on array1. When the run reaches the time limit then you run again the function but starting from the last compared row (you would know which was because you'll be tracking the compared rows), and when the second run times out you repeat, and so on.
Every time you run your comparison you ask if it's the first run (or use a boolean - I prefer to ask the user, this way you won't forget to change the boolean), if it's the first run, you delete the tracking
column, if it's not the first run, you'll start with the next to last tracked row so basically continuing your script where it ended. I've been using this technique with good results.
In code (untested, so check it out before running it with real data):
/**
* Only checks if it's the first run and calls the real work function
*/
function test() {
var firstRun = "yes" === Browser.msgBox("Question", "Is this the first run?", Browser.Buttons.YES_NO);
doTest(firstRun);
}
/**
* Gets the data of the 2 spreadsheets and also the starting
* row
*/
function doTest(firstRun) {
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId = Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
/**
* Here is the code to create the tracking hability
*/
var strartFromRow = 0; // 0 because row 1 is array 0 index when you getValues();
var trackSheet = old_spreadsheet.getSheetByName("Tracking");
if (trackSheet === null) {
trackSheet = old_spreadsheet.insertSheet("Tracking");
}
if (firstRun) {
trackSheet.getRange("A:A").clearContent(); // make sure there no row is tracked yet
}
else {
// we have to continue from the previous row, keep in mind you're making the comparison
// with array which is 0 based, but sheet is 1 based, but you want the next one so getLasRow()
// should be the first item to compare on your array
strartFromRow = trackSheet.getLastRow();
}
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
// when you call the DIFF function, pass the tracking sheet and the start Row
var newarray = getNewData(array,old_array, trackSheet, startFromRow);
Logger.log('there are ' + newarray.length + 'different rows');
}
/**
* Creates a diff array using array1 and array2
* It marks each element on array1 once it has checked if it's in array2
*/
function getNewData(array1, array2, trackingSheet, startFromRow){
var logRow = trackingSheet.getLastRow();
var diff = array2;
for (var i = startFromRow; i < array1.length; i++){
var duplicate = false;
for (var j = 0; j < diff.length;j++){
if (array1[i].join() == diff[j].join()){
Logger.log('duplicated line found on rows ' + i + ':' + j);
diff.splice(j,1);
duplicate = true;
break;
}
}
if (duplicate === false) {
Logger.log('not duplicated line found on row ' + i);
diff.push(array1[i]);
}
trackingSheet.getRange(logRow++, 1).setValue("Checked!"); // Mark i row as checked
}
return diff;
}
Here's an alternate solution that gets around the time limit. Create a new dedicated spreadsheet along with a custom sidebar. The sidebar will require you to create some HTML that will ultimately be embedded and rendered in an iframe on the client. You can embed pure javascript into the HTML via script tags.
The beauty of this approach is that these scripts will not run server-side but on the client independently of Google Apps Script's server-side environment and are not subject to the 6 minute limit. Moreover, they can also call functions in your Google Script. So one approach would be to have the client-side scripts call a Google Script function to retrieve the requisite data, do all the heavy processing in the client-side scripts, and then send the results back to the server-side script to update the sheet.
Here's a link to setting up a custom sidebar to get you started:
https://developers.google.com/apps-script/guides/dialogs#custom_sidebars
Finally, i decided to go for the Cache service option, here is the code and i'm testing it to see if i keep with this.
function getNewData() {
//deleting triggers
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction()=='getNewData'){
ScriptApp.deleteTrigger(triggers[i]);
}
}
//max running time = 5.5 min
var MAX_RUNNING_TIME = 330000;
var startTime= (new Date()).getTime();
//get cache
var cache = CacheService.getUserCache();
var downloaded =JSON.parse(cache.get('downloaded'));
var compared =JSON.parse(cache.get('compared'));
//start
if (downloaded==1 && compared!=1){
//folder
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId = licitacionesFolder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_array = old_sheet.getDataRange().getValues();
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(old_file.getId());
var sheet = spreadsheet.getSheets()[0];
var array = sheet.getDataRange().getValues();
Logger.log(array.length+'::'+old_array.length);
// Column
var searchString = 'NAME';
var RC = getColumn(array,searchString);
var Row = RC.Row;
var Column = RC.Column;
var RC = getColumn(old_array,searchString);
var Row_old = RC.Row;
var Column_old = RC.Column;
Logger.log(Row_old+':::'+Column_old+'\n'+Row+':::'+Column);
//compare
var diff_index =JSON.parse(cache.get('diff_index'));
var row_ind =JSON.parse(cache.get('row_ind'));
var Roww =JSON.parse(cache.get('Row'));
if (diff_index==null){var diff_index = [];}
if (row_ind==null){var row_ind = 0;}
if (Roww==null){var Roww = Row;}
Logger.log(row_ind+'\n'+Roww);
for (var i=Roww;i<array.length;i++){
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME){
Logger.log((currTime - startTime)/(1000*60));
Logger.log(i+'::'+row_ind);
cache.putAll({'diff_index': JSON.stringify(diff_index),'row_ind': JSON.stringify(row_ind),'Row': JSON.stringify(i-1)},21600);
ScriptApp.newTrigger('getNewData').timeBased().after(2 * 60 * 1000).create();
return;
} else {
Logger.log(i);
var existe = ArrayLib.indexOf(old_array, Column_old, array[i][Column]);
if (existe==-1){
Logger.log(row_ind+'!!!');
diff_index[row_ind]=i;
row_ind++;
}
}
}
cache.putAll({'diff_index': JSON.stringify(diff_index),'Row': JSON.stringify(Row),'compared': JSON.stringify(1)},21600);
} else {
Logger.log('file not downloaded yet or already compared');
}
}
function getColumn(array,searchString){
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < array[i].length; j++) {
if (array[i][j] == searchString) {
var Row = i+1;
var Column = j;
break;
}
}
if (Row != undefined){
break;
}
}
return {Row: Row, Column: Column};
}

optimizing code and how to take selected region of active spreadsheet as input-google docs

I have written script for changing a format like 12.4/12/12.03 into 12:40:00/12:00:00/12:03:00
Here's the code:
function myFunction() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows= sheet.getDataRange();
var numRows=rows.getNumRows();
var values=rows.getValues()
var column = [];
var p = 0;
var k = "H";
for (var i=0;i<numRows;i++) {
// var cell =
//Split the string a the .
var string = values[i][7].split(".");
string[0] = string[0].toString();
p = i+1;
k = "H"+p;
var cell = sheet.getRange(k);
if(string[1]){
string[1] = string[1].toString();
// if the second part is like 4 in 12.4 you set it to 40
if(string[1]!=0) {
if (string[1].length == 1 )
{ string[1] += "0";}
}
// Set the row value to the format you like, here : 12:40:00/12:40
var changed_format = string[0] + ":" + string[1] + ":00";
values[i][7]=changed_format;
p = i+1;
k = "H"+p;
cell.setValue(changed_format);
}
else {
var changed_format = values[i][7]+":00:00";
cell.setValue(changed_format);
}
}
In the above code, I have mentioned columns...i.e., I have to run this script for each column...every time... ex: values[i][7] k="H"+p for 8th column. So, can anyone plz tell me how to do...all at a time...and if possible reduce my code..(optimize)..and also..if is it possible to do like this : if I select the column in the spreadsheet and the changes done by the script applies to that selected region...I mean I want my script to take the selected region as input...is it possible to do..if how.?
One key to optimize your code is to reduce the number of calls to Google services and try getting them done using JavaScript. Here is an optimized version that you could use.
Note that I havent tested it - so if you come across minor syntax errors, feel free to fix them or give a shout if you cannot fix them.
function myFunction() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows= sheet.getDataRange();
var values=rows.getValues();
var COL_H = 8;
var numRows=values.length ; // Length of array = nuymRows. rows.getNumRows();
var column = [];
//var p = 0;
//var k = "H";
var destArray = new Array();
for (var i=0;i<numRows;i++) {
// var cell =
//Split the string a the .
var string = values[i][7].split(".");
string[0] = string[0].toString();
//p = i+1;
//k = "H"+p;
//var cell = sheet.getRange(k);
if(string[1]){
string[1] = string[1].toString();
// if the second part is like 4 in 12.4 you set it to 40
if(string[1]!=0) {
if (string[1].length == 1 )
{ string[1] += "0";}
}
// Set the row value to the format you like, here : 12:40:00/12:40
var changed_format = string[0] + ":" + string[1] + ":00";
//values[i][7]=changed_format;
//p = i+1;
//k = "H"+p;
//cell.setValue(changed_format);
destArray.push([changed_format]);
}
else {
var changed_format = values[i][7]+":00:00";
//values [i][7] = changed_format;
//cell.setValue(changed_format);
destArray.push([changed_format]);
}
}
var destRange = sheet.getRange(1, COL_H, destArray.length, 1);
destRange.setValues(values);
}
TIP: Putting formatted code in the question helps readability