Cannot get the column specific timestamp to display on checkbox - checkbox

I have set up a Google sheet for attendance of Employees on which clicking on the checkbox, the current time is recorded.
I cannot get the specific timestamp in the respective columns when the checkbox is ticked.
Clicking on a different checkbox also change the timestamp for previously checked checkboxes.
I have used this formula here : =If(B2 = TRUE,now(),"")
and used filter handle to apply the formula for other rows underneath.
I have used this formula: =If(B2 = TRUE,now(),"")
The sheet can be seen here: https://docs.google.com/spreadsheets/d/1p6jmnHXtCu2m7BdLfC-23A51MrwkJJJpXidpbsDwxfU/edit?usp=sharing
I want to record the current timestamp, for each row.
i.e on clicking of the checkbox the current time should be changed/recorded only for the column in the same row and not for other records.

this needs to be done with the script:
function onEdit(e) {
var s = SpreadsheetApp.getActiveSheet();
{
var r = s.getActiveCell();
if( r.getColumn() == 2 ) {
var nextCell = r.offset(0, 1);
var newDate = Utilities.formatDate(new Date(),
"GMT+1", "dd/MM/yyyy hh:mm:ss");
nextCell.setValue(newDate);
}
}
how to add a script to your spreadsheet
go to Tools
select Script editor
copy paste the script
save the project under some name
click on run icon and authorise it...
select your account
click on Advanced
select Go to * (unsafe)
click on Allow and return to your sheet (you can close script window/tab)

Related

Need to populate data from spreadsheet to form that has multiple section

I'm using a similar code to get data from sheet and populate google form.
Only data shown in table below (from sheet) must appear in google Form (excluding empty space). The data will always get update (increase or decrease). The table of data indicates if laptop/desktop quantity is zero, then it won't appear in the table.
THE CODE USED
function getDataFromGoogleSheets() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data viewed in form");
const [header, ...data] = sheet.getDataRange() .getDisplayValues();
const choices = {}
header.forEach(function(title, index) {
choices[title] = data.map(row => row[index]).filter(e => e !== "");
});
return choices;
}
function populateGoogleForms() {
const GOOGLE_FORM_ID = "1MkEtrmPSYsVH_L7-fsos2SWAE4hzLUQSd42GzG14zWc";
const googleForm = FormApp.openById(GOOGLE_FORM_ID);
const items = googleForm.getItems();
const choices = getDataFromGoogleSheets();
items.forEach(function(item) {
const itemTitle = item.getTitle();
if (itemTitle in choices) {
const itemType = item.getType();
switch (itemType) {
case FormApp.ItemType.LIST:
item.asListItem().setChoiceValues(choices[itemTitle]);
break;
default:
Logger.log("Google Form Updated",itemTitle);
}
}
})
}
The FORM workflow:
Buyer must choose either 'laptop OR desktop' in page 1(section 1) DROPDOWN LIST.
if buyer choose laptop, page will redirect to section 2 (DROPDOWN LIST). After choosing page will show submit button. The end of process.
Same goes when buyer choose desktop, page will redirect to section 3, then submit form
In form 'laptop units' is in Section 2 and 'desktop units' are in Section 3.
GOOGLE SPREADSHEET
enter image description here
GOOGLE FORM
SECTION 1
SECTION 2 - Laptop Units
After Section 2 Submit Form (same goes to section 3
SECTION 3
Issue:
A trigger is used for populateGoogleForm function, it works only when data in inserted in Google Sheet (means will appear in FORM). But when quantity of items becomes zero, automatically item name won't appear in table(this works fine in SHEET). AND that updated table do not get updated in Form. (still the Form will have item name even when the item is zero).
The code doesn't update both section, only can be used if Form consist of only single section.
**Question:
To insert page break and separate both sections so both sections will get updated from Google Sheet.
Use trigger to auto-update Form **
**Question:
To insert page break and separate both sections so both sections will get updated from Google Sheet.
Use trigger to auto-update Form **

How to use a parameter which a user can update in a calculated field in getFields (Data Studio Community Connector)

I have a single select parameter that I define in a data studio community connector in getConfig() as below. The parameter is then used as a dropdown data control in the report.
config
.newSelectSingle()
.setId("characteristic_selected")
.setName("Characteristic selected")
.addOption(
config
.newOptionBuilder()
.setLabel("a")
.setValue("a")
)
.addOption(
config
.newOptionBuilder()
.setLabel("b")
.setValue("b")
)
.addOption(
config
.newOptionBuilder()
.setLabel("c")
.setValue("c")
)
.addOption(
config
.newOptionBuilder()
.setLabel("d")
.setValue("d")
)
.addOption(
config
.newOptionBuilder()
.setLabel("e")
.setValue("e")
)
.setAllowOverride(true);
In getFields() I define Characteristic which then returns data from the database a, b, c, d or e:
fields.newDimension()
.setId('Characteristic')
.setType(types.TEXT);
I am trying to define a calculated field which I will then use as a filter in my charts and tables to only display data for the option selected by the user in the dropdown data control. E.g. if the user selects "b", then only data labelled "b" for Characteristic will display.
My attempt (inspired by the answer here: How to use a Parameter in calculated field defined in getFields() (Google Data Studio Community Connector)?) is:
fields.newDimension()
.setId('Characteristic calc')
.setDescription('Sets true if characteristic selected in dropdown is the same as the characteristic dimension field')
.setFormula('$Characteristic = "' + request.configParams.characteristic_selected + '"')
.setType(types.BOOLEAN);
I then apply a filter to the tables and charts only include Characteristic calc = True
The default is "a". When I first load the page, the data filters correctly and only displays "a". However when I select "b" from the dropdown data control, it still only displays data for "a". It appears that the code does not capture the updated configParam when changed in the report.
Note: if I set the calculated field up in the report as opposed to in the data studio connector, then it works correctly. I use the connector in lots of reports however, so it is annoying to have to create the calculated field each time I create a new report.
Sorry, using the .setFormula will only work, if the parameter is only set during the source configuration. If the user can change it in the report the field calculation has to be done in the function getSchema(request).
function getSchema(request) {
var fields = cc.getFields();
var types = cc.FieldType;
fields.newDimension()
.setId('Characteristic_info_info')
.setDescription('tempory field')
.setType(types.TEXT);
fields.newDimension()
.setId('Characteristic calc')
.setDescription('Sets true if characteristic selected in dropdown is the same as the characteristic dimension field')
.setFormula('$Characteristic = $Characteristic_info_info')
.setType(types.BOOLEAN);
Then set the column to the right value in this function:
function getData(request) {
....
var schemaE = getFields(request).forIds(
request.fields.map(function(field) {
return field.name;
})
).build();
var out1=[];
schemaE.forEach(a=>out1.push(a.name in params ? params[a.name] : 'nope:'+a.name ));
var out= {
schema: schemaE,
rows: [{values:out1},{values:out1},{values:out1}]
};
console.log(schemaE)
console.log(request.configParams.characteristic_selected)
for(let col in schemaE){
if(schemaE[col].name=="Characteristic_info_info"){
for(let i in out.rows)
{
out.rows[i].values[col]=request.configParams.characteristic_selected+"!";
}
}
}
return out;
}
If a paramter could be used in .setFormula and not only $ for fields, life would be much easier.

SuiteScript 2.0: How do you load a dataset and then add conditions?

The situation:
I am trying to load a dataset and then add additional criteria (filters) to the dataset based off users selected fields. The whole dev is a "Custom Report" build using a suitlete that has some fields the user can populate to choose "dynamic filters". When they click on the generate button I add the criteria/filters to a search and dataset and then join the results and display them.
The issue is that while I am able to add filters to the search after I load it no matter what I try I can't seem to add filters to the Dataset.
This code gets the dataset Data:
var datasetData = datasetLib.load({ id: datasetId });
resultSet.pageRanges.forEach(function (pageRange) {
// Fetch the results on the current page
var myPage = resultSet.fetch({ index: pageRange.index });
res.data = res.data.concat(myPage.data.results);
if (res.columns.length < 1) {
var columns = JSON.parse(myPage.pagedData.queryDefinition).columns;
for (var i = 0; i < columns.length; i++) {
res.columns.push(columns[i].label);
}
}
});
I attempted many different iterations to create the condition... here is one:
dataset.createCondition({
column: datasetData.columns[0], // I loaded the dataset and use it to reference the column
operator: query.Operator.ANY_OF,
values: params.customer.split(',')
})
Now the above code DOSE create a condition but when I attempt to add it into the dataset's current conditions I receive errors.I am attempting to push it into the child parameter of the parent criteria.
Please ask if you need more info...
If using a workbook is fine then I would suggest you to load the workbook using your above dataset using the query module and then use the above createCondition to add the condition to the loaded query dynamically.
var myLoadedQuery = query.load({
id: 'custworkbook237'
});
var mySalesRepJoin = myLoadedQuery.autoJoin({
fieldId: 'salesrep'
});
var thirdCondition = mySalesRepJoin.createCondition({
fieldId: 'email,
operator: query.Operator.START_WITH_NOT,
values: 'foo'
});
I would also urge to ensure the joins are accurately represented by looking at the Records catalog via Setup>Records Catalog. Hope this helps.

Adding multiple owners for Google App Maker records

I need help with Google App Maker data model security. I want to set multiple owners for a single record. Like the current user + the assigned admin + super admin.
I need this because all records can have different owners and super-owners/admins.
I know that we can point google app maker to a field containing record owner's email and we can set that field to the current user at the time of the creation of the record.
record.Owner = Session.getActiveUser().getEmail();
I want to know if it is possible to have field owners or have multiple fields like owner1, owner2 and then assign access levels to owner1, owner2...
Or how can we programmatically control the access/security/permissions of records?
The solution I'd use for this one definitely involves a field on the record that contains a comma separated string of all the users who should have access to it. I've worked on the following example to explain better what I have in mind.
I created a model and is called documents and looks like this:
In a page, I have a table and a button to add new document records. The page looks like this:
When I click on the Add Document button, a dialog pops up and looks like this:
The logic on the SUBMIT button on the form above is the following:
widget.datasource.item.owners = app.user.email;
widget.datasource.createItem(function(){
app.closeDialog();
});
That will automatically assign the creator of the record the ownership. To add additional owners, I do it on an edit form. The edit form popus up when I click the edit button inside the record row. It looks like this:
As you can see, I'm using a list widget to control who the owners are. For that, it is necessary to use a <List>String custom property in the edit dialog and that will be the datasource of the list widget. In this case, I've called it owners. I've applied the following to the onClick event of the edit button:
var owners = widget.datasource.item.owners;
owners = owners ? owners.split(",") : [];
app.pageFragments.documentEdit.properties.owners = owners;
app.showDialog(app.pageFragments.documentEdit);
The add button above the list widget has the following logic for the onClick event handler:
widget.root.properties.owners.push("");
The TextBox widget inside the row of the list widget has the following logic for the onValueEdit event handler:
widget.root.properties.owners[widget.parent.childIndex] = newValue;
And the CLOSE button has the following logic for the onClick event handler:
var owners = widget.root.properties.owners || [];
if(owners && owners.length){
owners = owners.filter(function(owner){
return owner != false; //jshint ignore:line
});
}
widget.datasource.item.owners = owners.join();
app.closeDialog();
Since I want to create a logic that will load records only for authorized users, then I had to use a query script in the datasource that will serve that purpose. For that I created this function on a server script:
function getAuthorizedRecords(){
var authorized = [];
var userRoles = app.getActiveUserRoles();
var allRecs = app.models.documents.newQuery().run();
if(userRoles.indexOf(app.roles.Admins) > -1){
return allRecs;
} else {
for(var r=0; r<allRecs.length; r++){
var rec = allRecs[r];
if(rec.owners && rec.owners.indexOf(Session.getActiveUser().getEmail()) > -1){
authorized.push(rec);
}
}
return authorized;
}
}
And then on the documents datasource, I added the following to the query script:
return getAuthorizedRecords();
This solution will load all records for admin users, but for non-admin users, it will only load records where their email is located in the owners field of the record. This is the most elegant solution I could come up with and I hope it serves your purpose.
References:
https://developers.google.com/appmaker/models/datasources#query_script
https://developers.google.com/appmaker/ui/binding#custom_properties
https://developers.google.com/appmaker/ui/logic#events
https://developers-dot-devsite-v2-prod.appspot.com/appmaker/scripting/api/client#Record

Populate Google Apps flexTable with filtered Spreadsheet Data and returning changes to spreadsheet

I am writing an application that pulls task data (for a task management system) from rows in a Google Spreadsheet with conditions matching a query. I then send this data into a flexTable next to a checkbox with an onClickHandler as well as a textbox to enter in hours data. Below is the simplest example of how I have achieved this:
\\establish handler for checkboxes that are generated in flexTable
var updateHandler = app.createServerClickHandler('updateTasksfunc');
updateHandler.addCallbackElement(taskTable);
\\search Google Spreadsheet for rows matching condition
for(var i=1; i< taskData.length; i++){
if (taskData [i][1] !=employee){
continue;
}
\\look up if task is already completed, and set checkbox value to true/false
var complete = taskData[i][8].toString();
if(complete==="true"){var checkbox=true;}else{var checkbox=false;}
\\populate flexTable with values from spreadsheet query
taskTable.setWidget(i,0,app.createTextBox().setName('hours'+i).setWidth('50').setId("hour"+i)).setWidget(i,1,app.createCheckBox().setValue(checkbox).setName('complete'+i).addClickHandler(updateHandler)).setWidget(i,2,app.createLabel(taskData[i][2]).setWidth('250')).setWidget(i,3,app.createLabel(taskData[i][3]).setWidth('250')).setWidget(i,4,app.createLabel(taskData[i][4])).setWidget(i,5,app.createLabel(taskData[i][5])).setWidget(i,6,app.createLabel(taskData[i][6])).setWidget(i,7,app.createLabel(taskData[i][7])).setWidget(i,8,app.createTextBox().setVisible(false).setName("dataRow"+i).setValue(i))
}
\edits to doGet()
var numberOfItems = app.createTextBox().setName('rows').setValue(i).setVisible(false);
var rows1 = i+1;
taskTable.setWidget(rows1,0,numberOfItems)
I have stored the spreadsheet data row in the invisible textbox in column 8 of the flex table that I intend to use in the clickHandler function to update the Google Spreadsheet.
I have successfully activated a click handler that runs when a checkbox is clicked; however, I have not yet successfully figured out a way to grab data from the flexTable that I can use to update the Spreadsheet.
//Functional code below
function updateTasksfunc(e){
var app = UiApp.getActiveApplication();
var taskSS = SpreadsheetApp.openById('SPREADSHEETID');
var taskSH = taskSS.getSheets()[0];
var taskData = taskSS.getDataRange().getValues();
var results = [];// an array to collect results
var numberOfItems = e.parameter.rows;
app.add(app.createLabel(numberOfItems));
for(var n=0;n<numberOfItems;n++){
results.push([e.parameter['check'+n]]);// each element is an array of 2 values
}
for(var i=1;i<results.length;i++){
var check = results[i][0];
var row = i+1;
taskSH.getRange(row,9,1,1).setValue(check);
}
return app;
}
I am getting a generic error: cannot find function getRowCount() from generic.
I assume that this means that I am not properly calling my flexTable.
Any thoughts??
flextables are not working like spreadsheets, you can't get widgets values directly as part of the table. You have to get every textBox and checkBox separately using e.parameter.Name.
You can do that easily in a loop but you'll have to know the number of textBoxes to be able to rebuild the names just as you did to create them in the doGet function.
I would suggest to store this number in a tag on one of your widget or in a separate hidden widget (hidden class or invisible textBox). Then you will be able to get the values like this :
...
var results = [];// an array to collect results
var numberOfItems = Number(e.parameter.itemNumbers); // this is the var you are missing right now and need to add. This is the solution of a separate hidden widget
for(var n=0;n<numberOfItems;n++){
results.push([e.parameter['hours'+n],e.parameter['complete'+n]]);// each element is an array of 2 values
}
... // from here you will have an 2D array with all your values and you can continue like you did=.

Resources