I have comments in a database relative to each post. It pulls the post and all comments according to that post in one query, and groups them inside an XML node. I get the amount of attributes in each node, and take away the standard number of attributes that every post has by default, and that leaves me with the number of comments.
The comment structure is as follows:
comment0 Hey nice post!
commentdate0 2014-12-1 08:25:02
commentaudthor0 Chris
comment1 cool!
commentdate1 2014-08-2 09:25:02
commentaudthor1 Jason
and so on, the comments increase by that number.
So I need to check how many comments there are (done) and then retrieve them from the xml node (using $(this).attr('comment'+i)) Where i would be the counter (comment0, comment1 and so on)
Here is my current code to get it into the array:
var comms = new Array();
var count = this.attributes.length;
var av = count-11;
if(av != 0) {
for(var i=0; i<av; i++) {
for(var j=0; j<2; j++){
comms[i][j] = $(this).attr('comment'+i);
comms[i][j+1] = $(this).attr('commentdate'+i);
comms[i][j+2] = $(this).attr('commentauthor'+i);
}
}
}
But it is giving me the following error:
Uncaught TypeError: Cannot set property '0' of undefined
Now, how can I load it into a multi dimensional array to store the data, pass it to a function, and then process each row separately?
ie: this is what I am trying to do:
Array {
'comment1':
comment
commentdate
commentauthor
'comment2':
comment
commentdate
commentauthor
}
and then how would I be able to process each comment inside the function? ie: with each comment, do this.
Thanks in advance!
You need to create the inner array before adding to it. Try this:
var comms = new Array();
var count = this.attributes.length;
var av = count-11;
//if(av != 0) { // I commented this condition out, as it is not needed here
for(var i=0; i<av; i++) {
comms[i] = []; // Create a new array here before adding to it (this syntax is more common than the longer "new Array()" syntax you used
comms[i][0] = $(this).attr('comment'+i);
comms[i][1] = $(this).attr('commentdate'+i);
comms[i][2] = $(this).attr('commentauthor'+i);
}
//}
Related
I have 2 arrays, arr1 and arr2. They're both 2-dimensional. I want to copy certain array values from arr1 to arr2.
For instance, I want to copy the value from arr1[9][9] into arr2[0][0]. My guess was to write arr2[0][0] = arr1[9][9]; but that failed.
I looked at some similar questions on this site but they did not answer my question.
Here is the code for the particular situation. The code is written is Google Apps script.
// eplList and attList are both arrays. They are filled below (I checked, the values exist)
var eplList = epl.getRange(2, 2, eplLastRow, 2).getValues();
var attList = attsheet.getRange(3, 1, attLastRow, 20).getValues();
var eplListLength = eplList.filter(String).length;
var attListLength = attList.filter(String).length;
// Declaring the empty array I want to fill
var masterArray = [];
var ix, jx, day;
// Here I begin to fill the array
for (ix = 0; ix < eplListLength; ix++)
{
masterArray[ix][0] = eplList[ix][0]; // This is where I am getting the error message
masterArray[ix][1] = eplList[ix][1];
for (jx = 0; jx < attListLength; jx++)
{
if (eplList[ix][0] == attList[jx][day*4-4])
masterArray[ix][6+day] = masterArray[ix][6+day].concat(" ", attList[jx][day*4-2], ": ",attList[jx][day*4-3]);
};
// and some morecode
};
The error I'm getting is "TypeError: Cannot set property '0' of undefined"
To fix particular issue you've got try this code, but i not sure about code below):
masterArray[ix] = [eplList[ix][0], eplList[ix][1]]; // This is where I am getting the error message
I am trying to create a multiple choice question form to be created from data in a google spreadsheet. I managed to create the form of 60 questions each with 4 choices and setting the correct choice based on the information I have in the spreadsheet.
Last thing I need to do is to insert the correct feedback for each question based on column G in my spreadsheet that contains the feedback for each question.
Edit: here is a picture of how my spreadsheet & form would look like
Picture for Spreadsheet
Picture for how the form questions should look like
Picture of how the form questions look like (without a feedback)
The problem is that is not being implemented, The maximum I could was to set a fixed feedback/word for all questions, but was not possible to import the specific feedback for each question to the feedback section of each question, could anyone help with that, below is my code:
function popForm() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Sheet1');
var numberRows = sheet.getDataRange().getNumRows();
var myQuestions = sheet.getRange(1,1,numberRows,1).getValues();
var myAnswers = sheet.getRange(1,2,numberRows,1).getValues();
var myGuesses = sheet.getRange(1,2,numberRows,4).getValues();
var myfeedback = sheet.getRange(1,7,numberRows,1).getValues();
var myShuffled = myGuesses.map(shuffleEachRow);
Logger.log(myShuffled);
Logger.log(myAnswers);
// Create the form as a quiz. The resulting form's "Quiz options" are different from a manually created quiz. Be aware (and change manually if needed!
var form = FormApp.create('Fast Track Question - Domain I');
form.setIsQuiz(true);
// Write out each multiple choice question to the form.
for(var i=0;i<numberRows;i++){
if (myShuffled[i][0] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0],true),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][1] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1],true),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][2] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2],true),
addItem.createChoice(myShuffled[i][3])
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
else if (myShuffled[i][3] == myAnswers[i][0]) {
var addItem = form.addMultipleChoiceItem();
addItem.setTitle(myQuestions[i][0])
.setPoints(1)
.setChoices([
addItem.createChoice(myShuffled[i][0]),
addItem.createChoice(myShuffled[i][1]),
addItem.createChoice(myShuffled[i][2]),
addItem.createChoice(myShuffled[i][3],true)
]);
var incorrectFeedback = FormApp.createFeedback()
.setText(myfeedback[i][7])
.build();
addItem.setFeedbackForIncorrect(incorrectFeedback);
}
}
}
// This function, called by popForm, shuffles the 5 choices.
function shuffleEachRow(array) {
var i, j, temp;
for (i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
Proposed change to script
Your code was long and I found it easier to re-write it with a few extra tools such as getDataRange, push and splice and forEach.
It seemed you were calling the methods in the right way, but since you were having to repeat yourself in a few places and keep track of many arrays and indices, it is likely that a small mistake came up.
This is a working script adapted from yours:
function createQuiz() {
let file = SpreadsheetApp.getActive();
let sheet = file.getSheetByName("Sheet1");
// Instead of getting individual ranges, it is more efficient
// to get all the data in one go, and then operate on the two
// dimensional array in memory.
let range = sheet.getDataRange();
let values = range.getValues();
// Here I am using a existing form to test, but you can just
// create a new one if you want.
var form = FormApp.openById("[TESTING_ID]");
form.setIsQuiz(true);
values.shift(); // Using this to remove the first row of headers
// Going through each line using a forEach to create a
// multiple choice question
values.forEach(q => {
let choices = [q[1], q[2], q[3], q[4]];
let title = q[0];
let feedback = q[5]
// Calling function to create multiple choice question
createShuffledChoices(form, title, choices, feedback)
});
}
function createShuffledChoices(form, title, choices, feedback){
let item = form.addMultipleChoiceItem();
item.setTitle(title)
.setPoints(1)
// Setting up the array that will be passed into item.setChoices()
let shuffledChoices = [];
// Making sure that the correct answer is only marked once
let correctAnswerChosen = false;
// I found I had to shuffle the questions within the process of
// creating choices as it made it easier to maintain the spreadsheet
for (let i = choices.length; i != 0; i--) {
let rand = Math.floor(Math.random() * (i - 1));
// If the first answer is chosen, it is the correct one.
if (rand == 0 && correctAnswerChosen == false) {
// Combination of push and splice to remove from ordered array
// to the shuffled one
shuffledChoices.push(item.createChoice(choices.splice(rand, 1)[0], true));
// Marking the correct answer as chosen,
// so that no others are marked correct.
correctAnswerChosen = true;
} else {
shuffledChoices.push(item.createChoice(choices.splice(rand, 1)[0]));
}
}
// Finally setting the choices.
item.setChoices(shuffledChoices);
// Creating the feedback
let formFeedback = FormApp.createFeedback().setText(feedback).build();
item.setFeedbackForIncorrect(formFeedback);
}
The way that you were creating feedback was correct, I suspect that you were just getting mixed up with your arrays and indexes. This is why I tried to simplify your code and eliminate repeated sections.
I combined the shuffling process with the creation of the multiple choice question. This is because the shuffled array that is passed into item.setChoices has to be built of item.createChoice objects. This can't be done in another scope because item is not available.
Combining the logic for shuffling this way means that you don't need to have the letter prefixes in your questions A). You also don't need the column that has the correct answer, because the process knows that the first answer is the correct one. So your sheet can be simplified to this:
For this script to work, the data needs to be organized in this way. (Though you can adapt it anyway you like of course)
References
getDataRange
push
splice
shift
forEach
I've created a new project that should compare a name from Sheet1 with a list of names in Sheet2 and check if the name is already in that list. For that I chose a for-loop to get through the list in Sheet2 and compare every list entry with the name from Sheet1. Only if the name already exists in the list stuff should happen.
function myFunction() {
var tabSheet1 = 'Sheet1';
var tabSheet2 = 'Sheet2';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName(tabSheet1);
var sheet2 = ss.getSheetByName(tabSheet2);
var lastRow1 = sheet2.getLastRow() + 1;
var playerNameSheet1 = sheet1.getRange(1, 1).getValue();
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
...stuff...
}
}
}
Now my problem is that it seems like the script isn't able to identify that a name already exists in the list. Both values (playerNameSheet1 and playerNameSheet2) are completely identical (no space or other hidden obstacles), however the script would never continue with stuff in the if-statement. My example name to test my script was "Oliver Baumann".
I'm a bit confused about it - even more, because another comparison a bit later in the script code works just fine.
I've already tried to change the operator into === but that wouldn't work either.
if (playerNameSheet2 === playerNameSheet1) {
...stuff...
}
I've also observed that if I put a dot behind both variables I'm only able to choose further functions with playerNameSheet2, but not with playerNameSheet1. Maybe I did a typing error and am just too blind to see it? I don't know. Anyone an idea how to resolve the issue?
The complete project can be found here. However, a lot of stuff is in german and very rudimental. I just started it and haven't got time to clean it up. Just so you don't wonder.
You will likely benefit from a change to your inspection routine - currently what you have is not scalable due to the slow, repeated calls to the Spreadsheet Service. Use a batch method - getValues() - to return a Javascript Array that contains all the content you could want from your 'master list' of names:
// Create an N x 1 array of arrays, e.g. [ [r1c1], [r2c1], [r3c1], ... [rNc1] ],
// of data in column A in sheet2. There will be blanks at the end if other columns have more data.
var allNames = sheet2.getRange(1, 1, sheet2.getLastRow(), 1).getValues();
To check if the name from the first sheet is present, we can replace this code:
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
/* do stuff */
with this code (note j now starts at 0):
for (var j = 0; j < allNames.length; ++j) {
if (playerNameSheet1 === allNames[j][0]) {
/* do stuff */
If you only need to do stuff on a name once in the function call (e.g. you don't need to execute the loop body twenty times when the sheet 1 name is "Bob" and there are twenty instances of "Bob" on sheet 2), you can simplify checking allNames for a value with the Array#indexOf method. First, one must collapse the "2D" array of arrays of values into an array of values. We want to apply a function to every element of the outer array and construct an array of its outputs, so we choose to call Array#map on it:
var db = allNames.map(function (row) { return row[0]; });
The function we use simply returns the first element of the passed element - i.e. the value in the first column, resulting in an output like [ r1c1, r2c1, r3c1, ... rNc1 ].
The replacement code is then:
if (db.indexOf(playerNameSheet1) === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff */
Which says "if the name is not on sheet 2, log the failed lookup and then quit running the function." To promote actual logging, the log is sent to Stackdriver, which will keep it for much longer than the native Logger class would.
If your do stuff bits use the j index, you can still obtain that index and use the associated row in sheet 2:
var index = db.indexOf(playerNameSheet1);
if (index === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff with the user's existing row of data, e.g.
var userDataRow = sheet2.getRange(index + 1, 1, 1, sheet2.getLastColumn()).getValues();
var userData = userDataRow[0];
...
*/
A possible improvement to the indexOf modification, which I leave for you to investigate and/or implement, would be to use an Object to hold the names as "keys" (object properties) and the index of the associated sheet data (or even the data directly) as the associated value of the key-value pair.
you can try to convert data in array and compare in for-loop:
var dataRangeSpieler = sheetSpieler.getDataRange().getValues();
var dataRangeDBSpiele = sheetDBSpieler.getDataRange().getValues();
for (i in dataRangeSpieler ) {
for (j in dataRangeDBSpiele) {
if (dataRangeSpieler[i][1] == dataRangeDBSpiele[j][0]) {
Logger.log(dataRangeSpieler[i][1]); //Oliver Baumann
}
}
}
I'm working on an add-in for excel 2016 using the javascript API. I can successfully get the range into an array and get the values to show in console.log. I've also been able to get the values into a JSON array using JSON.stringify();
I need to manipulate the array to remove the empty values ("").
Can this be accomplished by using regular javascript array methods?
I'm thinking I can display the results back into a different worksheet using a similar approach like i did with var shWk
Here are some snippets of what I'm trying to do:
(function () {
"use strict";
// The initialize function must be run each time a new page is loaded
Office.initialize = function (reason) {
$(document).ready(function () {
app.initialize();
//document.getElementById("date").innerHTML = Date("MAR 30 2017");
$('#deleteTab').click(deleteTab);
$('#preview').click(preview);
$('#publish').click(publish);
});
};
function preview() {
Excel.run(function(ctx) {
//getting the colname from a date range in B2
var colName = ctx.workbook.worksheets.getItem('preview').getRange("B2");
colName.load('values');
return ctx.sync().then(function() {
//converting colname value to string for column name
var wkN = (colName.values).toString();
// displaying on the task pane
document.getElementById("tst").innerText = wkN;
// testing to confirm i got the correct colname
var shWk = ctx.workbook.worksheets.getItem('preview').getRange("B3");
shWk.values = colName.values;
//building the column connection by setting the table name located on a different worksheet
var tblName = 'PILOT_ZMRP1';
var tblWK = ctx.workbook.tables.getItem(tblName).columns.getItem(wkN);
//loading up tblWK
tblWK.load('values');
return ctx.sync().then(function(){
//this is where my question is:
var arry = tblWK.values;
for (var i=0; i < tblWK.length; i++){
if (tblWK.values !== ""){
arry.values[i][0]) = tblWK.values[i][0]
};
};
console.log(arry.length); //returns 185
console.log (arry.values);//returns undefined
tblWK.values = arry;
var tblWeek = tblWK.values;
console.log(tblWeek.length);//returns 185
console.log(tblWK.values);//returns [object Array] [Array[1],Array[2]
})
});
}).catch(function (error) {
console.log(error);
console.log("debug info: " + JSON.stringify(error.debugInfo));
});
}
What am I missing? Can you point me to some resources for javascript array handling in the specific context of office.js?
I want to thank everyone for the time spent looking at this question. This is my second question ever posted on Stack Overflow. I see that the question was not written as clear as it could've been. What i was trying to achieve was filtering out the values in a 1D array that had "". The data populating the array was from a column in a separate worksheet that had empty values (hence the "") and numeric values in it. the code below resolved my issue.
//using .filter()
var itm = tblWK.values;
function filt(itm){
return itm != "";
}
var arry = [];
var sht = [];
var j=0;
var s=0;
arry.values = tblWK.values.filter(filt);
//then to build the display range to show the values:
for (var i=0; i < itm.length-1; i++) {
if (tblWK.values[i][0]){
var arry; //tblWK.values.splice(i,0); -splice did not work, maybe my syntax was wrong?
console.log("this printed: "+tblWK.values[i][0]);
var cl = ('D'+i); //building the range for display
j++; //increasing the range
s=1;//setting the beignning range
var cll = cl.toString();//getRange() must be a string
console.log(cll);//testing the output
}
}
//using the variable from the for loop
var cl = ('D'+s+':D'+j);
var cll = cl.toString();
console.log(cll);//testing the build string
sht = ctx.workbook.worksheets.getItem('Preview').getRange(cll);
sht.values = arry.values; //displays on the preview tab
console.log (arry.values); //testing the output
The question was probably easier said by asking what vanilla javascript functions does office.js support. I found a lot help reading Building Office Add-ins using Office.js by Micheal Zlatkovsky and by reading the MDN documentation as well as the suggested answer posted here.
Regards,
J
I'm not sure what this check is trying to achieve: tblWK.values !== "". .values is a 2D array and won't ever be "".
For Excel, the value "" means that the cell is empty. In other words, if you want to clear a cell, you assign to "". null value assignment results in no-op.
You can just fetch the values form the array that contains null by using for each and can can push the null values into another array.
I'm having an issue pulling the correct values out of a for loop in Google Sheets.
Here's my code:
Note: this is a snippet from a larger function
function sendEmails() {
var trackOriginSheet = SpreadsheetApp.getActiveSpreadsheet().getName();
var getMirSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Miranda");
//Set a new object to hold conditional data
var holdingData = new Object();
//Create function to get values from origin sheet
var returnedValues = function (trackOriginSheet) {
//Load dynamic variables into an object via returnedValues()
if (trackOriginSheet === getMirSheet) {
var startMirRow = 2; // First row of data to process
var numRowsMir = 506; // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(startMirRow, 1, numRowsMir, 26);
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k in dataMir) {
var secondRowMir = dataMir[k];
var intRefDescMir = secondRowMir[3];
var intAdminActionsMir = secondRowMir[4];
//Push returned data to holdingData Object
holdingData.selectedData = secondRowMir;
holdingData.refDesc = intRefDescMir;
holdingData.adminActions = intAdminActionsMir;
}
}
}
Here's a copy of the sheet I'm working on
What I need to have happened here first, is track the origin sheet, then create an object to hold data returned from the returnedValues() function. Later, I'll call the properties of this object into a send email function.
The problem is that I need to be able to pull data from the selected sheet dynamically (the "Miranda" sheet in this case.) In other words, when a user selects the "Yes" option in column I of the Miranda sheet, the first thing this script needs to do is pull the values of the variables at the top of the for loop within the same row that the user selected "Yes." Then, I'm pushing that data to a custom object to be called later.
It's apparent to me, that I'm doing it wrong. There's, at least, something wrong with my loop. What have I done? :)
EDIT:
After reviewing the suggestion by VyTautas, here's my attempt at a working loop:
for (var k = 0; k < dataMir.length; k++) {
var mirColI = dataMir[k][8];
var mirRefDesc = dataMir[k][2];
var mirAdminActions = dataMir[k][3];
var mirDates = dataMir[k][4];
if (mirColI === "Yes") {
var activeRowMir = mirColI.getActiveSelection.getRowIndex();
//Pull selected values from the active row when Yes is selected
var mirRefDescRange = getMirSheet.getRange(activeRowMir, mirRefDesc);
var mirRefDescValues = mirRefDescRange.getValues();
var mirAdminActionsRange = getMirSheet.getRange(activeRowMir, mirAdminActions);
var mirAdminActionsValues = mirAdminActionsRange.getValues();
var mirDatesRange = getMirSheet.getRange(activeRowMir, mirDates);
var mirDatesValues = mirAdminActionsRange.getValues();
var mirHoldingArray = [mirRefDescValues, mirAdminActionsValues, mirDatesValues];
//Push mirHoldingArray values to holdingData
holdingData.refDesc = mirHoldingArray[0];
holdingData.adminActions = mirHoldingArray[1];
holdingData.dates = mirHoldingArray[2];
}
}
Where did all that whitespace go in the actual script editor? :D
You already correctly use .getValues() to pull the entire table into an array. What you need to do now is have a for loop go through dataMir[k][8] and simply fetch the data if dataMir[k][8] === 'Yes'. I also feel that it's not quite necessary to use for (var k in dataMir) as for (var k = 0; k < dataMir.length; k++) is a lot cleaner and you have a for loop that guarantees control (though that's probably more a preference thing).
You can also reduce the number of variables you use by having
holdingData.selectedData = mirData[k]
holdingData.refDesc = mirData[k][2] //I assume you want the 3rd column for this variable, not the 4th
holdingData.adminActions = mirData[k][3] //same as above
remember, that the array starts with 0, so if you mirData[k][0] is column A, mirData[k][1] is column B and so on.
EDIT: what you wrote in your edits seems like doubling down on the code. You already have the data, but you are trying to pull it again and some variables you use should give you an error. I will cut the code from the if, although I don't really see why you need to both get the active sheet and sheet by name. If you know the name will be constant, then just always get the correct sheet by name (or index) thus eliminating the possibility of working with the wrong sheet.
var titleMirRows = 1; // First row of data to process
var numRowsMir = getMirSheet.getLastRow(); // Number of rows to process
// Fetch the range of cells A2:Z506
var dataRangeMir = getMirSheet.getRange(titleMirRows + 1, 1, numRowsMir - titleMirRows, 26); // might need adjusting but now it will only get as many rows as there is data, you can do the same for columns too
// Fetch values for each cell in the Range.
var dataMir = dataRangeMir.getValues();
for (var k = 0; k < dataMir.length; k++) {
if (dataMir[k][7] === 'Yes') { //I assume you meant column i
holdingData.refDesc = dataMir[k] //this will store the entire row
holdingData.adminActions = dataMir[k][3] //this stores column D
holdingData.dates = dataMir[k][4] //stores column E
}
}
Double check if the columns I have added to those variables are what you want. As I understood the object stores the entire row array, the value in column called Administrative Actions and the value in column Dates/Periods if Applicable. If not please adjust accordingly, but as you can see, we minimize the work we do with the sheet itself by simply manipulating the entire data array. Always make as few calls to Google Services as possible.