GAS - Using Map to check an array against another array - google-apps-script

I'm trying to check every individual value in array 'chkArr' to check if that string occurs in another array 'arr' and if it does return blank "". I have it working for the first value "text" but it isn't looping though the other element of chkArr the way I expected.
If I manually change the loop variable 'rep' I get the correct result for whichever array element is selected. Can anyone see what I'm doing wrong? Perhaps I need to move the loop out of the filterLogic function?
function myFunction() {
var arr = [["random.text1"],[6.0],["othermsg"],[8],["testtext2"]];
var newArr = arr.map(filterLogic);
Logger.log(newArr);
}
var filterLogic = function(item){
var chkArr = [["text"],[6.0],["other"]["other"]];
for (var rep = 0; rep < chkArr.length; rep++) {
if(item.toString().indexOf(chkArr[rep]) === -1){return item;} else {return [""];}
}
}
So the result I would hope to get from the above would be:
[[], [], [], [8.0], []]
However what I actually get is:
[[], [6.0], [othermsg], [8.0], []]

I found a way to do it which does give me the result I need. I'm sure there is a better way still to do this so if anyone has a neater method please share.
My solution.
function myFunction() {
var arr = [["random.text1"],[6.0],["othermsg"],[8],["testtext2"]];
rep = 0;
for (rep = 0; rep <= 2; rep++){
var newArr = arr.map(filterLogic);
Logger.log(newArr);
}
}
var filterLogic = function(item){
var chkArr = [["text"],[6],["other"]];
if(item.toString().indexOf(chkArr[rep]) === -1){return item;} else {return [""];}
}

Related

Using an array or the map function to improving on a nested loop performance to change/substitute cell colours in googlescript

Hi still a relative newbie here...I've got some nested loops which cycle through each cell in a sheet range looking for a background color and if it finds a match then the background colour is changed. Yes - I am sorry for this approach(!). Am thinking I must be able to get the background colours via an array and then apply a transformation to the array using .map(?) but have experimented and haven't worked it out so far. Other examples on S/O don't show a substitute approach that I have been able to apply across. Any advice/thoughts would be greatly appreciated. Thanks
function myColorFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("My Sheet");
for (var y = 1; y < 70; y++){
for (var x = 1; x < 50; x++){
var bghex = ss.getRange(y,x).getBackground();
if(bghex == '#001d66') { //occasional cell colour
ss.getRange(y,x).setBackground("#027db6");
}
else if (bghex == '#00288b') { //main bkgrnd colour
ss.getRange(y,x).setBackground("#0297db");
}
else if (bghex == '#fbf025') { //yellow fill-in
ss.getRange(y,x).setBackground("#fdd201");
}
}
Logger.log('Line ' + y + ' complete');
}
}
My approach would be like this, using a nested .map() function and using getBackgrounds() first to populate the 2D Array of cell colors, then replacing it using an if-else statement, from there you can use setBackgrounds() to apply the changes to the whole range.
function myColorFunction2() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("My Sheet");
var range = ss.getRange(1,1,70,50);
var bghex = range.getBackgrounds();
bghex.map(row => row.map((elem,index) => {
if(elem == '#001d66'){
row[index] = '#027db6';
}
else if (elem == '#00288b') {
row[index] = '#0297db';
}
else if (elem == '#fbf025') {
row[index] = '#fdd201';
}
}));
range.setBackgrounds(bghex);
Logger.log(bghex);
}
Output:
This amount took about only 1 second runtime duration:
Ref:
setBackgrounds()
getBackgrounds()
Array.prototype.map()
Instead of using getBackground(), use getBackgrounds(), this way instead of getting individual values, you get an array where you can apply the map() function.
Here is an example replacing red backgrounds.
function myColorFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("My Sheet");
var range = sheet.getRange("A1:B10");
var backgrounds = range.getBackgrounds(); // Return 2D arr
// Applies map() function on each row
for (let i = 0; i < backgrounds.length; i++) {
backgrounds[i] = backgrounds[i].map(function(item) { return item == "#ff0000" ? "#0000ff" : item; });
}
// Set new backgrounds
range.setBackgrounds(backgrounds);
}

Google APP script to convert value into TRUE for setting MultipleChoice answer key

I’ve searched a script that automates google form from my question banks (g-sheets).
All of my questions are multiple choice. I already added the setPoints() and setRequired(), however, for the answer key, I can’t find a way on how to script the value of my Answer’s column into TRUE when it meets the criteria. Below are the codes:
function getSpreadsheetData(sheetName) {
// This function gives you an array of objects modeling a worksheet's tabular data, where the first items — column headers — become the property names.
var arrayOfArrays = SpreadsheetApp.openById("GoogleSheetID").getSheetByName(sheetName || 'question').getDataRange().getValues();
console.log("Spreadsheet: " + arrayOfArrays[0]);
console.log("Spreadsheet: " + arrayOfArrays[1]);
var headers = arrayOfArrays.shift();
return arrayOfArrays.map(function (row) {
return row.reduce(function (memo, value, index) {
if (value) {
memo[headers[index]] = value;
}
return memo;
}, {});
});
}
function onOpen(e){
var form = FormApp.openById("GoogleFormID");
form.setTitle('DO ANY TITLE YOU WANT HERE');
form.setDescription('This is an example of Form generation');
getSpreadsheetData().forEach(function (row) {
var capitalizedName = row.Number.charAt(0).toUpperCase() + row.Number.slice(1);
console.log("Spreadsheet: " + capitalizedName);
form.addPageBreakItem()
.setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle(row.Questions)
.setPoints(1)
.setRequired(true)
.setChoices([
item.createChoice(row.Option1),
item.createChoice(row.Option2),
item.createChoice(row.Option3),
item.createChoice(row.Option4)
]);
});
}
function onSubmit(e) {
var form = FormApp.getActiveForm();
var items = form.getItems();
while(items.length > 0){
form.deleteItem(items.pop());
}
}
I’ve also search that to make choices the right answer, you just put TRUE or something like this:
item.createChoice(row.Option1, true),
item.createChoice(row.Option2, false),
item.createChoice(row.Option3, false),
item.createChoice(row.Option4, false)
However, this will only set Option1 as always the right answer. I want that TRUE or FALSE will be automatically place when found the right answer as stated in column G of my google sheets. Attach is my sheet:
GoogleSheet
This took me a while and I experience a weird issue, which is: After the Questions are set and the form saved, refreshing the page or trying to answer deletes all the options from one of the options (not always the same option).
I still have no clue why does this happen and I will investigate it later, but my code can give you an idea.
I changed your code a bit and added a new function which returns true or false for each option depending on the value of the G column:
form.addPageBreakItem()
.setTitle(capitalizedName);
var item = form.addMultipleChoiceItem();
item.setTitle(row.Questions)
.setPoints(1)
.setRequired(true)
.setChoices([
item.createChoice(row.Option1, checktrue(row, row.Option1)),
item.createChoice(row.Option2, checktrue(row, row.Option2)),
item.createChoice(row.Option3, checktrue(row, row.Option3)),
item.createChoice(row.Option4, checktrue(row, row.Option4))
]);
});
}
function checktrue(row, option){
var numRow = 2;
var sprsheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var colA = sprsheet.getRange("A2:A5").getValues();
for (var i = 0; i < colA.length; i++){
if (colA[i] == row.Number){
numRow += i;
}
}
var answer = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("G"+numRow).getValue();
if (option == answer){
return true
} else {
return false
}
}

How to get nested deep property value from JSON where key is in a variable?

I want to bind my ng-model with JSON object nested key where my key is in a variable.
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']"
Here I want to get value 5 from data JSON object.
I found the solution to convert "course.sections.chapter_index" to array notation like course['sections']['chapter_index'] this. but don't know how to extract value from data now
<script type="text/javascript">
var BRACKET_REGEXP = /^(.*)((?:\s*\[\s*\d+\s*\]\s*)|(?:\s*\[\s*"(?:[^"\\]|\\.)*"\s*\]\s*)|(?:\s*\[\s*'(?:[^'\\]|\\.)*'\s*\]\s*))(.*)$/;
var APOS_REGEXP = /'/g;
var DOT_REGEXP = /\./g;
var FUNC_REGEXP = /(\([^)]*\))?$/;
var preEval = function (path) {
var m = BRACKET_REGEXP.exec(path);
if (m) {
return (m[1] ? preEval(m[1]) : m[1]) + m[2] + (m[3] ? preEval(m[3]) : m[3]);
} else {
path = path.replace(APOS_REGEXP, '\\\'');
var parts = path.split(DOT_REGEXP);
var preparsed = [parts.shift()]; // first item must be var notation, thus skip
angular.forEach(parts, function (part) {
preparsed.push(part.replace(FUNC_REGEXP, '\']$1'));
});
return preparsed.join('[\'');
}
};
var data = {"course":{"sections":{"chapter_index":5}}};
var obj = preEval('course.sections.chapter_index');
console.log(obj);
</script>
Hope this also help others. I am near to close the solution,but don't know how can I get nested value from JSON.
This may be a good solution too
getDeepnestedValue(object: any, keys: string[]) {
keys.forEach((key: string) => {
object = object[key];
});
return object;
}
var jsonObject = {"address": {"line": {"line1": "","line2": ""}}};
var modelName = "address.line.line1";
var result = getDescendantPropValue(jsonObject, modelName);
function getDescendantPropValue(obj, modelName) {
console.log("modelName " + modelName);
var arr = modelName.split(".");
var val = obj;
for (var i = 0; i < arr.length; i++) {
val = val[arr[i]];
}
console.log("Val values final : " + JSON.stringify(val));
return val;
}
You are trying to combine 'dot notation' and 'bracket notation' to access properties in an object, which is generally not a good idea.
Source: "The Secret Life of Objects"
Here is an alternative.
var stringInput = 'course.sections.chapter_index'
var splitInput = stringInput.split(".")
data[splitInput[1]]][splitInput[2]][splitInput[3]] //5
//OR: Note that if you can construct the right string, you can also do this:
eval("data[splitInput[1]]][splitInput[2]][splitInput[3]]")
Essentially, if you use eval on a string, it'll evaluate a statement.
Now you just need to create the right string! You could use the above method, or tweak your current implementation and simply go
eval("data.course.sections.chapter_index") //5
Source MDN Eval docs.
var data = {
"course": {
"sections": {
"chapter_index": 5
}
}
};
var key = "course['sections']['chapter_index']";
var keys = key.replace(/'|]/g, '').split('[');
for (var i = 0; i < keys.length; i++) {
data = data[keys[i]];
}
console.log(data);
The simplest possible solution that will do what you want:
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']";
with (data) {
var value = eval(key);
}
console.log(value);
//=> 5
Note that you should make sure key comes from a trusted source since it is eval'd.
Using with or eval is considered dangerous, and for a good reason, but this may be one of a few its legitimate use cases.
If you don't want to use eval you can do a one liner reduce:
var data = {"course":{"sections":{"chapter_index":5}}};
var key = "course['sections']['chapter_index']"
key.split(/"|'|\]|\.|\[/).reduce((s,c)=>c===""?s:s&&s[c], data)

The appropriate way to loop inside a d3 filter (or, how to select multiple features)

Let's say I have an array like this, which I want to use for pattern matching:
var mich = ["Michigan", "Connecticut", "Florida", "New York"];
var arrayLength = mich.length;
And I have a topojson object like this, nested inside a basic d3.json function that's accessing a topojson file:
var allstates = topojson.feature(us, us.objects.layer1);
Which I filter using:
var fromstate = allstates.features.filter()[0];
How can I find all objects within the topojson.features that match my array? A loop through the array inside the filter only matches the first object. I.e., this fails inside the filter:
function (d){ for (i=0; i<arrayLength; i++){ return d.properties.NAME == mich[i];}
Please let me know if more notation is needed.
Try this in your function
return mich.indexOf(d.properties.NAME) >= 0;
instead of
for (i=0; i<arrayLength; i++){ return d.properties.NAME == mich[i];}
Here what it will do, it will check that if d.property.NAME & mich[0] are equal, if both are equal then it will return true otherwise it'll return false.
var mich = ["Michigan", "Connecticut", "Florida", "New York"];
var allstates = topojson.feature(us, us.objects.layer1);
var fromstate = allstates.features.filter( function (d){
for (i=0; i<mich.length; i++){
if (d.properties.NAME == mich[i]) {
return true;
}
//otherwise the loop continues
}
//if none of them matched
return false;
});
Or if you don't need to support IE<9:
var fromstate = allstates.features.filter( function (d){
return mich.indexOf(d.properties.NAME) != -1;
});

GAS - Sort a flexTable by column

I understand that cellTable in GWT performs this (Sort FlexTable Inquiry) and I was wondering if anyone knew a way to emulate some of the column sorting behaviour using a flexTable in UiApp.
In my case it is only necessary for the app to sort the column once at creation, not have it sortable by the user on click. I have included my flexTable creation code below:
var flexTable = app.createFlexTable()
.setStyleAttribute('marginTop', '10px')
.setCellPadding(5)
.setCellSpacing(2);
for(var i = 0;i<(size-1);i++){
var class = "class" + (i+1);
var classCode = classObjectsIndex[loggedInUser][class];
var text10 = statusObjectsIndex[classCode].classname;
var text11 = statusObjectsIndex[classCode].homeworkstatus;
var text12 = statusObjectsIndex[classCode].classcalendarlink;
var anchor = app.createAnchor('Calendar', text12)
.setTarget('_blank');
var calPanel = app.createAbsolutePanel()
.add(anchor);
flexTable.setText(i, 0, text10);
flexTable.setText(i, 1, text11);
flexTable.setWidget(i, 2, calPanel);
if(text11 == "No homework set for this class"){
flexTable.setRowStyleAttribute(i, "backgroundColor", "#96bcfd")
flexTable.setRowStyleAttribute(i, "color", "#000000");
}else{
flexTable.setRowStyleAttribute(i, "backgroundColor", "#eca8a3");
flexTable.setRowStyleAttribute(i, "color", "#FFFFFF");
};
}
app.add(flexTable);
Due to the way in which the table is populated sorting the array the values are pulled from will not help.
This the first question I have posted here, please be gentle. If I could ask it in a better way, I have overlooked an obvious resource to get my answer or if there is more information I need to provide please let me know!
EDIT//////////////////////////////////
I was having trouble sorting using the code provided, very helpfully, by Serge and so I approached it slightly differently and created individual objects for each row of data. The advice given by both Serge and Zig helped me end up with this working solution, many thanks!
//create flexTable
var flexTable = app.createFlexTable();
flexTable.setStyleAttribute('marginTop', '10px')
flexTable.setCellPadding(5);
flexTable.setCellSpacing(2);
//create empty table array to store rowObjects
var tableArray =[];
//create rowObjects
for(var i = 0; i<(size-1); i++){
var rowObject = {};
var class = 'class' + (i+1);
var classCode = classObjectsIndex[loggedInUser][class];
rowObject.className = statusObjectsIndex[classCode].classname;
rowObject.homeworkStatus = statusObjectsIndex[classCode].homeworkstatus;
rowObject.link = app.createAbsolutePanel().add(app.createAnchor('Calendar',statusObjectsIndex[classCode].classcalendarlink));
if(statusObjectsIndex[classCode].homeworkstatus == "No homework set for this class"){
rowObject.BGColor = "#96bcfd";
rowObject.color = "#000000";
}else{
rowObject.BGColor = "#eca8a3";
rowObject.color = "#FFFFFF";
}
tableArray.push(rowObject);
}
//sort objects in array by homework status
tableArray.sort(function (a, b) {
if (a.homeworkStatus > b.homeworkStatus)
return 1;
if (a.homeworkStatus < b.homeworkStatus)
return -1;
return 0;
});
//populate flextable
for(var i = 0;i<(size-1);i++){
flexTable.setText(i,0, tableArray[i].className);
flexTable.setText(i,1, tableArray[i].homeworkStatus);
flexTable.setWidget(i,2, tableArray[i].link);
flexTable.setRowStyleAttribute(i, 'color', tableArray[i].color);
flexTable.setRowStyleAttribute(i, 'backgroundColor', tableArray[i].BGColor);
};
app.add(flexTable);
Theres nothing that prevents you from sorting the source array first. Just store each 5 columns (3 data columns plus background/foreground colors) as rows in another array and sort that other array. After sort populate the table.
I fully agre with Zig on this, here is an example of such an implementation to help you figure out how to approach it. (code not tested but should be right)
var flexTable = app.createFlexTable()
.setStyleAttribute('marginTop', '10px')
.setCellPadding(5)
.setCellSpacing(2);
var array = [];
var t0 = [];
var t1 = [];
var t2 = [];
var color = [];
var BGcolor = [];
for(var i = 0;i<(size-1);i++){
var class = "class" + (i+1);
var classCode = classObjectsIndex[loggedInUser][class];
t0.push(statusObjectsIndex[classCode].classname);
t1.push(statusObjectsIndex[classCode].homeworkstatus);
t2.push(app.createAbsolutePanel().add(app.createAnchor('Calendar',statusObjectsIndex[classCode].classcalendarlink)));
if(statusObjectsIndex[classCode].homeworkstatus == "No homework set for this class"){
color.push("#000000")
BGcolor.push("#96bcfd")
}else{
color.push("#FFFFFF")
BGcolor.push("#eca8a3")
};
array.push(t0,t1,t2,color,BGcolor);
}
// sort the array here
array.sort();// use other sort parameter if you want, search SO for examples
for(var n in array){
flexTable.setText(i, 0, array[n][0]);
flexTable.setText(i, 1, array[n][1]);
flexTable.setWidget(i, 2, array[n][2]);
flexTable.setRowStyleAttribute(i, array[n][3])
flexTable.setRowStyleAttribute(i, array[n][4]);
}
app.add(flexTable);