Issues reading cells using map() in a range in Google Sheets - arrays

I have a function that works perfectly when assigning it on a per-cell basis, but when I try to create an array using the map() function, cell values are not being read properly. Can anyone help on this?
I've tried to read up on this and it might have something to do with Google Sheets map() function not being able to handle certain scenarios because all calcs are being done server-side? Not sure.
Anyway, I'm trying to read dates from one column, and see whether there was a discount in another column, and based on the date and discount token "DSC", return a percent discount value: 25% for or 30% if between 2 dates.
Picture of no 30% values in 3rd column
Below is the code for the working function (assigned to each cell) and non-working map() function assigned to a range then a Google Sheets link:
// ==================================== Using .map
/**
#customfunction
*/
function Disc_Perc_Map (input1, input2){ // input1 is product ID column, input2 is Date
var Date_1_a = Date.parse(String("1/3/2017")) ;// put start date of discount change here
var Date_1_b = Date.parse(String("1/4/2017")) ;// put end date of discount change here
var Disc_Def = .25;
var Disc_1 = .30;
if (input1.map){ // Checks if array
return input1.map(Disc_Perc_Map); // added along with added brace at end
} else {
// Main part of code
if (String(input1).indexOf(",") >-1) { // if there are commas
Cell_Array = input1.split(",");
Cell_Len = input1.split(',').length;
var Date_Num = Date.parse(String(input2));
if (Date_Num >= Date_1_a && Date_Num <= Date_1_b){
return Disc_1;
} else {
return Disc_Def;
}
} else { // if there are NO commas
return "";
//Cell_Len = 1;
}
}
}
// ==================================== without using .map
/**
#customfunction
*/
function Disc_Perc_No_Map(input1, input2) { // input1 is product ID column, input2 is Date
var Date_1_a = Date.parse(String("1/3/2017")) ;// put start date of discount change here
var Date_1_b = Date.parse(String("1/4/2017")) ;// put end date of discount change here
var Disc_Def = .25;
var Disc_1 = .30;
// Main part of code
if (String(input1).indexOf(",") > -1) { // if there are commas
Cell_Array = input1.split(",");
Cell_Len = input1.split(',').length;
var Date_Num = Date.parse(String(input2));
if (Date_Num >= Date_1_a && Date_Num <= Date_1_b){
return Disc_1;
} else {
return Disc_Def;
}
} else { // if there are NO commas
return "";
//Cell_Len = 1;
}
}
Link to example Google Sheets:
Any help great appreciated.

I think that the reason of issue is as follows.
When Disc_Perc_Map() do the callback, input2 becomes the index of input1.map().
So the values of input2 inputted from the custom function is removed from the running script.
In order to avoid this issue, how about this modification?
Modification points :
Backup input2 and use in the callback.
In order to use the backed up input2, add a counter.
Retrieve the date value using the counter.
Modified script :
var cnt = -1; // Added
var input2bk; // Added
function Disc_Perc_Map (input1, input2){ // input1 is product ID column, input2 is Date
if (isNaN(input2)) input2bk = input2; // Added
var Date_1_a = Date.parse(String("1/3/2017")) ;// put start date of discount change here
var Date_1_b = Date.parse(String("1/4/2017")) ;// put end date of discount change here
var Disc_Def = .25;
var Disc_1 = .30;
if (input1.map){ // Checks if array
return input1.map(Disc_Perc_Map); // added along with added brace at end
} else {
cnt += 1; // Added
// Main part of code
if (String(input1).indexOf(",") > -1) { // if there are commas
Cell_Array = input1.split(",");
Cell_Len = input1.split(',').length;
var Date_Num = Date.parse(String(input2bk[cnt][0])); // Modified
if (Date_Num >= Date_1_a && Date_Num <= Date_1_b){
return Disc_1;
} else {
return Disc_Def;
}
} else { // if there are NO commas
return "";
//Cell_Len = 1;
}
}
}
Another pattern :
As an another pattern, how about this modification? This is also the same result with above.
function Disc_Perc_Map (input1, input2) { // input1 is product ID column, input2 is Date
var Date_1_a = Date.parse(String("1/3/2017")) ;// put start date of discount change here
var Date_1_b = Date.parse(String("1/4/2017")) ;// put end date of discount change here
var Disc_Def = .25;
var Disc_1 = .30;
return input1.map(function(e, i) {
// Main part of code
if (String(e[0]).indexOf(",") > -1) { // if there are commas
Cell_Array = e[0].split(",");
Cell_Len = e[0].split(',').length;
var Date_Num = Date.parse(String(input2[i][0])); // Modified
if (Date_Num >= Date_1_a && Date_Num <= Date_1_b) {
return [Disc_1];
} else {
return [Disc_Def];
}
} else { // if there are NO commas
return [""];
//Cell_Len = 1;
}
});
}
If I misunderstand your question, I'm sorry.

Related

comparing json array rows with value on destination rows (apps script)

I've use this script before, and work well, here's script, This script copies the values and paste to last row in google sheet,
function doPost(request = {}) {
const { parameter, postData: { contents, type } = {} } = request; //request data
const { dataReq = {} } = JSON.parse(contents); //content
const { fname = {} } = JSON.parse(contents); //function name
const response = {
status: "function not found: " + fname, // prepare response in function not found
data2: dataReq
}
switch (fname) { //function selection
case 'pasteData':
var output = JSON.stringify(pasteDAta(dataReq)) //call function with data from request
break
default:
var output = JSON.stringify(response)
break
}
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.JSON); //response to frontend
}
function pasteDAta(dataReq) {
const id = '1_27rjNQmlXrwVKpLWUbGrJYPJufGRa7Dk-XEKcNAHr0'; //id of Google Sheet
var sheet = SpreadsheetApp.openById(id).getSheetByName('Sheet1'); //sheet
var headings = sheet.getDataRange().getValues()[0]; //Headers
var values = dataReq.map((a) => {
let holder = [];
for (x in headings) {
let output = (headings[x] in a) ? a[headings[x]] : '';
holder.push(output);
}
return holder;
});
var len = values.length;
sheet.getRange(sheet.getLastRow() + 1, 1, len, values[0].length).setValues(values);
return "Numbers of sheets added: " + len;
}
I want this script to be able to check the row values ​​in column b (source [json]), if the value in the source rows in column b is the same as the value in the destination rows in column b (google sheet) then the values ​​are replaced all but if not, the values ​​are copied to the last rows. If it is possible, can anyone give me a modified working script?
Example
First condition; Before (Destination Sheet)
Date
Code
Name
Grade
02/04/21
Math1
John
80
02/04/21
Math2
John
80
Expected results
After replacing (from JSON)- if Column B (Code) is the same as the source
Date
Code
Name
Grade
02/04/21
Math1
Dare
78
02/04/21
Math2
Brian
90
Second condition; Before (Destination Sheet)
Date
Code
Name
Grade
02/04/21
Bio1
Anton
78
02/04/21
Bio2
Julian
65
Expected results
After after appending to last row (from JSON)- if Column B isn't same as the source
Date
Code
Name
Grade
02/04/21
Bio1
Anton
78
02/04/21
Bio2
Julian
65
02/04/21
Math1
Dare
78
02/04/21
Math2
Brian
90
Try to change the function pasteDAta() this way:
function pasteDAta(dataReq) {
const id = "1_27rjNQmlXrwVKpLWUbGrJYPJufGRa7Dk-XEKcNAHr0";
var sheet = SpreadsheetApp.openById(id).getSheetByName("Sheet1");
// get header and the rest data from the sheet
var [ headings, ...data ] = sheet.getDataRange().getValues();
var values = dataReq.map((a) => {
let holder = [];
for (x in headings) {
let output = headings[x] in a ? a[headings[x]] : "";
holder.push(output);
}
return holder;
});
var len = values.length;
var new_values = []; // values to add at the bottom of the sheet
var col_b = data.map(x => x[1]); // get column B from the data
values.forEach(row => {
// find an index of the row with the same value in cell B
var row_index = col_b.indexOf(row[1]);
// if nothing was found add the row to the new values
if (row_index == -1) new_values.push(row);
// else change the found row on the sheet
else sheet.getRange(row_index + 2, 1, 1, row.length).setValues([row]); // '+2' due the header
})
// add the new values at the bottom of the sheet
if (new_values.length > 0)
sheet.getRange(sheet.getLastRow() + 1, 1, len, new_values[0].length).setValues(new_values);
return "Numbers of sheets added: " + len;
}

How can I use a script to delete all data on a google spreadsheet?

I have a google form that exports all of the answers to a google sheet. I also have a script that exports my google sheets (aka my form answers) and converts it to json. The problem is, If I make another response on google forms, my script converts both responses into a json when I only want the most recent response to converted. What I need is an addition to my script to delete all rows after it exports the data so when I try again it doesn't take the old responses.
// Tweak the makePrettyJSON_ function to customize what kind of JSON to export.
var FORMAT_ONELINE = 'One-line';
var FORMAT_MULTILINE = 'Multi-line';
var FORMAT_PRETTY = 'Pretty';
var LANGUAGE_JS = 'JavaScript';
var LANGUAGE_PYTHON = 'Python';
var STRUCTURE_LIST = 'List';
var STRUCTURE_HASH = 'Hash (keyed by "id" column)';
/* Defaults for this particular spreadsheet, change as desired */
var DEFAULT_FORMAT = FORMAT_PRETTY;
var DEFAULT_LANGUAGE = LANGUAGE_JS;
var DEFAULT_STRUCTURE = STRUCTURE_LIST;
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{name: "Export JSON for this sheet", functionName: "exportSheet"},
{name: "Export JSON for all sheets", functionName: "exportAllSheets"}
];
ss.addMenu("Export JSON", menuEntries);
}
function makeLabel(app, text, id) {
var lb = app.createLabel(text);
if (id) lb.setId(id);
return lb;
}
function makeListBox(app, name, items) {
var listBox = app.createListBox().setId(name).setName(name);
listBox.setVisibleItemCount(1);
var cache = CacheService.getPublicCache();
var selectedValue = cache.get(name);
Logger.log(selectedValue);
for (var i = 0; i < items.length; i++) {
listBox.addItem(items[i]);
if (items[1] == selectedValue) {
listBox.setSelectedIndex(i);
}
}
return listBox;
}
function makeButton(app, parent, name, callback) {
var button = app.createButton(name);
app.add(button);
var handler = app.createServerClickHandler(callback).addCallbackElement(parent);;
button.addClickHandler(handler);
return button;
}
function makeTextBox(app, name) {
var textArea = app.createTextArea().setWidth('100%').setHeight('200px').setId(name).setName(name);
return textArea;
}
function exportAllSheets(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheetsData = {};
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
var rowsData = getRowsData_(sheet, getExportOptions(e));
var sheetName = sheet.getName();
sheetsData[sheetName] = rowsData;
}
var json = makeJSON_(sheetsData, getExportOptions(e));
displayText_(json);
}
function exportSheet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rowsData = getRowsData_(sheet, getExportOptions(e));
var json = makeJSON_(rowsData, getExportOptions(e));
displayText_(json);
}
function getExportOptions(e) {
var options = {};
options.language = e && e.parameter.language || DEFAULT_LANGUAGE;
options.format = e && e.parameter.format || DEFAULT_FORMAT;
options.structure = e && e.parameter.structure || DEFAULT_STRUCTURE;
var cache = CacheService.getPublicCache();
cache.put('language', options.language);
cache.put('format', options.format);
cache.put('structure', options.structure);
Logger.log(options);
return options;
}
function makeJSON_(object, options) {
if (options.format == FORMAT_PRETTY) {
var jsonString = JSON.stringify(object, null, 4);
} else if (options.format == FORMAT_MULTILINE) {
var jsonString = Utilities.jsonStringify(object);
jsonString = jsonString.replace(/},/gi, '},\n');
jsonString = prettyJSON.replace(/":\[{"/gi, '":\n[{"');
jsonString = prettyJSON.replace(/}\],/gi, '}],\n');
} else {
var jsonString = Utilities.jsonStringify(object);
}
if (options.language == LANGUAGE_PYTHON) {
// add unicode markers
jsonString = jsonString.replace(/"([a-zA-Z]*)":\s+"/gi, '"$1": u"');
}
return jsonString;
}
function displayText_(text) {
var output = HtmlService.createHtmlOutput("<textarea style='width:100%;' rows='20'>" + text + "</textarea>");
output.setWidth(400)
output.setHeight(300);
SpreadsheetApp.getUi()
.showModalDialog(output, 'Exported JSON');
}
// getRowsData iterates row by row in the input range and returns an array of objects.
// Each object contains all the data for a given row, indexed by its normalized column name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - columnHeadersRowIndex: specifies the row number where the column names are stored.
// This argument is optional and it defaults to the row immediately above range;
// Returns an Array of objects.
function getRowsData_(sheet, options) {
var headersRange = sheet.getRange(1, 1, sheet.getFrozenRows(), sheet.getMaxColumns());
var headers = headersRange.getValues()[0];
var dataRange = sheet.getRange(sheet.getFrozenRows()+1, 1, sheet.getMaxRows(), sheet.getMaxColumns());
var objects = getObjects_(dataRange.getValues(), normalizeHeaders_(headers));
if (options.structure == STRUCTURE_HASH) {
var objectsById = {};
objects.forEach(function(object) {
objectsById[object.id] = object;
});
return objectsById;
} else {
return objects;
}
}
// getColumnsData iterates column by column in the input range and returns an array of objects.
// Each object contains all the data for a given column, indexed by its normalized row name.
// Arguments:
// - sheet: the sheet object that contains the data to be processed
// - range: the exact range of cells where the data is stored
// - rowHeadersColumnIndex: specifies the column number where the row names are stored.
// This argument is optional and it defaults to the column immediately left of the range;
// Returns an Array of objects.
function getColumnsData_(sheet, range, rowHeadersColumnIndex) {
rowHeadersColumnIndex = rowHeadersColumnIndex || range.getColumnIndex() - 1;
var headersTmp = sheet.getRange(range.getRow(), rowHeadersColumnIndex, range.getNumRows(), 1).getValues();
var headers = normalizeHeaders_(arrayTranspose_(headersTmp)[0]);
return getObjects(arrayTranspose_(range.getValues()), headers);
}
// For every row of data in data, generates an object that contains the data. Names of
// object fields are defined in keys.
// Arguments:
// - data: JavaScript 2d array
// - keys: Array of Strings that define the property names for the objects to create
function getObjects_(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty_(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
// Returns an Array of normalized Strings.
// Arguments:
// - headers: Array of Strings to normalize
function normalizeHeaders_(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader_(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
// Normalizes a string, by removing all alphanumeric characters and using mixed case
// to separate words. The output will always start with a lower case letter.
// This function is designed to produce JavaScript object property names.
// Arguments:
// - header: string to normalize
// Examples:
// "First Name" -> "firstName"
// "Market Cap (millions) -> "marketCapMillions
// "1 number at the beginning is ignored" -> "numberAtTheBeginningIsIgnored"
function normalizeHeader_(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum_(letter)) {
continue;
}
if (key.length == 0 && isDigit_(letter)) {
continue; // first character must be a letter
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty.
// Arguments:
// - cellData: string
function isCellEmpty_(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise.
function isAlnum_(char) {
return char >= 'A' && char <= 'Z' ||
char >= 'a' && char <= 'z' ||
isDigit_(char);
}
// Returns true if the character char is a digit, false otherwise.
function isDigit_(char) {
return char >= '0' && char <= '9';
}
// Given a JavaScript 2d Array, this function returns the transposed table.
// Arguments:
// - data: JavaScript 2d Array
// Returns a JavaScript 2d Array
// Example: arrayTranspose([[1,2,3],[4,5,6]]) returns [[1,4],[2,5],[3,6]].
function arrayTranspose_(data) {
if (data.length == 0 || data[0].length == 0) {
return null;
}
var ret = [];
for (var i = 0; i < data[0].length; ++i) {
ret.push([]);
}
for (var i = 0; i < data.length; ++i) {
for (var j = 0; j < data[i].length; ++j) {
ret[j][i] = data[i][j];
}
}
return ret;
} ```
This isn't my original script and I am not that knowledgeable in this space so any help is appreciated.
Clear contents on all sheets
function clearAll() {
SpreadsheetApp.getActive().getSheets().forEach(sh => sh.clear());
}

use ISFORMULA with ARRAYFORMULA on a range of cells

I have a row of cells, some with a formula, and some without.
I am trying to use ARRAYFORMULA WITH IFFORMULA to see if a cell has a formula or not. This is the function I am using:
=ARRAYFORMULA(ISFORMULA(B2:B))
But it just outputs one single value.
There are other things I need to do for each row/cell which is why I need ARRAYFORMULA.
Is there another way to get ISFORMULA to work with ARRAYFORMULA?
not all functions are convertible into ArrayFormula. ISFORMULA is one of those...
the best you can do is pre-program it like:
={ISFORMULA(B2);
ISFORMULA(B3);
ISFORMULA(B4);
ISFORMULA(B5);
ISFORMULA(B6)}
the next best thing you can do is to use conditional formatting to color it like:
green color:
=ISFORMULA($B2)
red color:
=NOT(ISFORMULA($B2))*(B2<>"")
UPDATE
now possible with lambda:
=BYROW(B2:B6; LAMBDA(x; ISFORMULA(x)))
This is so frustrating. I myself have made areFormula() by my needs. Not so elegant, but working. It takes a range of row or column (if area, treated as a row), and returns a row.
function areFormula(rowOrColumn) {
// takes a range as input parameter
// from https://webapps.stackexchange.com/a/92156
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=\w+\((.*)\)/i)[1].split('!');
try {
if (args.length == 1) {
var range = sheet.getRange(args[0]);
}
else {
sheet = ss.getSheetByName(args[0].replace(/'/g, ''));
range = sheet.getRange(args[1]);
}
}
catch(e) {
throw new Error(args.join('!') + ' is not a valid range');
}
rowOrColumn = range;
//here starts my code
let values = rowOrColumn.getValues();
let isItRow = true;
if(values.length == 1) {
isItRow = false;
values = values[0];
}
else {
values = values.map(el => el[0]);
}
let result = [];
let x = 0, y = 0;
for(let i = 0; i < values.length; i++) {
const cell = rowOrColumn.getCell(1+y, 1+x);
result.push(cell.getFormula() ? true : false);
if(isItRow)
y++;
else
x++;
}
console.log(result);
return(result);
}

Cant make the condition formatting to work with a script in Google sheets

I am trying to color format a row in a spreadsheet where I run a loop to check if any of the values is equal to 0 and if it is equal to 0 to color it red and if not color it red.
I know I can do it with conditional format but I need this to activate after I press a button included in the sheet, so I need a script added to the button.
I have managed to write this script but it seems it color only the last cell from the range.
Can you please tell me what am I doing wrong since I am new to coding.
function FormatColor() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Acontobetaling');
var range = sheet.getRange('D34:AA34');
var values = [];
var cell = [];
for (var i = 1; i <= 24; i=i+1)
cell = range.getCell(1, i)
values = cell.getValue();
if (values = '0') {
cell.setBackground('red');
} else {
cell.setBackground('green');
}
}
I made it work, if someone is having the same problem you can use this
Anyway if you can provide me with better or fast way to do it you are welcome to show me how :)
function CopyData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Acontobetaling');
var rangeColor = sheet.getRange('D34:AA34');
var values = [];
var cell = [];
var colors = [];
for (var i = 1; i <= 24; i=i+1) {
cell = rangeColor.getCell(1, i);
values = cell.getValue();
if (values == '0') {
colors = cell.setBackground('red');
} else {
colors = cell.setBackground('green');
}
}

Angular Filter by Date/Time Range

I'm trying to build an event date filter by passing in a time range. I was able to filter for events that are the same date as today but need help to filter events from last week, last month, etc...
$scope.eventDateFilter = function(column) {
if(column === 'today') {
$scope.dateRange = $scope.dateToday;
} else if (column === 'pastWeek') {
//need logic
} else if (column === 'pastMonth') {
//need logic
} else if (column === 'future') {
//need logic
} else {
$scope.dateRange = "";
}
}
Here's my fiddle:
http://jsfiddle.net/c6BfQ/3/
Your help is greatly appreciated.
I would use a custom filter. Here is one I used to filter things created in the last two days, it should give you an idea of how to do yours.
.filter('dateFilter', function() {
return function (objects) {
var filtered_list = [];
for (var i = 0; i < objects.length; i++) {
var two_days_ago = new Date().getTime() - 2*24*60*60*1000;
var last_modified = new Date(objects[i].date_created).getTime();
if (two_days_ago <= last_modified) {
filtered_list.push(objects[i]);
}
}
return filtered_list;
}
});

Resources