Search spreadsheet and get rows - google-apps-script

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;
}

Related

Trying to use onEdit to autofill timeline of a Gantt Chart

I'm trying to create an apps script to autofill a gantt chart when the sheet is edited, but having trouble.
Here is a link to the spreadsheet if it helps.
function ganttChart()
{
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ganttSheet = ss.getSheetByName("Gantt Chart");
var headerRow = ss.ganttSheet.getRange('headerRow').getRow();
var lastRow = ss.ganttSheet.getLastRow();
var lastCol = ss.ganttSheet.getLastColumn();
var firstTask = headerRow + 1
var taskRoleCol = ss.ganttSheet.getRange('taskRole').getColumn();
//I'm not sure if I need to do the below RoleCol if I already have a named range -- this will return an integer which is the column #
var roleCol = ss.getSheetByName("Roles").getRange('Roles').getColumn();
var taskCol = ss.ganttSheet.getRange('taskNames').getColumn();
var startWeekRow = ss.ganttSheet.getRange('startWeek').getRow();
var expDurationCol = ss.ganttSheet.getRange('expDuration').getColumn();
//set the requirements for the edit trigger -- not sure what these would be
//if (e.range)
//{
for (var i = firstTask; i < lastRow; i++)
{
var currentTask = ss.ganttSheet.getRange(i, taskCol).getValue();
var currentStartWeek = ss.ganttSheet.getRange(i, startWeekCol).getValue();
var currentTaskExpDuration = ss.ganttSheet.getRange(i,expDurationCol).getValue();
var currentTaskRole = ss.ganttSheet.getRange(i,taskRoleCol).getValue();
if (currentTask != null)
{
if (currentStartWeek != null)
{
//for loop to identify the column that matches the start week #
for (var j = 0; j < lastCol; j++)
{
var checkWeek = ss.ganttSheet.getRange(startWeekRow, j).getValue();
if (checkWeek == currentStartWeek)
{
//identify the range
var taskTimeRange = ss.ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1);
//for loop get the background color based on role
for (var k = 0; k < lastRow; k++)
{
var checkRole = ss.ganttSheet.getRange(k, roleCol).getValue();
//if role value matches the currentTaskRole
if (checkRole == currentTaskRole)
{
var roleColor = ss.ganttSheet.getRange(k, roleCol).getBackground();
//reformat the range based on duration
taskTimeRange.setBackground(roleColor);
}
}
}
}
}
}
}
//}
}
I took off the "onEdit" to try and get the program to work on run, but I'm still getting a "cannot read properties of undefined" error.
What should happen is:
when a user edits the "Gantt Chart" sheet
the program changes the background color in the corresponding range to indicate the weeks a task is being worked on, based on the start week and calculated duration
the background color should correspond to the task role, based on the colors set in the "Roles" sheet
If the above isn't clear, here is a link to a video where I try to explain what the program should do
The question relates to onEdit but the trigger is, at this point of development of the script, irrelevant, since the script is littered with syntax errors.
Even so, I suggest that the trigger is irrelevant in any event. The script can/should be triggered when the data has been populated. This trigger can be done manually, or (perhaps) via a menu option.
getRange(), getLastRow() and getLastColumn are sheet-based methods.
Incorrect
var headerRow = ss.ganttSheet.getRange('headerRow').getRow()
var lastRow = ss.ganttSheet.getLastRow()
var lastCol = ss.ganttSheet.getLastColumn()
Correct
var headerRow = ganttSheet.getRange('headerRow').getRow()
var lastRow = ganttSheet.getLastRow()
var lastCol = ganttSheet.getLastColumn()
Other
startWeekCol is not defined
for (var j = 0; j < lastCol; j++)
"j" substitutes for the column number, but a value of 0 is invalid
var taskTimeRange = ganttSheet.getRange(i,j - 1,(currentTaskExpDuration*2 +1), 1)
when "j" is 1, "j-1" resolves to 0 (zero) which is invalid
for (var k = 0; k < lastRow; k++)
"k" substitutes for the row number, but a value of 0 is invalid

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

add either check box, list box or radio button in one of the cells in each row in a flex table

How to add either check box, list box or radio button in one of the cells in each row in a flex table. I am using apps script. i tried following but it only displays the list box in one cell in last row:
function doGet(){
var app = UiApp.createApplication();
var panel = app.createVerticalPanel();
var listBox = app.createListBox();
listBox.addItem("Yes").addItem("No").setName("myListBox");
var table = app.createFlexTable().setId("myTable");
table.setBorderWidth(1)
table.setCellPadding(1);
//Get Data from spreadsheet
var doc = SpreadsheetApp.openById('Spreadsheet ID');
var spreadsheetId = 'Spreadsheet ID';
var dataArray = getData(spreadsheetId);
for (var row = 0; row<dataArray.length; row++){
for (var col = 0; col<dataArray[row].length; col++){
if( col == 1){
table.setWidget(row, col, listBox);
}
table.setText(row, col, dataArray[row][col].toString());
}
}
app.add(table);
return app;
}
function getData(spreadsheetId){
var ss = SpreadsheetApp.openById(spreadsheetId);
var sheet = ss.getSheets()[0].getDataRange();
return sheet.getValues();
}
You simply forgot to use an "else" in the condition, without "else" the text overwrites the other widgets.
for (var row = 0; row<dataArray.length; row++){
for (var col = 0; col<dataArray[row].length; col++){
if( col == 1){
table.setWidget(row, col, listBox);
}else{
table.setText(row, col, dataArray[row][col].toString());
}
}
}
EDIT : I forgot another point, even more important : you can't use the same listBox multiple times, you should define a new listBox widget each time you add one to the flexTable... so place the app.createListBox in the if condition in the loop and think about giving each of them a different name and Id.

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
}

I need to get the input value (True/False) of a checkbox when a button is pressed

I want to get the input value (true/false) of a checkbox with .setName(chk_id), when id is the id of the row which i have introduced in flexTable cell bycell.
But i always get "e.parameter_"undefined"" becouse e.parameter.chk_i is "undefined"
var flexTableReg = app.createFlexTable().setId("flexTableReg").setBorderWidth(1);
var check = new Array(lastRow);
for(var r = 0; r < lastRow; r++){
for(var c = 0; c < lastCol; c++){
var text = rowsToConfirm[r][c].toString();//HERE IS THE CELL
flexTableReg.setText(r+1, c, text);
}
var id = rowsToConfirm[r][0];
check[r] = app.createCheckBox().setName("chk_"+id).setId("chk_"+id);
flexTableReg.setWidget(r+1, lastCol, check[r]);
}//end fors
}//end if
var botMod = app.createButton().setText("Activar").setId("botStatus");
var botHandle = app.createServerHandler("changeStatus").addCallbackElement(verticalPanelAdmin);
botMod.addClickHandler(botHandle);
mainPanelAdmin.add(flexTableReg);
mainPanelAdmin.add(botMod);
verticalPanelAdmin.add(mainPanelAdmin);
absolutePanelAdmin.add(verticalPanelAdmin);
app.add(absolutePanelAdmin);
return app;
}
function changeStatus(e){
var ss = SpreadsheetApp.openById(LOGS_SHEET_ID);
var app = UiApp.getActiveApplication();
var datarray = getDataArray();
var lastRow = ss.getLastRow();
for(var i =0; i<=lastRow;i++ ){
try{
var par = e.parameter["chk_"+i];
if(true){
stop_if_true
var row = getRowByID(0,lastRow,i);
ss.getRange("E"+i).setValue("1");
}//end if
}catch(e){e.parameter_"undefined" //if e.parameter "undefined"
return app;}
}//end for
return app;
};
I don't know what var id = rowsToConfirm[r][0]; is returning, but that is what you're using as the original id parameter when setting the .setName() and .setId() properties.
When you try to get the values in the callback function you use a numeric i param so that could clash.
Second, the return value of a checkbox is always a string so check for 'true' or 'false' instead of true or false.
hope this helps,
Greets.