Storing rows from FlexTable - google-apps-script

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
}

Related

Google forms duplicates sections when running Google Apps Script multiple times

I've got a code in Google Apps Script to automatically populate a Google Form. It works perfectly, but there's one big problem. When I run the script multiple times, the Forms copies itself. So, instead of overwriting the previous run, it adds a whole new (identical) Form. For example: after running the code one time, it shows 4 sections. But when I run it two times, it shows 8 sections, and so on.
Does anybody know how to solve this?
Here's the used code:
function rosterMaker() {
//spreadsheet id of the rosters
var SHEET_ID = FormApp.getActiveForm().getDestinationId();
var ss = SpreadsheetApp.openById(SHEET_ID)
var form = FormApp.getActiveForm();
var sheets = ss.getSheets().filter(function(sheet) {return sheet.getName().match(/Roster/gi);});
//add multiple choice item
var classSelect = form.addMultipleChoiceItem();
classSelect.setTitle('Choose a class');
var classChoices = [];
for(var i = 0; i < sheets.length; i++) {
var className = sheets[i].getName();
var classSection = form.addPageBreakItem()
.setTitle(className)
.setGoToPage(FormApp.PageNavigationType.SUBMIT);
var students = getStudents(sheets[i]);
var studentSelect = form.addCheckboxItem()
.setTitle(className + 'absent')
.setHelpText('Select the students who are absent from this class');
var studentChoices = [];
for(var j = 0; j < students.length; j++) {
studentChoices.push(studentSelect.createChoice(students[j]));
}
studentSelect.setChoices(studentChoices);
classChoices.push(classSelect.createChoice(className, classSection));
}
classSelect.setChoices(classChoices);
}
function getStudents(sheet) {
var studentValues = sheet.getDataRange().getValues();
var students = [];
for(var i = 1; i < studentValues.length; i++) {
students.push(studentValues[i].join(' '));
}
return students;
}
What you need to do is remove all the items you populated in your form before populating it again. Best place to add it on should be before adding anything to the form.
Based on your script, you can add it after var form = FormApp.getActiveForm();
See my sample code below on how it works.
var formID = <FORM_ID>;
var fData = FormApp.openById(formID);
function clearForm(){
// function that clears all items in your form
var items = fData.getItems();
// always delete while there is remaining item
while(items.length > 0){
fData.deleteItem(items.pop());
}
}
function populateForm() {
// call clearForm to prevent duplicating populated questions
clearForm();
// section to populate your form
// ....
}
If you have any issues/problems on making the code work, feel free to comment below.

Split a Google Sheet into multiple tabs based on column values

I used a previous answer (thanks kessy!) to split 7000 or so rows into 40 or so different tabs based upon values in a column. I ran the same script on another nearly identical file and I get the error "TypeError: Cannot read property 'getRange' of null (line 5, file "Code")". I tried with a greatly simplified file and get the same error. Any help getting this to work is very much appreciated.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("No Room");
} else {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
var currentSheet = SpreadsheetApp.getActiveSpreadsheet()
currentSheet.appendRow(rows[i]);
}
}
}
I am not sure what is exactly your goal, but based on the error message you are getting it seems that you are not getting the active sheet properly. Instead, I would suggest you to specify the sheet by its name. Let's assume the desired name of the sheet you want to get is Sheet1. Then, in the first line of your function you can replace this:
var sheet = SpreadsheetApp.getActiveSheet();
with this:
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
I also optimized your code a little by removing all the unnecessary SpreadsheetApp.getActiveSpreadsheet() calls:
function myFunction() {
var ss = SpreadsheetApp.openById("SpreadsheetId");
var sheet = ss.getSheetByName('Sheet1');
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C"+sheet.getLastRow()).getValues();
// This var will contain all the rows
var rows = sheet.getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = ss.insertSheet("No Room");
} else {
var currentSheet = ss.insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
sheet.appendRow(rows[i]);
}
}
}
You can also run a loop within the loop and keep things server side for a faster result (at least it worked for me, I was having trouble with long spreadsheets timing out).
You have to know how many columns you want to pass over, maybe there is a better way to push the values than I have done (I only dabble in script).
function splitSheets() {
var theWorkbook = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = theWorkbook.getSheetByName("Master");
//Let's delete any sheets that were previously split, so we can rerun the script again and again
var sheets = theWorkbook.getSheets();
for (i = 0; i < sheets.length; i++) {
switch(sheets[i].getSheetName()) {
case "Master":
break;
default:
theWorkbook.deleteSheet(sheets[i]);
}
}
// This var will contain all the values from column C -> Your splitting Key
var key = theSheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = theSheet.getDataRange().getValues();
//Set the first row as the header, get the range so we can keep the formatting
var headerFormat = theSheet.getRange("2:2");
//Store the rooms already created
var completedSheets = [];
//We start at i=2 because we're on row 3, row zero for the button, row one for the header
for (var i = 2; i < key.length; i++) {
//We don't want to run the loop if we've already created the blank page and the row key is also blank.
if(completedSheets.includes('Blank') && key[i][0] === ""){
//do nothing
}else{
//Check if the room is already done, if not go in and create the sheet
if(!completedSheets.includes(key[i][0]) ) {
//Set the Sheet name = unique key (except if there is no name, then = Blank)
if (key[i][0] === "") {
var currentSheet = theWorkbook.insertSheet("Blank");
} else {
var currentSheet = theWorkbook.insertSheet(key[i][0]);
}
//To avoid pasting formulas, we have to paste contents, copying allows us to keep formatting
headerFormat.copyTo(currentSheet.getRange(1,1),{contentsOnly:true});
headerFormat.copyTo(currentSheet.getRange(1,1),{formatOnly:true});
//Now here find all the rows containing the same key address and push them, this way doing it server side
var theNewRows =[];
var b=0;
for(var j = 1; j < rows.length; j++) {
if((rows[j][2] == key[i][0]) || (rows[j][2] === '' && currentSheet.getName() == "Blank")){
theNewRows[b]=[];//Initial new array
theNewRows[b].push(rows[j][0],rows[j][1],rows[j][2],rows[j][3],rows[j][4],rows[j][5],rows[j][6],rows[j][7],rows[j][8]);
b++;
}
}
var outrng = currentSheet.getRange(2,1,theNewRows.length,9);//Make the output range the same size as the output array
outrng.setValues(theNewRows);
//The new sheet name gets added to the completed sheets list and the value of var last is updated in prep of next step
if(currentSheet.getSheetName() == 'Blank') {
completedSheets.push('Blank');
last = "Blank";
}else{
completedSheets.push(key[i][0])
last = key[i][0]
}
}
}
}
//And return to the Master
SpreadsheetApp.setActiveSheet(theWorkbook.getSheetByName('Master'));
}
Example here, just click the button on the page
https://docs.google.com/spreadsheets/d/1pfeU2CFDbZbA4O0b4z80l5MyCKDNQnUdkpKlzODbAiI/edit?usp=sharing
It's not perfect, but hope it helps.

Dynamic Editing of Form Dropdown Creates Extra Column

I'm relatively new to Google Scripts for Sheets/Forms, and have edited this answer to create a script that updates the options on a dropdown question whenever it is triggered. It works wonderfully, but with one small glitch. My code as below:
var idColumn = 1;
function getSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ProjectList");
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var lr = rows.getLastRow();
var docId = sheet.getRange(2,idColumn,lr,1).getValues(); //gets the data from the last row in selected column
fillFormData(docId);
}
//fills form with document ID
function fillFormData(docId) {
var formId = 'form ID goes here'
var form = FormApp.openById(formId);
var items = form.getItems();
var item = items[1];
//loop variables
var thisValue = "";
var arrayOfItems = [];
var newItem = "";
for (var i=0;i<docId.length;i++) {
thisValue =docId[i][0];
Logger.log('thisValue: ' + thisValue);
newItem = item.createChoice(thisValue);
arrayOfItems.push(newItem);
};
item.setChoices(arrayOfItems)
}
My hope was that, by updating the question using setChoices(), responses would be placed in the same column in the response sheet. Instead, each trigger of this script creates another column in the response sheet named "Project Number", overwriting one of the columns that were there before. Is this an expected behaviour, or does anyone know of a way to ensure that my responses go into the same column? Is there a way to direct responses to this question to a specific column in the response sheet?
Edit: As per the suggestions below, I've tried adjusting the macro to use .asListItems() with no success. The interesting behaviour here is that the new, duplicate column seems to overwrite one of my other columns rather than create a new one.
Here's my ProjectList, just a single column:
Project Number
Project A
Project B
Project C
Project D
Project E
The form consists of 35 questions, of a variety of types, split among 3 sections.
I think you need to explicitly set the question you're trying to add the choices to, please see below example of working script to add new items to form.
function getSheet() {
var sheet = SpreadsheetApp.openById('sheet id here');
}
var formId = "form id here";
var question = [{formFieldTitle:"question title here", worksheetName:"linked form sheet name here"}]
function newChoices(item, sheetName) {
var data = (SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(sheetName)
.getDataRange()
.getValues());
var choices = [];
for (var i = 1; i < data.length; i += 1){
choices.push(item.createChoice(data[i][0]));
}
item.setChoices(choices);
}
function addNewChoices() {
var form = FormApp.openById(formId);
var items = form.getItems();
for (var i = 0; i < items.length; i += 1) {
for (var j = 0; j < question.length; j += 1) {
var item = items[i];
if (item.getTitle() === question[j].formFieldTitle) {
addNewChoices(item.asCheckboxItem(), question[j].worksheetName);
break;
}
}
}
}
Variable "question" should have the question header and the sheet name from which your form items are being added.
var question = [{formFieldTitle:"question title here", worksheetName:"sheet name here"}]
Also, make sure to change "asCheckboxItem" if your question is not a checkbox in the line below, see Interface Item documentation to determine what to use.
addNewChoices(item.asCheckboxItem(), question[j].worksheetName);
Instead of using method createChoice() and setChoices(), you should use setChoiceValues() so it doesn't create a new question. By using setChoiceValues(), you are effectively updating the choices.
See the code below:
function optWriteItem_CHECKBOX_(paramItem, paramItemData) {
try {
var thisItem = paramItem.asCheckboxItem();
var listChoices;
var n, i;
n = paramItemData.length;
if(paramItemData.indexOf('__OTHER__') > 5) {
listChoices = paramItemData.slice(5, n-1);
thisItem.showOtherOption(true);
} else {
listChoices = paramItemData.slice(5, n);
thisItem.showOtherOption(false);
}
thisItem.setTitle(paramItemData[0]);
if(paramItemData[3].toLowerCase() == 'yes') thisItem.setRequired(true);
else thisItem.setRequired(false);
thisItem.setHelpText(paramItemData[4]);
if(listChoices.length == 0) listChoices = [ 'Option 1' ];
thisItem.setChoiceValues(listChoices);
} catch(err) {
Logger.log('optWriteAsType/opt=CHECKBOX : ' + err.message);
console.error("optWriteItem_CHECKBOX_()", err);
return {s:false, m:err.message};
}
return {s:true};
}
Source-code

Optimal way to set cell values in Google Sheet via 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!');
}
}

Search spreadsheet and get rows

I'm creating an app that can help find rows wich contains search query and show result in Ui. I have write this code, but something might be wrong and it don't works:
// Global variables
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lastrow = ss.getLastRow();
var lastcol = ss.getLastColumn();
// Menu additions
function onOpen() {
var menuEntries = [{name: 'search', functionName: 'ui'}];
ss.addMenu('Ui',menuEntries);
}
// UI
function ui() {
var app = UiApp.createApplication().setHeight('400').setWidth('900').setTitle('UI');
var panel = app.add(app.createHorizontalPanel());
var input = panel.createTextBox().setId('input').setFocus(true);
panel.add(input).add(printBtn).add(saveBtn).add(emailBtn);
var table = app.createFlexTable().setId('table').setBorderWidth(1).setCellPadding(1);
var handler = app.createServerHandler('search');
input.addChangeHandler(handler);
handler.addCallbackElement(table);
app.add(table);
ss.show(app);
}
function search(e) {
var app = UiApp.getActiveApplication();
var table = app.getElementById('table');
var query = e.toLowerCase();
var data = sh.getRange(2,1,lastrow,lastcol).getValues();
for (var row = 1; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if(data[row][col].toString().toLowerCase().match(e.toString()) == e.toString() && e != '');
table.setText(row, col, data[row][col].toString());
}
}
return app;
}
Debugger says object e from function's search is undefined. Also I'm absolutely noob in js and simply don't know how to give this e parameter to function.
Thank you in advance.
e is an object with multiple properties, as for any js object you should tell what property of this object you want to read. In this code the value you want is the value of the textBox in which you enter a text value so there are 2 things you have to do :
give a name to this textBox ( .setName('input')) so that you can use this very same name as an object property in the form var inputValue = e.parameter.input
for this to work the textBox itself must also be part of the callbackElement of the handler. You used your table as callbackElement but the query textBox (input) is not a child of this table so you can either add the textBox to the table some way or, probably simpler in this case, add the textBox as a second callbackelement like this :
handler.addCallbackElement(table).addCallbackElement(input);
you could also have a look at this simple example that does approximately what you want.
edit : possible handler function
:
function search(e) {
var app = UiApp.getActiveApplication();
var table = app.getElementById('table');
var query = e.parameter.input.toLowerCase();
var tableRow = 0 ;
var data = sh.getRange(2,1,lastrow,lastcol).getValues();
for (var row = 1; row < data.length; row++) {
for (var col = 0; col < data[row].length; col++) {
if(data[row][col].toString().toLowerCase().match(query) == query && query!= ''){
for(c=0 ; c<data[row].length;++c){
table.setText(tableRow, c, data[row][c].toString());
}
++tableRow
continue;
}
}
}
return app;
}