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.
Related
I was working in a project which is converting some data to another table by an online API (web service), so the plan is fetching data from the API and converting the prices to another table, I still got the same error again and again which is not convert each row as the way I need.
SCREENSHOT : THE UI TO UNDERSTAND THE USE
CODE :
function Send_Button() {
//activate the spreadsheet
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var cellCurrency = spreadsheet.getRange('E9').getValues();
//fatching the data from the API
var res = UrlFetchApp.fetch("https://api.exchangeratesapi.io/latest?base="+cellCurrency).getContentText();
//parsing data to JSON
var json = JSON.parse(res);
//exporting data in variables
var USD=json['rates']['USD'],
CAD=json['rates']['CAD'],
GBP=json['rates']['GBP'],
EUR=json['rates']['EUR'];
//an array to fetch just 4 currencies
var CRN = [USD,CAD,GBP,EUR];
var cellsPrice = spreadsheet.getRange('E5:E8').getValues();
//targeted cell in the second currencies table
var cellsTraget1 = spreadsheet.getRange('H3:K3');
var cellsTraget2 = spreadsheet.getRange('H4:K4');
var cellsTraget3 = spreadsheet.getRange('H5:K5');
var cellsTraget4 = spreadsheet.getRange('H6:K6');
//converting process
for(var i=0;i<4;i++)
{
cellsTraget1.setValue(cellsPrice[0]*CRN[0]);
cellsTraget2.setValue(cellsPrice[1]*CRN[1]);
cellsTraget3.setValue(cellsPrice[2]*CRN[2]);
cellsTraget4.setValue(cellsPrice[3]*CRN[3]);
}
}
You are iterating but not using the iterator variable i, is that correct? Perhaps writing the code in a more readable way with first getting the rows and then the cell value for each column and then iterating over both row and column would make it more readable and makes you find the issue faster.
Are you absolutely sure that your var cellCurrency is the correct range you want?
From your screenshot it appears the right cell is "E10"... but let's assume you fixed that.
Then you have an issue with your array dimensions, your var cellsPrice is a 2D array (Rows, Columns) even if the range you are getting values from is only one row!.
Another issue, your var cellsTraget* is also a 2D Range, so you can't use setValue()... You could use setValues() but the values array MUST match the range's dimension. And as they currently are, they don't match. Hint: transpose them if you want to use said function.
Apart from that, your for-loop makes no sense at all, since you are not looping anyhow!.
Here is some sample working code:
function sendButton() {
// Activate the spreadsheet
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var baseCurrency = "USD";
// Fetching the data from the API
var res = UrlFetchApp.fetch("https://api.exchangeratesapi.io/latest?base="+baseCurrency).getContentText();
// Parsing data to JSON
var json = JSON.parse(res);
// Exporting data in variables
var USD = [json['rates']['USD'], json['rates']['CAD'], json['rates']['GBP'], json['rates']['EUR']];
// I will do it for one row
var articlePrice = spreadsheet.getRange('E5').getValue();
// Mind the array's dimensions!
var convertedPricesRange = spreadsheet.getRange('I5:L5');
var convertedPriceValues = [[articlePrice*USD[0],articlePrice*USD[1],articlePrice*USD[2],articlePrice*USD[3]]];
// Set converted values
convertedPricesRange.setValues(convertedPriceValues);
}
Further reading:
Sheets Class Range
I would like to create a filter for a column in a spreadsheet, then retrieve the list of default criteria values created for the filter. I believe that my code returns a Filter object without any values for it.
function TestFilter(){
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
sheet.getRange(1, 2, sheet.getMaxRows(), 1).createFilter();
var filter = sheet.getFilter();
var output = filter.getColumnFilterCriteria(2).getCriteriaValues();
return output;
}
You can use the following functions for this:
getHiddenValues()
Returns the values to hide.
getVisibleValues()
Returns the values to show.
In case your filter is set to hide all of the possible values, you will obtain what you desire by using the function getHiddenValues().
However, this will not be possible if your filter is only hiding a subset of your values. For that case, you could use a Google Apps Script function such as the following below to obtain the distinct values:
function getDistinctValues(range) {
var values = range.getValues();
var unique = {};
for (var i=0; i<values.length; i++) {
for (var j=0; j<values[i].length; j++) {
var key = values[i][j];
if (key !== null && key !== undefined && key !== '')
unique[key] = true;
}
}
return Object.keys(unique);
}
The usage of it would be, in case you were attempting to obtain the distinct values on your A column:
var distinctValues = getDistinctValues(sheet.getRange("A2:A"));
Note that this function will return the values as Strings. In case you want to obtain the actual numeric value instead of a String, you can parse the values simply by using the following code:
var distinctValues = getDistinctValues(sheet.getRange("A2:A")).map(parseFloat);
I believe there is a bug with 2 out of 3 of these functions, by using something like this:
var filter = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheetName").getFilter();
var criteriaValues = filter.getColumnFilterCriteria(9).getCriteriaValues();
Logger.log("criteria Values length " + criteriaValues.length);
Logger.log(criteriaValues);
var visibleValues = filter.getColumnFilterCriteria(9).getVisibleValues();
Logger.log("visible Values length " + visibleValues.length);
Logger.log(visibleValues);
var hiddenValues = filter.getColumnFilterCriteria(9).getHiddenValues();
Logger.log("hidden Values length " + hiddenValues.length);
Logger.log(hiddenValues);
and setting a filter on column I (9th from the left) regardless of how many or which values I filter by, I only ever see the values that I've hidden from the column, the criteriaValues and visibleValues arrays are always empty, while hiddenValues always shows correctly the values that are filtered out.
If someone could double check this and confirm it would be great, otherwise, maybe I'm just doing something really silly, in which case please let me know as well :).
This is created based on https://developers.google.com/apps-script/reference/spreadsheet/filter-criteria.html
Blockquote
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.
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);
}
//}
I've been trying to work with Trello and the Google Apps Script this week. I am trying to create an array of hashes that I can then use to load the spreadsheet. Google apps script doesn't like the typical javascript code of creating hashes. I've looked up the docs but they don't have anything like hashes...they say to:
var object = [];
var object1 = {};
object.push(object1);
This wont work because I'm essentially trying to do something like:
var hash={name: , label: };
var n= someNumber;
var l= someLabel
var hash.push(name: n, label: l);
Essentially that is the code I have right now. But here is my entire function:
function getData(){
var list={};
//get the list of delivered cards from Trello
var listRequest = authorizeToTrello(); // get authorization
var result = UrlFetchApp.fetch("https://trello.com/1/lists/4fea3a2c3a7038911ebff2d8/cards",
listRequest);//fetch list
var listOfCards = Utilities.jsonParse(result.getContentText());//Google app utility format json
//outer loop to iterate through list of Cards
for(var i=0; i < listOfCards.length; i++){
var cardId = listOfCards[i].id; //get the id of a single card
var l = listOfCards[i]["label"]; //get the label for the our structure
//get a json object for a single card within the list of cards iteration
var cardRequest = authorizeToTrello();
var getCard = UrlFetchApp.fetch("https://trello.com/1/cards/" + cardId + "/actions", cardRequest);
var singleCard = Utilities.jsonParse(getCard.getContentText());
//inner loop to iterate the single cards JSON objects
for(var j=0; j < singleCard.length; j++) {
if(singleCard[j].data != undefined && singleCard[j].data.listAfter != undefined)
{
var str = singleCard[j]["data"]["listAfter"]['name'];
if(str === "Delivered Q3 2012"){
var n = singleCard[j]['memberCreator']['fullName'];
}
}
}
//push the data to list
list.push(n,l);
}
return name, label; //return list for output
}
Reading the question, I understood that the author needs to know how to create an associative array in a GAS. If it is correct then here is a couple of links (here and here) and a sample code is bellow.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map;
}
If the author needs really
an array of hashes
then there are a couple of ways depending on which hash algorithm is required.
to use the Utilities.computeDigest method to calculate a hash of a string using one of available algorithms.
if the required hash calculation algorithm is not supported by the Utilities.computeDigest, then is possible to write own implementation as it is done for the BLAKE function.
Here is a sample of how to create an array of hashes using the MD5 hash.
function testHash() {
var array = [];
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value1"));
array.push(Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, "value2"));
return array;
}
P.S. The return line of the author code return name, label; //return list for output
is not correct - only the label variable value is returned. To return a couple of variables as an array is necessary to write return [name, label];. Or may be the author needs to return the list variable and not name and label.
I know this is an old post / question, but i would like to update my answer since the original anwer (1st answer) is misleading. I was myself looking for how to return associative arrays back to a cell in the spreadsheet, but alas.. "YOU CANNOT". Google spreadsheet MUST want an numerically indexed array or an object. Otherwise it returns "#ERROR".
Here are the steps to replicate the issue.
function testMap() {
var map = {};
map["name1"] = "value1";
map["name2"] = "value2";
return map
Formula in your cell: =testMap()
Value in your cell: Thinking... #ERROR
Solution (rather a workaround)
1: Transfer your objects from your associative array into a numerically indexed array using for-each type loop.
var temp = new Array();
for (var i in map) {
temp.push([i,map[i]])
// optionally use activeSheet.getRange(X:X).setValue([i,map[i]])) function here.
// set values will not work in cell functions. To use it via cell functions, rerun / trigger the functions using an on_edit event.
}
If you used a temp like numerically indexed array, you can return "temp" back to the calling cell.
Summary: For onEdit() purposes, use Cache Service to define associative array data.
Here's a shared Gsheet demonstrating this curious behavior. I tried the following solution in programmatically defining an associative array based on data in a Google sheet.
var assocArr = {
labels: {},
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheetName');
var values = sheet.getDataRange().getValues();
for(var row in values) {
assocArr.labels[values[row][0]] = values[row][1];
};
for(var key in assocArr.labels) {
Logger.log("key: %s, value: %s",key, assocArr.labels[key]);
};
return(void(0));
},
};
To execute this, you run the init() method in the onOpen() event handler.
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onOpen: key: %s, value: %s",key, assocArr.labels[key]);
};
The logger message confirms that init() loads the data from the worksheet.
Now if I try to reference this assocArr object in onEdit() it returns undefined for all key values.
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( assocArr.labels[key] );
Logger.log("onEdit: key: %s, value: %s",key, assocArr.labels[key]);
};
I infer that for security reasons, Google limited the simple-trigger onEdit() to not have global variable scope, same as they voided the utility of the event.user property.
Now instead if I simply put the key-value pair in the cache, it works! Here is the complete code that works using the Cache Service.
var cache = CacheService.getPrivateCache();
var assocArr = {
init: function () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Account Labels');
var values = sheet.getDataRange().getValues();
for(var row in values) {
cache.put(values[row][0], values[row][1], 3600);
};
return(void(0));
},
};
function onOpen() {
assocArr.init();
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onOpen: key: %s, value: %s",key, cache.get(key));
};
function onEdit(event) {
var key = 'test';
SpreadsheetApp.getUi().alert( cache.get(key) );
Logger.log("onEdit: key: %s, value: %s",key, cache.get(key));
};
Curiously, the onEdit() has the cache variable in its scope.
Here again is the shared Gsheet demonstrating this curious behavior.
I found this really quick way that is not listed
Create a json object (array style)
var myArray = {
1:{"id": "inprogress","title" : "in Progress"},
2:{"id": "notstarted","title" : "Not Started"},
3:{"id": "completed" ,"title" : "Completed"}
};
read the json
// get the lenght of the json object
var jsonSize = Object.keys(myArray).length;
// use this in a loop
for (var i = 1; i < Object.keys(jsonSize).length; i++) {
var title = myArray[i].title;
}
Works like a charm for me