JSON contains array of nulls with three objects - expected only three objects - json

Using a standalone Google Apps Script and a Google Spreadsheet. I have this script which returns as JSON an array of nulls and three objects, but I expected only to get three objects. Its a search, and when a zipcode is searched, the script is to return any matches. The thing is, it returns the matches successfully, but it also returns a null for each row that was not a match, in the order the rows appear on the google sheet. To make it work, the function testDoGetWithZipcode() should be run.
I don't know if I'm supposed to get those nulls, if they matter, or how I can fix it. It doesn't seem to go with anything I've learned about JSON so far but before even asking this I did an hour and a half Lynda.com course on Javascript and JSON and read the JSON.org website and read the documentation on Mozilla about JSON. I've adjusted variables in all of the functions because at first I thought it was in the function formatOrganization() but now I'm completely stumped.
s = SpreadsheetApp.openById("1280aUAvFoUDP2rtpCFS2JYR7TuQNYcd5gm8QudukiGc");
var sheet = s.getSheetByName("RAP - Data");
var data = sheet.getDataRange().getValues();
var headings = data[0];
function zipcodeQuery(zipcode) {
zipcodeArray = [];
for (var i = 1; i < data.length; i++){
if (zipcode === data[i][4].toString()){
zipcodeArray.push(data[i]);
}
}
return zipcodeArray
}
function formatOrganization(rowData){
var organization = {}
for (var i = 0; i < headings.length; i++){
Logger.log('Headings: ' + headings[i]);
organization[headings[i].toString()] = rowData[i];
}
return organization
}
function executeZipcodeQuery(request) {
zipcodes = request.parameters.zipcode;
// The object to be returned as JSON
response = {
organizations : []
}
// Fill the organzations dictionary with requested organizations
for (var i = 0; i < zipcodes.length; i++) {
sheetData = zipcodeQuery(zipcodes[i]);
if(sheetData !== undefined) {
for (var orgIndex = 0; orgIndex < sheetData.length; orgIndex++) {
var org = formatOrganization(sheetData[orgIndex]);
if(org !== undefined) {
Logger.log('Org object: ' + org);
if(typeof org === 'object') {
//FIXME
var orgId = parseInt(org.Id);
Logger.log('Org Id: ' + orgId);
response.organizations[orgId] = org
//response.organizations.push({orgId : org});
}
}
}
}
}
if (response.organizations.length > 0)
{
return ContentService.createTextOutput(JSON.stringify(response.organizations));
}
else
{
return ContentService.createTextOutput('Invalid Request. zipcode(s) not found.');
}
}
function testDoGetWithZipcode() {
var testRequest = {"parameter":{"zipcode":"19132"},"contextPath":"","contentLength":-1,"queryString":"zipcode=19132","parameters":{"zipcode":["19132"]}};
var textResult = doGet(testRequest);
textResult.setMimeType(ContentService.MimeType.JSON);
Logger.log('Mime Type: ' + textResult.getMimeType());
Logger.log('Result content: ' + textResult.getContent());
}
The return I get is this (abridged because there's over a 180 rows in the spreadsheet and they're all represented in the return by either null or an object):
[
null,
....
null,
{
"Id":61,
"Category":"Day / Drop in Centers",
"Organization Name":"Philadelphia Recovery Community Center (PRCC)",
"Address":"1701 W Lehigh Ave, Philadelphia, PA 19132",
"Zip Code":19132,
"Days":"Mon, Tues, Thurs, Fri: 12-8pm, Wed: 9-5pm, Sat: 9-1pm",
"Time: Open":"",
"Time: Close":"",
"People Served":"Women, Men, Families",
"Description":"Case management, outpatient treatment, youth programs, training programs",
"Phone Number":"215-223-7700"
},
....
null,
{
"Id":81,
"Category":"Emergency Shelter",
"Organization Name":"Station House",
"Address":"2601 N Broad St, Philadelphia, PA 19132",
"Zip Code":19132,
"Days":"",
"Time: Open":"",
"Time: Close":"",
"People Served":"Men",
"Description":"After hours reception for single men\n 2601 N. Broad Street\n After 4 pm",
"Phone Number":"215-225-9230"
},
null,
...
]

Your original object is this:
response = {
organizations : []
}
The value of the key/value pair for organizations is an array. But you are using notation as if organizations was an object.
response.organizations[orgId] = org
You could push a value into the array with:
response.organizations.push(org);
I'd probably try something like this:
var tempObject = {}; //Reset every time
tempObject[orgId] = org;
response.organizations.push(tempObject);

Related

what is the problem with the following code?

I have an array of facilities. I want to have indexes of the facility which is selected and allocated. In the end, I want to have a CSV output which shows me each of the facilities. But instead of showing them like [24 15 30 ...] I want to separate them like: [24,25,30,...]. The following code gives me an error. Is it possible to let me know what is the problem?
The error is 1. element "string" does not in an OPL model. The 2.element hub has never been used. (but as you can see I used it)
{int} hub = { s | s in facilities : y[s] == 1 };
//Output in a CSV file
execute{
string hubs="[";
for (var i=0; i<hub.length-1;i++){
hubs += hub[i]+",";
}
hubs += hub[hub.length-1]+"]";
var f=new IloOplOutputFile("1.csv");
f.writeln("Facilities");
f.writeln(hubs);
f.close();
}
{int} facilities=asSet(1..3);
int y[facilities]=[1,0,1];
{int} hub = { s | s in facilities : y[s] == 1 };
//Output in a CSV file
execute{
var f=new IloOplOutputFile("1.csv");
f.writeln("Facilities =");
var hubs="[";
for (var i in hub){
hubs += i+",";
}
hubs+="]";
f.writeln(hubs);
f.close();
}
This will give:
Facilities =
[1,3,]
PS:
{int} facilities=asSet(1..3);
int y[facilities]=[1,0,1];
{int} hub = { s | s in facilities : y[s] == 1 };
//Output in a CSV file
execute{
var f=new IloOplOutputFile("1.csv");
f.writeln("Facilities =");
var hubs="[";
for (var i in hub){
hubs += i;
if (i!=Opl.last(hub)) hubs+=",";
}
hubs+="]";
f.writeln(hubs);
f.close();
}
gives
Facilities =
[1,3]

How to use a for loop with .createChoice in Google Apps Script to create a quiz from a sheet?

I am using Google Apps Script to generate Google Forms from a Sheet. Questions are in rows and question choices are in columns.
Here is a link to the Google sheet if needed.
It is a straightforward task when using .setChoiceValues(values)
if (questionType == 'CHOICE') {
var choicesForQuestion = [];
for (var j = 4; j < numberColumns; j++)
if (data[i][j] != "")
choicesForQuestion.push(data[i][j]);
form.addMultipleChoiceItem()
.setChoiceValues(choicesForQuestion);
}
However, when I try to use .createChoice(value, isCorrect), the parameters call for value to be a string and isCorrect to be Boolean.
An example without a loop looks like this:
var item = FormApp.getActiveForm().addCheckboxItem();
item.setTitle(data[3][1]);
// Set options and correct answers
item.setChoices([
item.createChoice("chocolate", true),
item.createChoice("vanilla", true),
item.createChoice("rum raisin", false),
item.createChoice("strawberry", true),
item.createChoice("mint", false)
]);
I can not figure out how to add the loop. After reading over other posts, I have tried the following:
if (questionType == 'CHOICE') {
var questionInfo = [];
for (var j = optionsCol; j < maxOptions + 1; j++)
if (data[i][j] != "")
questionInfo.push( form.createChoice(data[i][j], data[i][j + maxOptions]) );
form.addMultipleChoiceItem()
.setChoices(questionInfo);
}
optionsCol is the first column of questions options
maxOptions is how many options are allowed by the sheet (currently 5). The isCorrect information is 5 columns to the right.
However, this not working because the array questionsInfo is empty.
What is the best way to do this?
Probably your issue is related to the method you reference--Form#createChoice--not existing. You need to call MultipleChoiceItem#createChoice, by first creating the item:
/**
* #param {Form} formObj the Google Form Quiz being created
* #param {any[]} data a 1-D array of data for configuring a multiple-choice quiz question
* #param {number} index The index into `data` that specifies the first choice
* #param {number} numChoices The maximum possible number of choices for the new item
*/
function addMCItemToForm_(formObj, data, index, numChoices) {
if (!formObj || !data || !Array.isArray(data)
|| Array.isArray(data[0]) || data.length < (index + 2 * numChoices))
{
console.error({message: "Bad args given", hasForm: !!formObj, info: data,
optionIndex: index, numChoices: numChoices});
throw new Error("Bad arguments given to `addMCItemToForm_` (view on StackDriver)");
}
const title = data[1];
// Shallow-copy the desired half-open interval [index, index + numChoices).
const choices = data.slice(index, index + numChoices);
// Shallow-copy the associated true/false data.
const correctness = data.slice(index + numChoices, index + 2 * numChoices);
const hasAtLeastOneChoice = choices.some(function (c, i) {
return (c && typeof correctness[i] === 'boolean');
});
if (hasAtLeastOneChoice) {
const mc = formObj.addMultipleChoiceItem().setTitle(title);
// Remove empty/unspecified choices.
while (choices[choices.length - 1] === "") {
choices.pop();
}
// Convert to choices for this specific MultipleChoiceItem.
mc.setChoices(choices.map(function (choice, i) {
return mc.createChoice(choice, correctness[i]);
});
} else {
console.warn({message: "Skipped bad mc-item inputs", config: data,
choices: choices, correctness: correctness});
}
}
You would use the above function as described by its JSDoc - pass it a Google Form object instance to create the quiz item in, an array of the details for the question, and the description of the location of choice information within the details array. For example:
function foo() {
const form = FormApp.openById("some id");
const data = SpreadsheetApp.getActive().getSheetByName("Form Initializer")
.getSheetValues(/*row*/, /*col*/, /*numRows*/, /*numCols*/);
data.forEach(function (row) {
var qType = row[0];
...
if (qType === "CHOICE") {
addMCItemToForm_(form, row, optionColumn, numOptions);
} else if (qType === ...
...
}
References
Array#slice
Array#forEach
Array#map
Array#some
I am sure the above answer is very good and works but I am just a beginner and needed a more obvious (plodding) method. I am generating a form from a spreadsheet. Question types can include: short answer (text item), long answer (paragraph), drop down (list item), multiple choice, grid item, and checkbox questions, as well as sections.
I had to be able to randomize the input from the spreadsheet for multiple choice and sort the input for drop downs. I am only allowing one correct answer at this time.
The columns in the question building area of the spreadsheet are: question type, question, is it required, does it have points, hint, correct answer, and unlimited choice columns.
qShtArr: getDataRange of the entire sheet
corrAnsCol: index within the above of the column with the correct answer
begChoiceCol: index within the above of first column with choices
I hope this helps other less skilled coders.
/**
* Build array of choices. One may be identified as correct.
* I have not tried to handle multiple correct answers.
*/
function createChoices(make, qShtArr, r, action) {
// console.log('Begin createChoices - r: ', r);
let retObj = {}, choiceArr = [], corrArr = [], aChoice, numCol, hasCorr;
numCol = qShtArr[r].length - 1; // arrays start at zero
if ((qShtArr[r][corrAnsCol] != '') && (qShtArr[r][corrAnsCol] != null)) {
hasCorr = true;
choiceArr.push([qShtArr[r][corrAnsCol], true]);
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push([aChoice, false]);
}
} //end for loop for multiple choice options
} else {
hasCorr = false;
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push(aChoice);
}
} //end for loop for multiple choice options
}
if (action == 'random')
choiceArr = shuffleArrayOrder(choiceArr);
if (action == 'sort')
choiceArr.sort();
console.log('choiceArr: ', JSON.stringify(choiceArr) );
let choices = [], correctArr = [] ;
if (hasCorr) {
for ( let i = 0 ; i < choiceArr.length ; i++ ) {
choices.push(choiceArr[i][0]);
// console.log('choices: ', JSON.stringify(choices) );
correctArr.push(choiceArr[i][1]);
// console.log('correctArr: ', JSON.stringify(correctArr) );
}
make.setChoices(choices.map(function (choice, i) {
return make.createChoice(choice, correctArr[i]);
}));
} else { // no correct answer
if (action == 'columns' ) {
make.setColumns(choiceArr);
} else {
make.setChoices(choiceArr.map(function (choice, i) {
return make.createChoice(choice);
}));
}
}
}

Why can't Google Sheets find reverseGeocode method from Maps API?

I'm using a Google Sheets function to reverse geocode a list lat/long coordinates. It looks like this:
function getAdd(lat, lng) {
if (lat == "") {
return "You have to provide latitudinal coordinates to the place"
} if (lng == ""){
return "You have to provide longitudinal coordinates to the place"
}
var response = Maps.newGeocoder().reverseGeocode(lat, lng);
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
Utilities.sleep(1000);
return result.formatted_address;
}
};
Question 1: Why is Google Sheets giving me the following error: "Cannot find method reverseGeocode(object,(class))"?
Question 2: Once I fix that, how can fetch country names from the result array instead of the complete address from the results?
You're trying to return a result for each result in the response object. Instead you have to choose one:
function getAdd(lat, lng) {
if (lat == "") {
return "You have to provide latitudinal coordinates to the place"
} if (lng == ""){
return "You have to provide longitudinal coordinates to the place"
}
var response = Maps.newGeocoder().reverseGeocode(lat, lng);
return response.results[0].formatted_address;
};
If you're looking for just the country, the format of the result object is here:
https://developers.google.com/maps/documentation/geocoding/#ReverseGeocoding
In that case, you should iterate through the results[0] object and test to see if the types includes "Country". If it does, select the results[0].address_components[i].short_name where i is your iterator. Or use long_name instead.
Ok, figured it out. Here's how I eventually got country from lat/long:
function getAdd(lat, lng) {
if (lat == "") {
return "Insert latitude."
}
if (lng == ""){
return "Insert longitude."
}
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
Utilities.sleep(1000); //in order not to exeed api calls per second
for (var i in response.results) {
var result = response.results[i];
}
for (var j in result.address_components) {
for (var k in result.address_components[j].types) {
if (result.address_components[j].types[k] == "country") {
return result.address_components[j].long_name;
}
}
}
};
Thank you for posting this it just helped me a lot with what I was trying to do. Just a quick note that your first loop simply returns the last record and not all data is in the last record but the country seems to always be there. If anyone (like me) is looking for locality names you can simply choose the first record and more data is available to you.
I just changed:
for (var i in response.results) {
var result = response.results[i];
}
to:
var result = response.results[0];
Full Code returns the locality and country:
function getAdd(lat, lng) {
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
var country="";
var place="";
Utilities.sleep(1000); //in order not to exeed api calls per second
var result = response.results[0];
for (var j in result.address_components) {
for (var k in result.address_components[j].types) {
if (result.address_components[j].types[k] == "country") {
country = result.address_components[j].long_name;
}
if(result.address_components[j].types[k] == "locality") {
place = result.address_components[j].long_name;
}
}
}
return [place, country];
};

How to pass a whole dojox.grid.DataGrid store(items json data) to servlet?

I have a button on page - when clicked, it passes all the data to the servlet that could update each row data. My question is how to pass the whole store to the servlet as json data? Is there any easy way? Thanks
Here is some code I wrote to get the store to an object. Then it can be converted to JSON using dojo.toJson(obj);. I learned about this from the dojotoolkit website originally. (Give credit where credit is due). I realize this code is huge and nasty. When I looked for a better way about a year back I could not find one.
JsonHelper.storeToObject = function(store) {
var object = [];
var index = -1;
store.fetch({
onItem : function(item, request) {
object[++index] = JsonHelper.itemToObject(store, item);
}
});
return object;
};
JsonHelper.itemToObject = function(store, item) {
// store:
// The datastore the item came from.
// item:
// The item in question.
var obj = {};
if (item && store) {
// Determine the attributes we need to process.
var attributes = store.getAttributes(item);
if (attributes && attributes.length > 0) {
var i;
for (i = 0; i < attributes.length; i++) {
var values = store.getValues(item, attributes[i]);
if (values) {
// Handle multivalued and single-valued attributes.
if (values.length > 1) {
var j;
obj[attributes[i]] = [];
for (j = 0; j < values.length; j++) {
var value = values[j];
// Check that the value isn't another item. If
// it is, process it as an item.
if (store.isItem(value)) {
obj[attributes[i]].push(itemToObject(store,
value));
} else {
obj[attributes[i]].push(value);
}
}
} else {
if (store.isItem(values[0])) {
obj[attributes[i]] = itemToObject(store,
values[0]);
} else {
obj[attributes[i]] = values[0];
}
}
}
}
}
}
return obj;
};

Scroll to alphabet in a List (ArrayCollection dataProvider) (Alphabet Jump)

Hopefully this is easy but that sometimes means its impossible in flex and I have searched quite a bit to no avail.
Say I have a list (LIST#1) of artists:
2Pac
Adele
Amerie
Beyonce
Jason Aldean
Shakira
The Trews
I also have a list (LIST#2) that has the values #,A-Z - how would I create an alphabet jump?
So If a user clicked on "A" in LIST#2 that would automatically scroll to "Adele" at the top of LIST#1 - not filter so he/she could scroll up to view 2Pac or down to view The Tews if they were not in the view yet.
Its a standard Flex Spark List with an ArrayCollection as the dataProvider - the artist field is called: "title" along with a unique id field that is not visible to the user.
Thanks!
Please see comments on marker answer for discussion on Dictionary that may be faster in some cases. See below for code (HAVE NOT CONFIRMED ITS FASTER! PLEASE TEST):
private function alphabet_listChange(evt:IndexChangeEvent) : void {
var letter:String;
letter = evt.currentTarget.selectedItems[0].toString();
trace(currentDictionary[letter]);
ui_lstLibraryList.ensureIndexIsVisible(currentDictionary[letter]);
}
public function createAlphabetJumpDictionary() : Dictionary {
//alphabetArray is a class level array containing, A-Z;
//alphabetDictionary is a class level dictionary that indexes A-z so alphabetDictionary["A"] = 0 and ["X"] = 25
var currentIndexDict:Dictionary = new Dictionary; //Dictionary is like an array - just indexed for quick searches - limited to key & element
var searchArray:Array = new Array;
searchArray = currentArrayCollection.source; //currentArrayCollection is the main array of objects that contains the titles.
var currentIndex:Number; //Current index of interation
var currentAlphabetIndex:Number = 0; //Current index of alphabet
for (currentIndex = 0; currentIndex < searchArray.length; currentIndex++) {
var titleFirstLetter:String = searchArray[currentIndex].title.toString().toUpperCase().charAt(0);
if (titleFirstLetter == alphabetArray[currentAlphabetIndex]) {
currentIndexDict[titleFirstLetter] = currentIndex;
trace(titleFirstLetter + " - " + currentIndex);
currentAlphabetIndex++;
} else if (alphabetDictionary[titleFirstLetter] > alphabetDictionary[alphabetArray[currentAlphabetIndex]]) {
trace(titleFirstLetter + " - " + currentIndex);
currentIndexDict[titleFirstLetter] = currentIndex;
currentAlphabetIndex = Number(alphabetDictionary[titleFirstLetter] + 1);
}
}
return currentIndexDict;
}
private function build_alphabeticalArray() : Array {
var alphabetList:String;
alphabetList = "A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z";
alphabetArray = new Array;
alphabetArray = alphabetList.split(".");
return alphabetArray;
}
private function build_alphabetDictionary() : Dictionary {
var tmpAlphabetDictionary:Dictionary = new Dictionary;
for (var i:int=0; i < alphabetArray.length; i++) {
tmpAlphabetDictionary[alphabetArray[i]] = i;
trace(alphabetArray[i] + " - " + i);
}
return tmpAlphabetDictionary;
}
private function buildCurrentDictionary() : void {
trace("Collection Changed");
currentDictionary = new Dictionary;
currentDictionary = createAlphabetJumpDictionary();
}
The Flex Spark list has a very convenient method called ensureIndexIsVisible(index). Check the Flex reference documentation. All you have to do is to find the index of the first artist for the corresponding selected alphabet letter:
public function findAlphabetJumpIndex():Number
{
var jumpToIndex:Number;
var selectedLetter:String = alphabethList.selectedItem;
for (var i:int=0; i < artists.length; i++)
{
var artistName:String = artists.getItemAt(i);
var artistFirstLetter:String = artistName.toUpperCase().charAt(0);
if (artistFirstLetter == selectedLetter)
{
jumpToIndex = i;
break;
}
}
return jumpToIndex;
}
You can iterate your artist list data provider and check if artist name starts with selected alphabet from list two. When corresponding artist is found, set artist list selected index a value what you get from iterating data.