I have this onEdit function that goes over the sheet, looks at the cell value under NAME column, finds its position in another cell under LIST column and outputs this position in the POSITION column.
I am trying to find a way to limit this in a specific range, more specifically to exclude the 1st row (row 1) from this indexing, since if i add a row above the LIST, NAME and POSITION the function fails with TypeError: Cannot read property 'toString' of undefined.
Heres my function -
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('sheet1');
const [hA, ...vs] = sh.getDataRange().getValues();
function onEdit() {
var r = ss.getActiveCell();
if( r.getColumn() > 7 ) {
let idx = {};
hA.forEach((h, i) => { idx[h] = i; });
let vO1 = vs.map((r, i) => {
var tes = [r[idx['LIST']].toString().split(',').indexOf( r[idx['NAME']] ) +1]
return (tes == 0) ? [''] : tes;
});
sh.getRange(2, idx['POSITION'] + 1, vO1.length, vO1[0].length).setValues(vO1);
}
}
Only 1 row before labels:
const [header, hA, ...vs] = sh.getDataRange().getValues();
Alternative:
const values = sh.getDataRange().getValues();
values.shift();
const [hA, ...vs] = values;
Mutiple rows (specify labelRow):
const labelRow = 2;
const values = sh.getDataRange().getValues();
const [hA, ...vs] = values.slice(labelRow - 1);
Alternative:
const labelRow = 2;
const rows = sh.getLastRow() - labelRow + 1;
const [hA, ...vs] = sh.getRange(label, 1, rows, sh.getLastColumn()).getValues();
There are bugs on your code.
return (tes == 0) ? [''] : tes; should be:
return (tes == [0]) ? [''] : tes;
sh.getRange(2, idx['POSITION'] + 1, vO1.length, vO1[0].length).setValues(vO1); should be (make sure to define labelRow):
sh.getRange(labelRow + 1, idx['POSITION'] + 1, vO1.length, vO1[0].length).setValues(vO1);
Related
I want to Set Formula for some cell, depend on how much data I have.
The problem was, I can't write script to make Range without Row Initial ( e.g Range(A:AA)).
To make just Column Initial, I try to replace Parameter : lastCol and lastRow with blank string ('') , and Zero Number (0), but not work.
Thanks
function recap() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheetForm = sheet.getSheetByName('METER')
const sheetPrint = sheet.getSheetByName('CETAK TAGIHAN')
const n = 5
var lastRow = sheetForm.getLastRow()
var lastCol = n+4
const startRow = 7
const currentCol = 3
for (let i = 0 ; i < n; i++){
// FORMULA =vlookup((max(METER!A:A)),METER!A:I,5)
// HOW TO SET ALL COLUMN RANGE :(A:I) WITHOUT ROW NUMBER
sheetPrint.getRange(i+startRow,currentCol).setFormula('vlookup((max(METER!A:A)),METER!'+sheetForm.getRange(1,1,lastRow,lastCol).getA1Notation()+','+(5+i)+')');
}
}
In your situation, how about the following modification?
Modified script:
function recap() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheetForm = sheet.getSheetByName('METER')
const sheetPrint = sheet.getSheetByName('CETAK TAGIHAN')
const n = 5
const lastColumn = sheetForm.getLastColumn();
const columnIndexToLetter_ = index => (a = Math.floor(index / 26)) >= 0 ? columnIndexToLetter_(a - 1) + String.fromCharCode(65 + (index % 26)) : ""; // ref: https://stackoverflow.com/a/53678158
const columnLetter = columnIndexToLetter_(lastColumn - 1);
const formulas = [...Array(n)].map((_, i) => [`=vlookup((max(METER!A:A)),METER!A:${columnLetter},${i + 5})`]);
sheetPrint.getRange(7, 3, formulas.length).setFormulas(formulas);
}
When this script is run, the following formulas are put to the cells "C7:C11".
=vlookup((max(METER!A:A)),METER!A:I,5)
=vlookup((max(METER!A:A)),METER!A:I,6)
=vlookup((max(METER!A:A)),METER!A:I,7)
=vlookup((max(METER!A:A)),METER!A:I,8)
=vlookup((max(METER!A:A)),METER!A:I,9)
If the value of METER!A:I is constant, I think that the following modification might be able to be used.
function recap() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheetForm = sheet.getSheetByName('METER')
const sheetPrint = sheet.getSheetByName('CETAK TAGIHAN')
const n = 5
const formulas = [...Array(n)].map((_, i) => [`=vlookup((max(METER!A:A)),METER!A:I,${i + 5})`]);
sheetPrint.getRange(7, 3, formulas.length).setFormulas(formulas);
}
As another approach, the following modified script might be able to be used. In this case, the number values are removed from a1Notation.
function recap() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheetForm = sheet.getSheetByName('METER')
const sheetPrint = sheet.getSheetByName('CETAK TAGIHAN')
const n = 5
const a1Notation = sheetForm.getRange(1, 1, 1, sheetForm.getLastColumn()).getA1Notation().replace(/\d/g, "");
const formulas = [...Array(n)].map((_, i) => [`=vlookup((max(METER!A:A)),METER!${a1Notation},${i + 5})`]);
sheetPrint.getRange(7, 3, formulas.length).setFormulas(formulas);
}
Note:
I think that when the formulas are put using setFormulas, the process cost can be reduced a little.
I'm building a calculator to use for pricing purposes. It has a primary "Calculator" sheet, where an admin can enter data and then generate a new result to the "DataLog" sheet. The "DataLog" sheet stores the results (columns A through X) and calculates the resulting price (columns Y through AO). There are also a few workflow columns that need to be present for each row (Columns AP through AS).
I am currently using appendRow() to print the data to the "DataLog" sheet. The issue is that appendRow() finds the first empty row, and since columns Y through AS are not empty because they contain necessary formulas/workflow, it prints to the bottom of the sheet. I am looking for a way to print the data where 1) it checks only a certain column for an empty row (column A or C, for example) and prints to that row, and 2) does not overwrite the formula/workflow columns (Y through AS).
Is there a way to do this using appendRow() or is there another function I should be using? Other than this one issue of where to print the results, everything works just as I want it to, but I cannot seem to find a way to resolve this issue.
EDIT: The reason the formula and workflow must be present within "DataLog" is that there are situations where after an entry has been filled out and printed changes need to be made to row, thereby changing the final price. So I cannot calculate the price within the function and print that as a static number.
Here is a copy of the calculator: https://docs.google.com/spreadsheets/d/1vsVZeOUUqhdiW1unz6dPuiP5yw24ENrv1-49kXqBnx4/edit#gid=0
Here is a copy of the code I am using:
function ClearCells() {
var sheet = SpreadsheetApp.getActive().getSheetByName('CALCULATOR');
sheet.getRange('G9:H9').clearContent();
sheet.getRange('G11').clearContent();
sheet.getRange('G14:H14').clearContent();
sheet.getRange('G6').clearContent();
sheet.getRange('I6').clearContent();
sheet.getRange('I17:I21').clearContent();
sheet.getRange('I24:I29').clearContent();
sheet.getRange('I32').clearContent();
sheet.getRange('K5').clearContent();
sheet.getRange('K15').clearContent();
}
function FinalizePrice() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sourceRangeFL = ss.getRangeByName('FirstLast');
const sourceValsFL = sourceRangeFL.getValues().flat();
const sourceRangeEN = ss.getRangeByName('EntityName');
const sourceValsEN = sourceRangeEN.getValues().flat();
const sourceRangeEP = ss.getRangeByName('EmailPhone');
const sourceValsEP = sourceRangeEP.getValues().flat();
const sourceRangeRT = ss.getRangeByName('ReturnType');
const sourceValsRT = sourceRangeRT.getValues().flat();
const sourceRangeRE = ss.getRangeByName('Returning');
const sourceValsRE = sourceRangeRE.getValues().flat();
const sourceRangeBQ = ss.getRangeByName('BasicQuestions');
const sourceValsBQ = sourceRangeBQ.getValues().flat();
const sourceRangeSEQ = ss.getRangeByName('SchEQuestions');
const sourceValsSEQ = sourceRangeSEQ.getValues().flat();
const sourceRangeEQ = ss.getRangeByName('EntityQuestions');
const sourceValsEQ = sourceRangeEQ.getValues().flat();
const sourceRangePYP = ss.getRangeByName('PYP');
const sourceValsPYP = sourceRangePYP.getValues().flat();
const sourceRangeADJ = ss.getRangeByName('Adjustment')
const sourceValsADJ = sourceRangeADJ.getValues().flat();
const sourceRangeAN = ss.getRangeByName('AdjustmentNote')
const sourceValsAN = sourceRangeAN.getValues().flat();
const sourceVals = [...sourceValsFL, ...sourceValsEN, ...sourceValsEP, ...sourceValsRT, ...sourceValsRE, ...sourceValsBQ, ...sourceValsSEQ, ...sourceValsEQ, ...sourceValsPYP, ...sourceValsADJ, ...sourceValsAN]
console.log(sourceVals)
const anyEmptyCell = sourceVals.findIndex(cell => cell === "");
if(anyEmptyCell !== -1){
const ui = SpreadsheetApp.getUi();
ui.alert(
"Input Incomplete",
"Please enter a value in ALL input cells before submitting",
ui.ButtonSet.OK
);
return;
}
const date = new Date();
const email = Session.getActiveUser().getEmail();
const data = [date, email, ...sourceVals];
const destinationSheet = ss.getSheetByName("DataLog");
destinationSheet.appendRow(data);
console.log(data);
sourceRangeFL.clearContent();
sourceRangeEN.clearContent();
sourceRangeEP.clearContent();
sourceRangeRT.clearContent();
sourceRangeRE.clearContent();
sourceRangeBQ.clearContent();
sourceRangeSEQ.clearContent();
sourceRangeEQ.clearContent();
sourceRangePYP.clearContent();
sourceRangeADJ.clearContent();
sourceRangeAN.clearContent();
ss.toast("Success: Item added to the Data Log!");
}
I know this is incomplete but for the purpose of discussion here's how I would clear content in your situation.
function ClearCells() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]).getRanges().forEach(r => r.clearContent();)
}
If you wished to append the values of your individual ranges into a row you could do it like this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(osh.getLastRow() + 1, 1, 1, vs.length).setValues([vs]);
}
But I'm guessing that you want to skip over cell functions and other columns so let me know what you want and may be we can find a solution that fits your needs
The breakUpRangeList function is something I wrote a while back to break up ranges into their individual cells which I find easier to deal with.
function breakUpRangeList(ss=SpreadsheetApp.getActive(),sh=ss.getSheetByName("Sheet0"),rgl) {
let b = [];
rgl.getRanges().forEach(rg => {
rg.getValues().forEach((r,i) => {
let row = rg.getRow() + i;
r.forEach((c, j) => {
let col = rg.getColumn() + j;
b.push(sh.getRange(row, col).getA1Notation())
})
})
})
b = [...new Set(b)];
Logger.log(JSON.stringify(b));
return sh.getRangeList(b);
}
Try this:
function appendRangeValues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const osh = ss.getSheetByName('Sheet1')
const rgl = sh.getRangeList(["G9", "G11", "G14:H14", "G6", "I6", "I17:I21", "I24:I29", "I32", "K5", "K15"]);
const rglb = breakUpRangeList(ss,sh,rgl);
const vs = rglb.getRanges().map(r => r.getValue());
Logger.log(JSON.stringify(vs))
osh.getRange(getColumnHeight(3,osh,ss) + 1, 1, 1, vs.length).setValues([vs]);
}
function getColumnHeight(col, sh, ss) {
var ss = ss || SpreadsheetApp.getActive();
var sh = sh || ss.getActiveSheet();
var col = col || sh.getActiveCell().getColumn();
var rcA = [];
if (sh.getLastRow()){ rcA = sh.getRange(1, col, sh.getLastRow(), 1).getValues().flat().reverse(); }
let s = 0;
for (let i = 0; i < rcA.length; i++) {
if (rcA[i].toString().length == 0) {
s++;
} else {
break;
}
}
return rcA.length - s;
}
hope you are doing well.
I'm trying to sort a row that contains 18 cells based on checkboxes. Each checkbox assigns a task to each person and I basically would like to group people who are working on each task.
People with tasks
As you can see in the screenshot it's difficult to identify each task. I would like that all the people who are working on the "new" task to be together and not separated and People who are working on the "Open" task to be in a group below the "New" task
Another problem I have is that I'm getting these names (column A) from a formula based on another list.
Do you think is there a way to this action works? or it's not possible? I found this code based on other values but I'm not sure if I could modify it to achieve the action I want. This is the code:
function autoSort(e) {
const row = e.range.getRow()
const column = e.range.getColumn()
const ss = e.source
const currentSheet = ss.getActiveSheet()
const currentSheetName = currentSheet.getSheetName()
if(!(currentSheetName === "Assignation" && column === 2 && row >= 2)) return
const ws = ss.getSheetByName("Assignation")
const range = ws.getRange (2,2, ws.getLastRow()-1,7)
range.sort({column: 1, ascending: false})
}
function onEdit(e){
const row = e.range.getRow()
const column = e.getRange.getColumn()
if(!(column === 1 && row >= 2)) return
autoSort()
}
Also, this is the sheet I'm using, it's a copy so you can modify it as you want.
https://docs.google.com/spreadsheets/d/1f5Mu2SKPAYKC9UcpDOkGdtS6JeFDHcsqPMmuZyz-zWA/edit?usp=sharing
I hope anyone can help me with this. Thank you!
I weighted them by their column index
function groupsort() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const vs = sh.getRange(2, 1, sh.getLastRow() - 1, 18).getValues();
const idxs = [1, 2, 3, 4];
vs.sort((a, b) => {
let va = idxs.reduce((acc, idx, i) => {
acc += a[idx] ? idx : 5;
return acc;
}, 0)
let vb = idxs.reduce((acc, idx, i) => {
acc += b[idx] ? idx : 5;
return acc;
}, 0)
return va - vb;
});
sh.getRange(2, 1, vs.length, vs[0].length).setValues(vs);
}
Sheet0 Before:
Sheet0 After:
I have one 'reporting_sheet' and one 'data_sheet'. I would like to make the 'reporting_sheet' check for data that exists in the 'data_sheet' but doesn't exist in the 'reporting_sheet' and move that over as soon as the data is entered in 'data_sheet' (On Edit)
reporting_sheet :
data_sheet :
result expected in reporting_sheet :
I tried this:
function onEdit(e) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [sheet1, sheet2] = ["reporting_sheet", "data_sheet"].map(s => ss.getSheetByName(s)); // set sheet names
var Avals = sheet1.getRange("A1:A").getValues(); // getting last row of column A
var lastRowSheet1 = Avals.filter(String).length;
const sheet1Obj = sheet1.getRange("A2:A" + lastRowSheet1).getValues().reduce((o, [a]) => (o[a] = true, o), {});
const sheet2Values = sheet2.getRange("A2:E" + sheet2.getLastRow()).getValues();
const values = sheet2Values.filter(([a]) => !sheet1Obj[a]);
if (values.length == 0) return;
sheet1.getRange(lastRowSheet1 + 1, 1, values.length, 5).setValues(values);
// sheet1.sort(1);
}
Issue with the code : It only compares the first column between both sheets and dumps the data if there's a new unique value in the A column of 'data_sheet' which doesn't exist in the 'reporting_sheet'.
How do I make it look for columns A to E?
Sorry I am a newbie.
Link - https://docs.google.com/spreadsheets/d/e/2PACX-1vSDW0zMBpBWK-RUMyn6JOqE0AvdwNdOCRVyE0UmVUUq2nfaS8koGxs_sCXQ0MApNk7t4GHx0GR2e-Ld/pubhtml
If you make sure that all the dates have the same format this modification should do the job:
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [sheet1, sheet2] = ["reporting_sheet", "data_sheet"].map(s => ss.getSheetByName(s));
const lastRowSheet1 = sheet1.getLastRow();
// make 'keys' (A+B+C+D+E) from the rows of reporting_sheet
const keys = sheet1.getRange("A2:E" + lastRowSheet1).getDisplayValues()
.map(x => x.join('')); // [a,b,c,d,e] => 'abcde'
// get all the rows from data_sheet
// and keep only the ones which A+B+C+D+E aren't among the 'keys'
const rows = sheet2.getRange("A2:E" + sheet2.getLastRow()).getDisplayValues()
.filter(x => !keys.includes(x.join('')));
// return if there is no rows
if (rows.length == 0) return;
// set the rows at end of the reporting_sheet
sheet1.getRange(lastRowSheet1 + 1, 1, rows.length, rows[0].length).setValues(rows);
// sheet1.sort(1);
}
Try this:
function lfunko() {
const ss = SpreadsheetApp.getActive();
const rsh = ss.getSheetByName("Sheet0");
const rvs = rsh.getRange(3,1,rsh.getLastRow() - 2,5).getDisplayValues();
const dsh = ss.getSheetByName("Sheet1");
const dvs = dsh.getRange(3,1,dsh.getLastRow() - 2,5).getDisplayValues()
const rA = rvs.map(r => r.join(""));
dvs.forEach((r,i) => {
if(!~rA.indexOf(r.join(""))) {
rsh.appendRow(r)
}
})
}
I'd like to have all cells in Column A of Sheet1 communicate with all cells in Column B of Sheet2 to check if the exact text in any cell of Column B already exists. If it does exist, as soon as the duplicate text is entered in a cell, I would like that exact cell where the duplicate text was entered to change its text to "Already exits"
Is there a way to do this through Google AppScript with the onEdit function?
https://i.stack.imgur.com/DpzBZ.png
Already Exisits
function onEdit(e) {
const sh = e.range.getSheet();
const names = ['Sheet0', 'Sheet1'];//Sheet names two check
const idx = names.indexOf(sh.getName());
if (~idx) {
const rA1 = e.range.getA1Notation();
const shts = names.map(name => e.source.getSheetByName(name));
shts.filter(s => shts.indexOf(s) != idx).forEach(s => {
if(s.getRange(rA1).getValue() == e.value ) {
e.range.setValue("Already Exists");
}
});
}
}
Version 2:
Includes specified column for each sheet
function onEdit(e) {
const sh = e.range.getSheet();
const names = ['Data Collected', 'Imports'];//Sheet names two check
const col = [1,3];
const idx = names.indexOf(sh.getName());
if (~idx && e.range.columnStart == col[idx]) {
const rA1 = e.range.getA1Notation();
const shts = names.map(name => e.source.getSheetByName(name));
shts.filter(s => shts.indexOf(s) != idx).forEach(s => {
if(s.getRange(rA1).getValue() == e.value ) {
e.range.setValue("Already Exists");
}
});
}
}
Check for Sheet1 A in Sheet 2 C
Not written as an onEdit triggered function
function checkForAinC() {
const ss = SpreadsheetApp.getActive();
const sh1 = ss.getSheetByName("Sheet0");
const sh2 = ss.getSheetByName('Sheet1');
const vs1 = [... new Set(sh1.getRange('A2:A' + sh1.getLastRow()).getValues().flat())];
const vs2 = sh2.getRange('C2:C' + sh2.getLastRow()).getValues().flat();
let o = vs1.map((e, i) => {
let f = 0;
let idx = -1;
do {
idx = vs2.indexOf(e, f)
if (~idx) {
sh2.getRange(idx + 2, 3).setValue("Already Exists");
//SpreadsheetApp.flush();
f = idx + 1;
}
} while (~idx);
})
}