Optimal way to set cell values in Google Sheet via Script - google-apps-script

I have a template sheet with checkboxes and I want to copy the checked ones to a new sheet. I have a working version that involves adding rows but I am looking for something faster. I thought getting a range of values on both the new and old sheets and working on the arrays would be best but I hit a error:
'Cannot covert Array to Object[][]".
I think the issue has to do with the fact that this is a new unpopulated sheet. The code below is the simplest example of what is happening. Am I doing something wrong, or is this just not possible?
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
var r = s.getRange(1,1,5);
var v = r.getValues();
for ( var i=0; i < 5; i++) {
v[i] = i;
}
r.setValues(v); //ERROR: Cannot covert Array to Object[][]`enter code here`
}
It looks like the line v[i] = i; converts the Object[][] to an array. So , i think (bizarre) I need to create a new array[][] asfollows:
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var r = s.getRange(1,1,5,1);
var v = r.getValues();
var ta = [];
for ( var i=0; i < 5; i++) {
ta[i] = [];
ta[i].push(i) ;
}
r.setValues(ta);
}

Ok. Here is the full solution.
The function looks for the sheet "Work" that has 2 columns; the first is a checkbox, the second is the string value of interest. For every checked box (value == true), the 2nd column's value, Font weight, and Font size are copied into appropriately 'shaped' structures.
Once constructed, a new sheet is created, a range in the new sheet is retrieved and used to set the values, weights and sizes of a single column.
function copyCheckedItems () {
var cl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Work');
if (cl) {
var cnt = cl.getLastRow();
var range = cl.getRange(1,1, cnt, 2 );
var values = range.getValues();
var weights = range.getFontWeights();
var sizes = range.getFontSizes();
// Compute data needed for new sheet in the right shape.
var tv = [];
var tw = [];
var ts = [];
var newCnt = 0;
for (var row in values) {
if(values[row][0]){
tv[newCnt] = [];
ts[newCnt] = [];
tw[newCnt] = [];
tv[newCnt].push(values[row][1]);
tw[newCnt].push(weights[row][1]);
ts[newCnt].push(sizes[row][1]);
newCnt++;
}
}
// construct the new sheet in a minimum of calls
var name = Browser.inputBox('Enter WorkSteet name');;
var sheetOut = SpreadsheetApp.getActiveSpreadsheet().insertSheet(name);
var ro = sheetOut.getRange(1,1,newCnt,1);
ro.setValues(tv);
ro.setFontSizes(ts);
ro.setFontWeights(tw);
//Browser.msgBox("Done.");
}
else {
Browser.msgBox('Work sheet not found!');
}
}

Related

Displaying array data in Google sheets vertically

This is probably an easy fix but I'm not sure why the array of matching emails are being outputted horizontally and not vertically in Google Sheets. I want all the emails to be in a specific column so the must be outputted vertically with each email being in an individual cell. I tried using the split method to separate the array into individual cells but only the first email is displayed.
function getTheDesiredContacts() {
var SS =SpreadsheetApp.getActiveSpreadsheet();
var EmailSheet = SS.getSheetByName("Threads");
var Contacts = ContactsApp.getContactGroup("Employees").getContacts();
var EmailsArray = [];
var label = GmailApp.getUserLabelByName(LABEL);
var MaxColumnCount = 30;
var threads = label.getThreads();
for(var i = 0, n = Contacts.length, x = 0; i < n; i++){
if(i < MaxColumnCount){
EmailsArray.push([]);
}
EmailsArray[x] = Contacts[i].getEmailAddresses()[0];
x++;
}
if (threads.length > 0) {
for (var t=threads.length-1; t>=0; t-- ){
var labels = threads[t].getLabels();
}
for (var i in labels) {
var z= labels[i].getName();
}
if(z==LABEL){
var EMails = GmailApp.getMessagesForThreads(
GmailApp.search("label:" + labels[i].getName())).reduce(function(a, b){
return a.concat(b);}).map(function(EMails){
return EMails.getFrom();
});
var AEmail = EMails.sort().filter(function(el,j,a){
if(j==a.indexOf(el))
return 1;return 0
});
var deDup = new Array();
for(var k in AEmail) {
var str = AEmail[k].split("<").pop();
str=str.replace(">",'');
deDup.push(str);
}
}
}
var matchingEM = new Array();
for(var m in EmailsArray){
for(var o in deDup){
if(EmailsArray[m]==deDup[o]){
matchingEM.push(deDup);
}
}
}
var unq = matchingEM.filter((item, i, ar) => ar.indexOf(item) === i);
var unique = unq.join();
var uniqueOff = unique.split(',');
var uniqueemails= new Array;
uniqueemails.push([unique]);
Logger.log("These are the matching emails: " + uniqueemails);
EmailSheet.getRange(2,4, uniqueemails.length,1).setValues([uniqueemails]);
return uniqueOff
}
Modification points:
When I saw your script, it seems that uniqueemails of EmailSheet.getRange(2,4, uniqueemails.length,1).setValues([uniqueemails]); is [["value1,value2,,,"]] that value1,value2,,, is a string value. In this case, the values are put to a cell. This is due to var unique = unq.join();. In order to put the values to the row direction, the array is required to be like [["value1"],["value2"],,,].
For this, how about the following modification?
Modified script:
Please modify your script as follows.
From:
var unq = matchingEM.filter((item, i, ar) => ar.indexOf(item) === i);
var unique = unq.join();
var uniqueOff = unique.split(',');
var uniqueemails= new Array;
uniqueemails.push([unique]);
Logger.log("These are the matching emails: " + uniqueemails);
EmailSheet.getRange(2,4, uniqueemails.length,1).setValues([uniqueemails]);
To:
var uniqueemails = matchingEM.filter((item, i, ar) => ar.indexOf(item) === i)[0].map(e => [e]);
EmailSheet.getRange(2, 4, uniqueemails.length, 1).setValues(uniqueemails);
In above modified script, the values are put from the cell "D2" to the row direction. This is from your script. If you want to put the values to other rows, please modify above script.
Note:
In this modified script, it supposes that your script currently works with no errors. Please be careful this.
References:
map()
setValues(values)

Optimizing .gs search & replace script?

Hoping someone could just point out my probably obvious mistakes.
Code searches a Data page & copies matched info to a Results page. This is fine & works quickly.
What happens is when I add an overwrite section to the script. It overwrites matched entries as "Done" on the data page, so if the Data page gets added to, the previous entries won't match again.
I have 2 variants I've tried that work but are quite slow...as in you can sit & watch each match change to "Done" once every few seconds.
Appreciate any insight.
Here's the code:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var data = sheet1.getRange(2,1,lr-1,2).getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
// Slow Overwrite-----------------------------------
/*
if(i>0){
var temp1 = sheet1.getRange(i,2).getValue();
var temp2 = sheet1.getRange(i+1,2).getValue();
var temp3 = sheet1.getRange(i+2,2).getValue();
// Testing1
if(temp1 == key ){
sheet1.getRange(i,2).setValue("Done");
}
if (i== lr-2){
if (temp2 == key){
sheet1.getRange(i+1,2).setValue("Done");
}
if (temp3 == key){
sheet1.getRange(i+2,2).setValue("Done");
}
}
}
*/
}
// Shorter code but even slower overwrite----------------
/*
for(var i=1; i<=lr; i++){
var temp1 = sheet1.getRange(i,2).getValue();
if(temp1 == key){
sheet1.getRange(i,2).setValue("Done");
}
}
*/
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Link to the Test Sheet
Solution:
The script runs slow because there are API calls in for loops. Best practice is to manipulate only the array inside the loop and do the API calls before and after the loop.
In your code it would look like this:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var datarange = sheet1.getRange(2,1,lr-1,2);
var data = datarange.getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
data[i][1] = "Done";
}
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
datarange.setValues(data);
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Note that the for loop does not have any getValue()/setValue() methods.
This has the same effect on your sample sheet, only way faster.

Get data from array in nested json using Google Script

I need to fix a Google Script I've been working on. Basically, I have a json https://www.instagram.com/nike/?__a=1 that returns basic info about Nike's account on instagram. I have no problem retrieving data from objects such as "biography". But, when I try to retrieve nested objects (arrays) I'm doing something wrong because the results arrive duplicated (see attachment). Can anyone help me figure out what I'm doing wrong?
// the name of the sheet within your document
var sheetName = "Sheet1";
// the name of the Instagram account you want the follower count for
var instagramAccountName = "nike";
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(this.sheetName);
var followers = [];
var followers = captionArray(this.instagramAccountName);
for(var i = 0 ; i < 3; i++) {
sheet.appendRow([Utilities.formatDate(new Date(), "GMT", "yyyy-MM-dd"), followers]);
};
}
function captionArray(username) {
var i = 0;
for(var i = 0; i < 3; i++) {
var url = "https://www.instagram.com/" + username + "/?__a=1";
var response = UrlFetchApp.fetch(url).getContentText();
var caption = [];
var caption = JSON.parse(response).graphql.user.edge_owner_to_timeline_media.edges[i].node.edge_media_to_caption.edges[i].node.text;
return caption;
};
}
I think this is causing problems:
You're using the same index (i) for both arrays, but the second have only one element.
You just need to do one request.
This code works for me:
function captionArray(username) {
var captions = [];
var url = "https://www.instagram.com/nike/?__a=1";
var response = UrlFetchApp.fetch(url).getContentText();
var edges = JSON.parse(response).graphql.user.edge_owner_to_timeline_media.edges;
for(var i = 0, limit = edges.length; i < limit; i++) {
captions.push(edges[i].node.edge_media_to_caption.edges[0].node.text);
}
return captions;
}

Google script - Exceeded maximum execution time , help optimize

google script spreadsheet
Novice
I try to create a matrix , if the array is a small database everything works fine, of course if it exceeds 800 lines and more rests on the error "You have exceeded the maximum allowed run time ." Not effectively create a matrix :
var s = SpreadsheetApp.getActiveSheet(); //List
var toAddArray = []; //Greate Arr
for (i = 1; i <= s.getLastRow()+1; ++i){ //Start getting Value
var numbr = s.getRange(i,4); //detect range
var Valus = numbr.getValues().toString(); //get value
//filter value
var newznach = Valus.replace(/\-/g, "").replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
toAddArray.push([i.toFixed(0),Valus,newznach]); //add to array 0- Row numb, 1- Value, 2- "filtered" value
}
toAddArray =
{
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
...
}
Can I somehow get an array of the same the other way ( faster, easier ) ?
You're doing a call to getValues every row, that eats a lot of performance.
It is better to do one big call to have all the data and then go through it sequentially.
var s = SpreadsheetApp.getActiveSheet();
var data = s.getRange(1,4, s.getLastRow()).getValues();
var toAddArray = data.map(function(row, i) {
var Valus = row[0].toString();
var newznach = Valus.
replace(/\-/g, "").
replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").
replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
return [i.toFixed(0), Valus, newznach];
});
this code:
var Valus = numbr.getValues().toString();
slows you down because you read data from the sheet in a loop.
Try reading data once into array and then work with it:
var data = s.getDataRange().getValues();
And then work with data, in a loop. This sample code log each cell in active sheet:
function logEachCell() {
var s = SpreadsheetApp.getActiveSheet();
var data = s.getDataRange().getValues();
// loop each cell
var row = [];
for (var i = 0; i < data.length; i++) {
row = data[i];
for (var j = 0; j < row.length; j++) {
Logger.log(row[j])
}
}
}

Storing rows from FlexTable

I have a FlexTable that contains checkBoxes in the all cells of first column and other data in the other cells. I need to store FlexTable's row when checkBox is true to subsequently put it in document with DocumentApp.create('Doc').getBody().appendTable(storedRows), and I have no idea how to realise this function.
Maybe it impossible when using FlexTable?
Anyway thankyou in advance.
if you need to read the value of a checkBox this checkBox has to have a unique name so you can get it using e.parameter.widgetName. What I usueally do when building in a loop is to generate a name in which I include the row number so I have a direct relation with data.
I rewrote your code with this modification and a few other ones.... please have a look and tell us if you need more details.
// Global variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange().getValues();
var app = UiApp.getActiveApplication();
// Menu additions
function onOpen() {
var menuEntries = [{name: 'Поиск вакансий', functionName: 'ui'}];
ss.addMenu('Отчеты',menuEntries);
}
// UI
function ui() {
var app = UiApp.createApplication().setHeight(400).setWidth(800).setTitle('Поиск вакансий');
var panel = app.add(app.createHorizontalPanel());
var input = panel.createTextBox().setName('input').setId('input').setFocus(true);
var button = panel.createButton('Сформировать документ');
panel.add(input).add(button);
var handler = app.createServerHandler('search');
input.addChangeHandler(handler);
handler.addCallbackElement(input);
var click = app.createServerHandler('click');
button.addClickHandler(click);
click.addCallbackElement(button);// this is useless, the button has no data to transmit ?
var table = app.createFlexTable().setId('table').setWidth(785).setBorderWidth(1).setCellPadding(1)
var tpanel = app.createScrollPanel(table).setPixelSize(800, 383).setAlwaysShowScrollBars(true);
table
.setWidget(0,0,app.createCheckBox().setValue(true).setEnabled(false))
.setWidget(0,1,app.createLabel('Вакансия'))
.setWidget(0,2,app.createLabel('График'))
.setWidget(0,3,app.createLabel('Время'))
.setWidget(0,4,app.createLabel('Условия'))
.setWidget(0,5,app.createLabel('Зарплата'))
.setWidget(0,6,app.createLabel('Оплата'))
.setWidget(0,7,app.createLabel('Организация'))
.setWidget(0,8,app.createLabel('Телефон'));
app.add(tpanel);
ss.show(app);
}
// Search
function search(e) {
var table = app.getElementById('table');
var query = e.parameter.input.toLowerCase();
var hidden = app.createHidden().setId('hidden').setName('hidden');
app.add(hidden);// set a name too but I don't use it right now
var check = app.createServerHandler('check');
check.addCallbackElement(table);
var r = 1;
for (var row = 0; row < data.length; row++) {
if(data[row].toString().toLowerCase().match(query) == query && query!= ''){ // you don't need to check each cell, you can use match on the stringified row instead.
table.setWidget(r,0,app.createCheckBox().addValueChangeHandler(check).setName('check'+row));// create the checkBow once every row if condition is true and give it a name
for (var c = 1; c < data[row].length; ++c) {
table.setText(r,c,data[row][c].toString());
}
++r
continue;
}
}
return app;
}
// Storing checked rows
function check(e) {
var checkedArray = [];
for(var n=0; n < data.length;++n){
Logger.log('check'+n+' = '+e.parameter['check'+n]);// shows also 'undefined' for items not found by input query, that's normal
if(e.parameter['check'+n]=='true'){
checkedArray.push(data[n]);
}
}
Logger.log('checkedArray = '+checkedArray);// see results
}
function click(e) {
// no action right now
}