onEdit checkbox then IMPORTRANGE - validation

I'm trying to combine a onEdit script with an import range script, I tried using "if" to check if there's a single TRUE checkbox on the selected range but the import range part does not work only the checkbox part.
I need to import only after a single TRUE checkbox is enabled on column 7 otherwise the other spreadsheet glitches.
Source for the checkbox validation script: https://stackoverflow.com/a/71798583/18249133
function onEdit(e) {
const ss = SpreadsheetApp.getActiveSheet()
const { range: { columnStart, rowStart, rowEnd }, value } = e
if (value === "FALSE") return;
const checkRowStart = 4
const checkRowEnd = 200
const checkColumn = 7
if (columnStart === checkColumn && checkRowStart <= rowStart && checkRowEnd >= rowEnd) {
for (let i = 0; i < checkRowEnd; i++) {
ss.getRange(i + checkRowStart, checkColumn).setValue(false)
}
ss.getRange(rowStart, columnStart).setValue(true)
}
}
Import range script
function import(){
importRange(
"SPREADSHEET_ID",
"SHEET NAME + RANGE",
"SPREADSHEET_ID",
"SHEET NAME + RANGE",
);
};
function importRange(sourceID, sourceRange, destinationID, destinationRangeStart) {
const sourceSS = SpreadsheetApp.openById(sourceID);
const sourceRng = sourceSS.getRange(sourceRange);
const sourceVals = sourceRng.getValues();
const destinationSS = SpreadsheetApp.openById(destinationID);
const destStartRange = destinationSS.getRange(destinationRangeStart);
const destSheet = destinationSS.getSheetByName(destStartRange.getSheet().getName());
const destRange = destSheet.getRange(
destStartRange.getRow(),
destStartRange.getColumn(),
sourceVals.length,
sourceVals[0].length
);
destRange.setValues(sourceVals);
};
Thanks in advance :]

Related

Filter & Delete rows of data based off of column value fast! (Google Sheets)

Is there a way to filter the data in column Q off my google sheet faster then reading line one by one. There is daily about 400+ lines it needs to scan through and I need to delete every row of data if the data in column Q is less than 1 right now watching it, it takes about 10+ minutes.
function UpdateLog() {
var returnSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('CancelRawData');
var rowCount = returnSheet.getLastRow();
for (i = rowCount; i > 0; i--) {
var rrCell = 'Q' + i;
var cell = returnSheet.getRange(rrCell).getValue();
if (cell < 1 ){
returnSheet.deleteRow(i);
}
}
{
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been updated", SpreadsheetApp.getUi().ButtonSet.OK);
}
}
Try it this way:
function UpdateLog() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getDataRange().getValues();
let d = 0;
vs.forEach((r, i) => {
if (!isNaN(r[16]) && r[16] < 1){
sh.deleteRow(i + 1 - d++);
}
});
}
This is a bit quicker
function UpdateLog() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getDataRange().getValues().filter(r => !isNaN(r[16]) && r[16] < 1);
sh.clearContents();
sh.getRange(1,1,vs.length,vs[0].length).setValues(vs);
}

Is there a way I can make my script more efficient?

I have created the below script to add a timestamp when a cell equals a specific value. This script repeats for each row but it is not efficient. How can I make this simpler and quicker? Below is an extract of the script, it repeats for each row
function MyFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getSheetByName('Overview')
var cell = sheet.getRange('H3').getValue()
var sent = sheet.getRange('p3').getValue()
if (cell == "Full" && sent == "") {
sheet.getRange('p3').setValue(new Date())
}
else if (cell == "Open" && sent == "") {
sheet.getRange('p3').setValue("")
}
}
Try this.
function MyFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Overview');
// get range H3:P were H=8, P=16, 9 columns total or index 0 to 8
var data = sheet.getRange(3,8,sheet.getLastRow()-2,9).getValues(); // get range H3:P
var i = 0;
var row = null;
for( i=0; i<data.length; i++ ) {
row = data[i];
if( ( row[0] === "Full" ) && ( row[8] === "" ) ) {
row[8] = new Date();
}
// Your else doesn't do anything if blank set blank ??
row.splice(0,8); // extract only column P
}
sheet.getRange(3,16,data.length,1).setValues(data);
}
Try using setvalues() method of class range
function MyFunction() {
const ss = SpreadsheetApp.getActive()
const sh = ss.getSheetByName('Overview');
const rg = sh.getDataRange();
const vs = rg.getValues();
let vo = vs.map(r => {
if( r[7] == "Full") r[15]=new Date()
if( r[7] == "Open") r[15]='';
return [r[15]];
});
sh.getRange(2,16,vo.length,1).setValues(vo);
}

How to use EnumProcesses in node-ffi

I was trying to use EnumProcesses with node-ffi. I got code below:
import ffi from 'ffi'
export const psapi = ffi.Library('psapi', {
EnumProcesses: ['bool', ['ulong', 'ulong', 'uint16*']]
})
export class Win32ProcessManager {
public async getProcessList () {
let lpidProcess = ref.alloc('ulong*')
const cb = 1024
const lpcbNeeded = ref.alloc('uint16*')
const res = psapi.EnumProcesses(lpidProcess, cb, lpcbNeeded)
const ulongSize = (ref as any).sizeof.ulong
const totalBytesReturned = lpcbNeeded.readInt16LE()
const processCount = totalBytesReturned / ulongSize
console.log(`processCount: ${processCount}`)
// ??? How to get the value from the lpidProcess?
return lpidProcess
}
}
I tried with ref.get but I encountered errors:
let processId = ref.get(array, 0, ref.types.ulong)
console.log(processId)
const pointerSize = (ref as any).sizeof.pointer
console.log(pointerSize)
let processId2 = ref.get(array, (ref as any).sizeof.pointer, ref.types.ulong)
console.log(processId2)
Errors:
RangeError [ERR_BUFFER_OUT_OF_BOUNDS]: Attempt to write outside buffer bounds
Anyone knows how to use node-ffi read the array data from dll?
Thanks #DrakeWu-MSFT, I finally got my code works, here are how they looks finally:
import ffi from 'ffi';
import ref from 'ref';
import ArrayType from "ref-array";
export const psapi = ffi.Library('psapi', {
EnumProcesses: ['bool', ['ulong*', 'ulong', 'uint16*']],
});
export class Win32ProcessManager {
public getProcessIdList (): number[] {
const processIdLength = 1024;
const ulongSize = (ref as any).sizeof.ulong;
const cb = processIdLength * ulongSize;
let processIdArray = ArrayType('ulong', processIdLength);
let lpidProcess = ref.alloc(processIdArray);
const lpcbNeeded = ref.alloc('uint16*');
const res = psapi.EnumProcesses(lpidProcess, cb, lpcbNeeded);
if (res) {
const totalBytesReturned = lpcbNeeded.readInt16LE();
const processCount = totalBytesReturned / ulongSize;
const processArray = (lpidProcess as any).deref();
let resultProcessArray: number[] = [];
for (let i = 0; i < processCount; i++) {
resultProcessArray.push(processArray[i]);
}
return resultProcessArray;
} else {
console.error(`Get process list failed with result from EnumProcess: ${res}`);
return [];
}
}
}
I was struggled with getting array data from the pointer, and that was wrong, as #DrakeWu-MSFT said in the comment, because I didn't allocate enough spaces for the buffer, no data can be write into that. With ref-array and a pointer to the array, it works like a charm.

Set the sourceRange of Data Validation to an array of values

I'm creating a social media outreach tracker. I want to create a drop-down list of the contact name. The problem is that I have two sources of names on two different sheets.
I wrote a script that pulls the names from the two different sources and combines them to a single array.
I was hoping to set the source range as that array.
Here is my code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var auditionsSheet = SpreadsheetApp.getActiveSpreadsheet();
var castingDirectorsTab = auditionsSheet.getSheetByName("Casting Directors");
var contactsTab = auditionsSheet.getSheetByName("Contacts");
var socialMediaOutreachTab = auditionsSheet.getSheetByName("Social Media Outreach");
var lastRowCD = castingDirectorsTab.getLastRow();
var lastRowContacts = contactsTab.getLastRow();
var activeCell = socialMediaOutreachTab.getActiveCell();
var activeColumn = activeCell.getColumn();
// get data
var castingDirectorNameData = castingDirectorsTab.getRange(2, 1, lastRowCD, 1).getValues();
var contactNameData = contactsTab.getRange(2, 1, lastRowContacts, 1).getValues();
//get name data to a single arrays
var castingDirectorName = [];
castingDirectorNameData.forEach(function(yr) {
castingDirectorName.push(yr[0]);
});
var contactName = [];
contactNameData.forEach(function(yr) {
contactName.push(yr[0]);
});
// get rid of the empty bits in the arrays
for (var x = castingDirectorName.length-1; x > 0; x--) {
if ( castingDirectorName[x][0] === undefined ) {
castingDirectorName.splice( x, 1 )
}
}
for (var x = contactName.length-1; x > 0; x--) {
if ( contactName[x][0] === undefined ) {
contactName.splice( x, 1 )
}
}
//combine two data sources for data validation
var combinedNames = [];
combinedNames.push(castingDirectorName + contactName);
Logger.log (combinedNames);
Logger.log( typeof combinedNames);
// data validation set up and build
if (activeColumn == 1 && auditionsSheet.getName() == "Social Media Outreach") {
var range = auditionsSheet.getRange(activeCell.getRow(), activeColumn +1);
var sourceRange = combinedNames;
setDataValid_(range, sourceRange)
}
}
When I enter a date in Col A on Social Media Outreach, nothing happens in Col 2.
I was using an existing working nested data validation script I have but the sourceRange pulls from a sheet based on the value in the active cell. Here is that code:
function setDataValid_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.build();
range.setDataValidation(rule);
}
function onEdit() {
var aSheet = SpreadsheetApp.getActiveSheet();
var aCell = aSheet.getActiveCell();
var aColumn = aCell.getColumn();
// data validation for Auditions Tab Projet Type to Project Details
if (aColumn == 9 && aSheet.getName() == 'Auditions') {
var range = aSheet.getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('RefTables!' + aCell.getValue())
setDataValid_(range, sourceRange)
}
}
For this script when I select from the data validation drop-down, a new data validation comes up in the next col with the appropriate secondary data validation.
So the question is, can the source range be set to an array or do I need to put the names back into my sheet to reference a la the second script.
I've looked through the documentation and searched and can't find an answer. I'm relatively new to GAS and am not sure of all the inner workings of the data validation builder.

Check cell value before post data to server

I'm having a problem checking cell value after an inline edit and before save data. Part of my ColModel is (without any unnesessary code):
{name:'event_start_date',index:'event_start_date',width:75,align:'center',editable:true,edittype:'text',editoptions:{size:'10',maxlength:'10',
dataInit:function(el){
$(el).mask('9999-99-99');
$(el).datepicker({dateFormat:'yy-mm-dd',
beforeShow: function(input, instance){instance.dpDiv.css({marginTop: '1px'});}})
}}
},
{name:'event_start_time',index:'event_start_time',width:70,align:'center',editable:true,edittype:'text',editoptions:{size:'8',maxlength:'8',
dataInit:function(el){$(el).mask('99:99:99');}}
},
{name:'event_end_date',index:'event_end_date',width:75,align:'center',editable:true,edittype:'text',editoptions:{size:'10',maxlength:'10',
dataInit:function(el){
$(el).mask('9999-99-99');
$(el).datepicker({dateFormat:'yy-mm-dd',
beforeShow: function(input, instance){instance.dpDiv.css({marginTop: '1 px'});}})
}}
},
{name:'event_end_time',index:'event_end_time',width:70,align:'center',editable:true,edittype:'text',editoptions:{size:'8',maxlength:'8',
dataInit:function(el){$(el).mask('99:99:99');}}
},
{name:'event_dur_calc',index:'event_dur_calc',width:90,align:'center',editable:false,edittype:'text',sorttype:'date',editoptions:{size:'10',maxlength:'10'}
}
I'm using double click to get inline edit mode. After user make some changes into date/time fields, new value calculated for cell "event_dur_calc":
$('#'+rowId+'_event_start_date').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_start_time').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_end_date').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_endt_time').focusout(function(){recalc_dur(rowId);});
Functions fo calculating new time:
function mktime(){
var i = 0, d = new Date(), argv = arguments, argc = argv.length;
var dateManip = {
0: function(tt){ return d.setHours(tt); },
1: function(tt){ return d.setMinutes(tt); },
2: function(tt){ return d.setSeconds(tt); },
3: function(tt){ return d.setMonth(parseInt(tt)-1); },
4: function(tt){ return d.setDate(tt); },
5: function(tt){ return d.setYear(tt); }
};
for( i = 0; i < argc; i++ ){
if(argv[i] && isNaN(argv[i])){
return false;
} else if(argv[i]){
if(!dateManip[i](argv[i])){
return false;
}
}
}
return Math.floor(d.getTime()/1000);
};
function recalc_dur(rowId){
var event_start_date_txt = $('#'+rowId+'_event_start_date').val();
var event_start_time_txt = $('#'+rowId+'_event_start_time').val();
var event_end_date_txt = $('#'+rowId+'_event_end_date').val();
var event_end_time_txt = $('#'+rowId+'_event_end_time').val();
if (event_end_date_txt=='0000-00-00'){
$('#'+rowId+'_event_dur_calc').val('0000:00:00');
}else{
var start_d_pices = event_start_date_txt.split('-');
var start_t_pices = event_start_time_txt.split(':');
var end_d_pices = event_end_date_txt.split('-');
var end_t_pices = event_end_time_txt.split(':');
var start_time = mktime(start_t_pices[0], start_t_pices[1], start_t_pices[2], start_d_pices[1], start_d_pices[2], start_d_pices[0]);
var end_time = mktime(end_t_pices[0], end_t_pices[1], end_t_pices[2], end_d_pices[1], end_d_pices[2], end_d_pices[0]);
var delta = end_time-start_time;
var secs = delta % 60;
delta = (delta - secs) / 60;
if (secs.toString().length==1) var new_dur = ':0'+secs; else var new_dur = ':'+secs;
var mins = delta % 60;
delta = (delta - mins) / 60;
if (mins.toString().length==1) new_dur = ':0'+mins+new_dur; else new_dur = ':'+mins+new_dur;
var hours = delta;
if (hours.toString().length==1){new_dur = '000'+hours+new_dur;
}else if (hours.toString().length==2){new_dur = '00'+hours+new_dur;
}else if (hours.toString().length==3){new_dur = '0'+hours+new_dur;
}else new_dur = hours+new_dur;
$('tr[id=\"'+rowId+'\"] > td[aria-describedby=\"dfr_event_dur_calc\"]').html(new_dur);
}
}
If the new calculating time will be below zero (when start date/time is greater than end date/time), modal window with alert message is appers and data can't be saved and posted on server.
Is there any event in inline edit mode that fires before posting data, that I can use to check new calculating value to prevent incorrect data will be saved?
The current code of jqGrid on github contains beforeSaveRow callback function which will be called before saving. The changes was made after publishing of jqGrid 4.5.2, but the feature will be exist in the next version (>4.5.2) of jqGrid. So if you don't want use developer sources from github you can use the standard validation way: usage editrules with custom: true and custom_func which do the validation.

Resources