I'm currently using this script:
function onEdit(e)
// Set a comment on the edited cell to indicate when it was changed.
var range = e.range;
range.setNote('Laatst veranderd: ' + new Date());
What do I need to add so this will only work in column 'C'?
Restrict the code from running in a Google Sheet if a certain column is edited. This uses Apps Script onEdit() reserved function name, which is triggered to run on the Edit Event.
Get the column number of the range:
function onEdit(e) {//"e" receives the event object
var range = e.range;//The range of cells edited
var columnOfCellEdited = range.getColumn();//Get column number
//Logger.log(columnOfCellEdited)
if (columnOfCellEdited === 3) {// Column 3 is Column C
//Set a comment on the edited cell to indicate when it was changed.
range.setNote('Laatst veranderd: ' + new Date());
};
};
Another version:
function onEdit(e) {//"e" receives the event object
var range = e.range;//The range of cells edited
var columnOfCellEdited = range.getColumn();//Get column number
//Logger.log(columnOfCellEdited)
if (columnOfCellEdited !== 3) {return;}// Halt the code if the column
//edited is not column C
//Set a comment on the edited cell to indicate when it was changed.
range.setNote('Laatst veranderd: ' + new Date());
};
You may also try to extract the column index. Function getA1Notation() returns cell position which can be used to parse column.
function onEdit(e){
// Set a comment on the edited cell to indicate when it was changed.
var range = e.range;
var title = range.getA1Notation();
var data = {
'bookName': 'Some book',
'whoAdded': 'Nick',
'whenAdded': new Date()
};
var options = {
'method' : 'post',
'contentType': 'application/json',
// Convert the JavaScript object to a JSON string.
'payload' : JSON.stringify(data)
};
if(title.charAt(0) === 'B'){
var result = UrlFetchApp.fetch('https://xxx.xxx', options);
}else{
range.setNote('Failed to upload request :(');
}
}
If you want the script to work for edits in column C, try
function onEdit(e) {
if(e.range.columnStart === 3) {
e.range.setNote('Laatst veranderd: ' + new Date());
}
}
Related
I get the following error message when running some code in Google Apps Script. I don't understand the Line/Column reference, Code:46:18. It appears to point to either a line with too few columns or a process with too few lines. I assume I am not interpreting the reference correctly.
TypeError: Cannot set property 'format' of undefined
at processInbox(processInbox Code:46:18)
Line 46 of all my code is this and certainly doesn't have 18 columns (and it closes a function that doesn't refer to format):
}
The process referred to by the error message, processInbox, is only 39 lines long.
The script is called by selecting "Run Script" in the menu, "CiviSchedule" in the related Google Sheet, which triggers the doTasks function. This menu and and trigger are created in the onOpen function.
How am I misinterpreting the error message? (Full code follows)
[screenshot of error]
[screenshot of lines 40-46]
The code for reference:
//General Info
//
// As detailed in Managing Scheduled Jobs URL method http://wiki.civicrm.org/confluence/display/CRMDOC/Managing+Scheduled+Jobs#ManagingScheduledJobs-URLmethod :
//
// a valid Username and Password (for a Drupal, Joomla or WordPress user who has adequate permissions
// for the job or jobs being run. The minimal permissions for that user are: “view all contacts”, “access
// CiviCRM”, “access CiviMail”). It also requires you to pass in the CIVICRM_SITE_KEY which is configured
// by editing your copy of civicrm.settings.php
//
// I’d recommend setting up a dedicated account for scheduling reports with only minimal permissions.
// Once you have a username/password setup open File > Project Properties and open the Script Properties
// tab. Click ‘Add row’ link and add your setup account name (username), pass (password), key (site key).
// Save the Script Properties and then in the script editor window enter the BASE_URL below of your Civi
// installation (in Drupal this looks like http://[SITEROOT]/sites/all/modules/civicrm/bin/cron.php?.
// File > Save your script
var BASE_URL = "https://www.fubar.org/sites/all/modules/civicrm/bin/cron.php?";
// To get this script to run automatically open Resources > Current project triggers
// and slect doTasks to run as a day timer (we set reports to run between 7-8am)
// If you want to run earlier or later also adjust the RERUN_HOUR below which sets the next run time
var RERUN_HOUR = 1;
var PS = PropertiesService.getScriptProperties();
var param = PS.getProperties();
param.job = "mail_report";
// helper so we know which value is in which column
var COL = {report_id: 0,
type: 1,
last_run: 2,
next_run: 3,
format: 4,
ss_id: 5,
ss_sht: 6,
total: 7};
function onOpen(){
var ui = SpreadsheetApp.getUi();
ui.createMenu('CiviSchedule')
.addItem('Run Script', 'doTasks')
.addToUi();
}
function doTasks() {
var doc = SpreadsheetApp.getActiveSpreadsheet(); // get spreadsheet
var sheet = doc.getSheetByName("Tasks"); // get sheet
var data = sheet.getRange(3, 1, sheet.getLastRow(), COL.total).getValues(); // get values
var now = new Date(); // time now
// for each row of the sheet interate accross
for (var i = 0; i < data.length; i++){
if (data[i][COL.report_id] != ""){ // if there is instance id do something
// collect row values
var report_id = data[i][COL.report_id]
var type = data[i][COL.type];
var next_run = data[i][COL.next_run] || 0;
// check if it's time to run the report again
if (next_run < now && type != "never"){
// if it is ping the report trigger
var new_next_run = callUrl(report_id, type, {format: data[i][COL.format], ss_id: data[i][COL.ss_id], ss_sht: data[i][COL.ss_sht]} );
// ..and record when to run again
sheet.getRange(parseInt(i)+3, 3, 1, 2).setValues([[now, new_next_run]]);
}
}
}
}
function callUrl(report_id, type, optParam){
// build the url to trigger the report
param.format = optParam.format || "print";
if (optParam.ss_id && optParam.ss_sht){
// if we have a sheet name and id force csv
param.format = 'csv';
// make a search string to find our report
optParam.search_str = 'report/instance/'+report_id+'?reset=1 has:attachment is:unread';
// store our search for later
PS.setProperty('search_str_'+report_id, JSON.stringify(optParam));
// set the script to read the email run 15min later
ScriptApp.newTrigger("processInbox")
.timeBased()
.after(1 * 60 * 1000)
.create();
}
// make url
var qs = BASE_URL
for(var key in param) {
if (key.substring(0, 10) != "search_str"){
var value = param[key];
qs += key + "=" + value + "&";
}
}
qs += "instanceId="+report_id;
try {
//gg var resp = UrlFetchApp.fetch(qs); // hit the url
// now calculate when to run again
var d = new Date();
d.setHours(RERUN_HOUR);
d.setMinutes(0);
switch (type){
case "daily":
d.setDate(d.getDate() + 1);
break;
case "weekly":
d.setDate(d.getDate() + 7);
break;
case "monthly":
// Get the first Monday in the month
d.setDate(1);
d.setMonth(d.getMonth() + 1);
while (d.getDay() !== 1) {
d.setDate(d.getDate() + 1);
}
break;
}
return d;
} catch(e) {
return e.message;
}
}
function processInbox(){
var PS = PropertiesService.getScriptProperties();
var data = PS.getProperties();
for (var key in data) {
try { if (key.substring(0, 10) == "search_str"){
var param_raw = data[key];
var param = JSON.parse(param_raw);
// get last 20 message threads using serach term
var threads = GmailApp.search(param.search_str, 0, 20);
// assume last thread has our latest data
var last_thread = threads.length-1;
if (last_thread > -1){
// get message in the last thread
var msg = threads[last_thread].getMessages()[0];
// get the attachments
var attachments = msg.getAttachments();
for (var k = 0; k < attachments.length; k++) {
// get the attachment as a string
var csv_str = attachments[k].getDataAsString();
// parse string as csv
var csv = Utilities.parseCsv(csv_str);
// create destination object
var doc = SpreadsheetApp.openById(param.ss_id);
var sheet = doc.getSheetByName(param.ss_sht);
// clear any old data
sheet.clear();
// write new data
sheet.getRange(1, 1, csv.length, csv[0].length).setValues(csv);
// mark message are read and archive (you could also label or delete)
threads[last_thread].moveToArchive().markRead();
PS.deleteProperty(key);
}
}
}
} catch(e) {
SpreadsheetApp.getUi().alert('problem: ${e}');
}
}
}
at processInbox(processInbox Code:46:18)
The syntax is
at ${FUNCTION}(${FILE}:${LINE}:${COLUMN})
This would suggest that the code causing the error is elsewhere.
At file
processInbox Code
within function
processInbox
And at line
46
and at column
18
You probably have a same function name processInbox at a different file named processInbox Code. In that file, at line 46, col 18, you'll have your error.
I think that you problem may be that this line:
var data = sheet.getRange(3, 1, sheet.getLastRow(), COL.total).getValues();
should be like this:
var data = sheet.getRange(3, 1, sheet.getLastRow() - 2, COL.total).getValues();
I have data from a questionnaire (20K rows) that I need to share with the store managers (report) of our shops (400 shops). I managed to write a script that sends a pdf of my sheet to a list of e-mail addresses. But I'm stuck on writing the loop for the filter, since I can't get the setVisibleValues(values) function to work for FilterCriteriaBuilder. The setHiddenValues(values) function works, but I can't figure out how to combine that with the loop.
Sample of my Google Sheet
See below for my current code:
/**
* Filtersheet by location
*/
function FilterSheet() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data')
spreadsheet.getRange('F1').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues(['Amsterdam, Rotterdam'])
.build();
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
};
/**
* Send pdf of currentspreadsheet
*/
function SendPdf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Adres');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = spreadsheet.getRange(startRow, 1, numRows, 2);
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = 'I hearby send you the overview of your data'
var subject = 'Overview of data';
MailApp.sendEmail(emailAddress, subject, message,{
attachments:[blob]});
}
}
getValues() returns the values of all range's cells no matter if they are shown or hidden.
Use a loop and isRowHiddenByFilter(rowPosition) to reap out all the filtered values. You could use Array.prototype.push to add the values to a new array or use Array.prototype.splice to modify the array holdin the values returned by getValues()
Related
How to use in Google Sheets setValue only for range of filtered rows (getRange for not hidden cells)?
I managemed to solve the problem.
This script takes a google spreadsheet with 2 sheets,one with Data and one with a combination EmailAdresses.
It sends a filtered list (filter column F) of sheet Data to the corresponding salon (location) in sheet Emailadresses (var mode email). Additionally, it has the option to "store" the pdf's in your google drive (var mode store)
*/
function construct() {
// settings:
//var mode = "store";
var mode = "email";
// get list of all salons and email
var salonList = SpreadsheetApp.getActive().getSheetByName('EmailAdressen');
// set endvar for loop
var endRow = salonList.getLastRow();
// loop trough the rows to get the Salon name and the corresponding email
for(i=1;i<=endRow;i++){
var salonName = salonList.getRange(i,2).getValue();
var email = salonList.getRange(i,1).getValue();
// create an array with all salons that should be hidden (we cant pick which one to show, so we have to go the other way around...)
var filterArray = [];
// create array with all salons to hide
for(c=1;c<=endRow;c++){
// get value from email list, check if it is not the current selected one and if so add it to the list to filter out
salonFilterName = salonList.getRange(c,2).getValue();
if(salonFilterName != salonName) {
filterArray.push(salonFilterName);
}
} // end for c
// filter the list with the array we just created
var spreadsheet = filterList(filterArray);
if(mode == "email"){
// export to PDF
var pdf = exportToPdf(spreadsheet);
// email to email address belonging to this salon
emailToAddress(email, pdf);
} // end if
if(mode == "store"){
StorePdf(spreadsheet, salonName);
}
} // end for i
return;
}
function filterList(salonNameArray) {
// select data sheet
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
// first remove all existing filters to make sure we are on a clean sheet
if(spreadsheet.getFilter()){
spreadsheet.getFilter().remove();
}
// create the filter
spreadsheet.getRange('F:F').createFilter();
// set criteria for filter with array passed from construct
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(salonNameArray).build();
// apply filter
spreadsheet.getFilter().setColumnFilterCriteria(6, criteria);
return spreadsheet;
}
function exportToPdf(ss) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName(ss.getName() + ".pdf");
return blob;
}
function StorePdf(ss, salonName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('Data');
var blob = DriveApp.getFileById(ss.getId()).getBlob();
blob.setName(salonName + "_" + Utilities.formatDate(new Date(), "GMT+1", "ddMMyyyy")+".pdf");
DriveApp.createFile(blob);
return;
}
function emailToAddress(email, pdf) {
MailApp.sendEmail(email, 'Type here the subject', 'Type here the body',{
attachments:[pdf]});
return;
}
Full disclosure, I am in no way a programmer of any kind. My library was looking for an easier way for multiple locations to add events to the public calendar.
Eventually I stumbled upon this script which I was able to adapt for our needs. However, the one change they would like is to have the end time default to 2 hours later. For example, if an event starts at 1 then the end time automatically defaults to 3.
Can anyone show me what change in the script I need to make for that to happen? Here is the test form that we use to enter the dates. Right now the end time is entered manually but I'd imagine that would have to be removed, correct?
Any help in figuring this out would be greatly appreciated.
Thanks!
//insert your google calendar ID
var calendarId = "ID-FOR-TEST-CALENDAR";
//index (starting from 1) of each column in the sheet
var titleIndex = 2;
var descriptionIndex = 3;
var startDateIndex = 4;
var endDateIndex = 5;
var googleCalendarIndex = 6;
/*
find the row where the Google Calendar Event ID is blank or null
The data of this row will be used to create a new calendar event
*/
function findRow(sheet) {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
for (var i = 0; i < values.length; i++) {
if(values[i][googleCalendarIndex-1]=="" || values[i][googleCalendarIndex-1]==null)
newEvent(i+1);
}
};
/*
get the data of the new row by calling getSheetData() and
create a new Calendar event by calling submitToGoogleCalendar()
*/
function newEvent(row){
var sheet = SpreadsheetApp.getActiveSheet();
var eventId = submitToGoogleCalendar(getSheetData(sheet,row),null)
if(eventId!=null)
sheet.getRange(row,googleCalendarIndex,1,1).setValue(eventId);
};
/*
Store the data of a row in an Array
*/
function getSheetData(sheet,row)
{
var data = new Array();
data.title=sheet.getRange(row,titleIndex,1,1).getValue();
data.description=sheet.getRange(row,descriptionIndex,1,1).getValue();
data.startDate = sheet.getRange(row,startDateIndex,1,1).getValue();
data.endDate = sheet.getRange(row,endDateIndex,1,1).getValue();
return data;
};
/*
if a cell is edited in the sheet, get all the data of the corresponding row and
create a new calendar event (after deleting the old event) by calling submitToGoogleCalendar()
*/
function dataChanged(event){
var sheet = SpreadsheetApp.getActiveSheet();
var row = event.range.getRow();
var eventId = sheet.getRange(row,googleCalendarIndex,1,1).getValue();
var eventId = submitToGoogleCalendar(getSheetData(sheet,row),eventId)
if(eventId!=null)
sheet.getRange(row,googleCalendarIndex,1,1).setValue(eventId);
};
/*
This function creates an event in the Google Calendar and returns the calendar event ID
which is stored in the last column of the sheet
*/
function submitToGoogleCalendar(sheetData,eventId) {
// some simple validations ;-)
if(sheetData.title == "" || sheetData.startDate == "" || sheetData.startDate == null)
return null;
var cal = CalendarApp.getCalendarById(calendarId);
var start = new Date(sheetData.startDate);
var end = new Date(sheetData.endDate);
// some simple date validations
if(start > end)
return null;
var event = null;
//if eventId is null (when called by newEvent()) create a new calendar event
if(eventId==null)
{
event = cal.createEvent(sheetData.title, start, end, {
description : sheetData.description,
});
return event.getId();
}
/*
else if the eventid is not null (when called by dataChanged()), delete the calendar event
and create a new event with the modified data by calling this function again
*/
else
{
event = cal.getEventSeriesById(eventId);
event.deleteEventSeries();
return submitToGoogleCalendar(sheetData,null);
}
return event.getId();
};
Without having tried it myself, I believe you could do it like this:
function submitToGoogleCalendar(sheetData,eventId) {
var cal = CalendarApp.getCalendarById(calendarId);
var start = new Date(sheetData.startDate);
var end = new Date(sheetData.endDate);
end.setHours(start.getHours() + 2);
...where the last line is the new line added to your original script.
To your question wether the end time should be removed, I would say yes, it should be removed.
Is there a way to refresh a single Kendo grid row without refreshing the whole datasource or using jQuery to set the value for each cell?
How do you define the row that you want to update? I'm going to assume that is the row that you have selected, and the name of the column being updated is symbol.
// Get a reference to the grid
var grid = $("#my_grid").data("kendoGrid");
// Access the row that is selected
var select = grid.select();
// and now the data
var data = grid.dataItem(select);
// update the column `symbol` and set its value to `HPQ`
data.set("symbol", "HPQ");
Remember that the content of the DataSource is an observable object, meaning that you can update it using set and the change should be reflected magically in the grid.
data.set will actually refresh the entire grid and send a databound event in some cases. This is very slow and unnecessary. It will also collapse any expanded detail templates which is not ideal.
I would recommend you to use this function that I wrote to update a single row in a kendo grid.
// Updates a single row in a kendo grid without firing a databound event.
// This is needed since otherwise the entire grid will be redrawn.
function kendoFastRedrawRow(grid, row) {
var dataItem = grid.dataItem(row);
var rowChildren = $(row).children('td[role="gridcell"]');
for (var i = 0; i < grid.columns.length; i++) {
var column = grid.columns[i];
var template = column.template;
var cell = rowChildren.eq(i);
if (template !== undefined) {
var kendoTemplate = kendo.template(template);
// Render using template
cell.html(kendoTemplate(dataItem));
} else {
var fieldValue = dataItem[column.field];
var format = column.format;
var values = column.values;
if (values !== undefined && values != null) {
// use the text value mappings (for enums)
for (var j = 0; j < values.length; j++) {
var value = values[j];
if (value.value == fieldValue) {
cell.html(value.text);
break;
}
}
} else if (format !== undefined) {
// use the format
cell.html(kendo.format(format, fieldValue));
} else {
// Just dump the plain old value
cell.html(fieldValue);
}
}
}
}
Example:
// Get a reference to the grid
var grid = $("#my_grid").data("kendoGrid");
// Access the row that is selected
var select = grid.select();
// and now the data
var data = grid.dataItem(select);
// Update any values that you want to
data.symbol = newValue;
data.symbol2 = newValue2;
...
// Redraw only the single row in question which needs updating
kendoFastRedrawRow(grid, select);
// Then if you want to call your own databound event to do any funky post processing:
myDataBoundEvent.apply(grid);
I found a way to update the grid dataSource and show in the grid without refreshing all the grid.
For example you have a selected row and you want to change column "name" value.
//the grid
var grid = $('#myGrid').data('kendoGrid');
// Access the row that is selected
var row = grid.select();
//gets the dataItem
var dataItem = grid.dataItem(row);
//sets the dataItem
dataItem.name = 'Joe';
//generate a new row html
var rowHtml = grid.rowTemplate(dataItem);
//replace your old row html with the updated one
row.replaceWith(rowHtml);
updateRecord(record) {
const grid = $(this.el.nativeElement).data('kendoGrid');
const row = grid.select();
const dataItem = grid.dataItem(row);
for (const property in record) {
if (record.hasOwnProperty(property)) {
dataItem.set(property, record[property]);
}
}
}
How do I obtain the selected row value for a hidden column in a Telerik RadGridView? The column is hidden on the aspx page and I would like to retrieve the value on the client side (JavaScript).
Essentially, I want to display a name in the grid view and be able to retrieve a value from the hidden field "ID" to bring up an edit form.
Here is an example of how I'm hiding the RadGridView column.
code sample:
onKeyPressEvent(sender, args) {
var variable = function (e) {
e = e || window.event;
if (e.keyCode == 13) {
var PartyID = args.getDataKeyValue("PARTY_ID");
var oManager = '<%=winMgr.ClientID %>';
var oManager = window.radopen("AttorneyEdit.aspx?PARTY_ID=" + PartyID, null);
oManager.setSize(1000, 530);
//Width, Height oManager.center();
} else { return true;
}
}
theForm.onkeypress = variable
}
Thanks for your help...
maybe you're going the wrong way. There is a property called "ClientDataKeyNames" on the mastertableview. You can add several key separated by a comma in this property. Then you'll be able to get your value without adding an extra column.
You should do something like this:
function onKeyPressEvent(sender, args) {
if (args.get_keyCode() == 13) {
var dataItem=sender.get_selectedItems()[0];
var PartyID = dataItem.getDataKeyValue("PARTY_ID");
var oManager = '<%=winMgr.ClientID %>';
var oManager = window.radopen("AttorneyEdit.aspx?PARTY_ID=" + PartyID, null);
oManager.setSize(1000, 530);
//Width, Height oManager.center();
}
else {
return true;
}
}
good luck