I'm creating an office js addin that inserts data from the bottom of Table 1 into Table 2 but I am unable to find a method of doing this that works.
I have tried using Excel.Functions.countA() but I can't seem to get a value other than NaN out of it. Here is the code I'm using:
async function run() {
try {
await Excel.run(async context => {
var sheet1Name = "Sheet1";
var sheet1RangeAddress = "B:B";
var sheet2Name = "Sheet2";
var sheet2RangeAddress = "A2:P2";
var sheet2Range = context.workbook.worksheets.getItem(sheet2Name).getRange(sheet2RangeAddress);
sheet2Range.insert("Down");
var sheet1CellAddress = context.workbook.worksheets.getItem(sheet1Name).getRange(sheet1RangeAddress).load("address");
var sheet1RangeLength = Number(context.workbook.functions.countA(sheet1CellAddress));
var sheet1LastCell = context.workbook.worksheets.getItem(sheet1Name).getRangeByIndexes(3,1,sheet1RangeLength,1).getLastCell();
var sheet2Cell = context.workbook.worksheets.getItem(sheet2Name).getRange("A2");
sheet2Cell.values = [[ context.workbook.worksheets.getItem(sheet2Name).getRange("A2").copyFrom(sheet1LastCell) ]]
await context.sync();
});
} catch (error) {
console.error(error);
}
}
I can't find anything useful in Microsoft's documentation or a working example online. Does anyone know what I'm doing wrong?
This line in your code looks problematic:
var sheet1RangeLength = Number(context.workbook.functions.countA(sheet1CellAddress));
The Functions.countA method returns an Excel.FunctionResult object which I don't think can be cast to a Number. The count returned by the function will be in the value property of the returned object. You need to load that value to read it. Try these two lines as a replacement:
var sheet1RangeLength = context.workbook.functions.countA(sheet1CellAddress).load("value");
await context.sync();
BTW, the following line is returning a Range object, not an address. That's OK because countA accepts a Range object parameter, but your variable is misleadingly named. Also, I don't think the load("address") on the end is serving any purpose.
var sheet1CellAddress = context.workbook.worksheets.getItem(sheet1Name).getRange(sheet1RangeAddress).load("address");
If you haven't already, please see this article: Call built-in Excel worksheet functions.
Related
I am trying to fecth some data from a google spreadsheet in an AppScript standalone file.
I tried with this piece of code, but it is definitely incomplete. It raises this error:
TypeError: Cannot read property 'getDataRange' of undefined
My function:
var sheet=SpreadsheetApp.openById('1n2a5iecKo7seMXcef81V_pxWSRjE4agQ9DSoROtf0').getSheets()[0]
function getColumnId(sheet, namecol) {
var data = sheet.getDataRange().getValues();
var col = data[0].findIndex((name) => name === namecol) + 1;
return col;
}
After extracting the column data, I want to store that data in a normal list:
var IDCol=getcolumnId(sheet, 'ID')
var AFCol=getcolumnId(sheet, 'AF')
Is that possible in this way? I'd do debugging but I do not know how to proceed first with my function.
I believe your goal is as follows.
You want to retrieve the values from a column using the function getColumnId(sheet, namecol).
Modification points:
Your function name is getColumnId. But you are trying to call the function with getcolumnId. I think that an error occurs.
var sheet=DriveApp.getFileById('1n2a5ieOMcKo7seMXcef81V_pxWSRjE4agQ9DSoROtf0').getBlob() is Blob. In your script, an error occurs at var data = sheet.getDataRange().getValues().
In order to retrieve the values from the column, I think that it is required to retrieve the values using the value of col. For this
Modified script:
function getColumnId(sheet, namecol) {
var data = sheet.getDataRange().getValues();
var col = data[0].findIndex((name) => name === namecol);
var transpose = data[0].map((_, c) => data.map(r => r[c])); // Here, "data" is transposed.
return transpose[col];
}
// Please run this function.
function sample() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1"); // Please set the sheet name.
var IDCol = getColumnId(sheet, 'ID')
var AFCol = getColumnId(sheet, 'AF')
console.log(IDCol)
console.log(AFCol)
}
When this modified script is run, the values of columns with the header values of ID and AF.
Reference:
map()
Added:
From your following replying and your updated question,
Hi Tanaike, I will definitely use your approach mapping the values as you said. But first of all I need to read the data from gsheet properly. Please see my Edit and the error is raised. My code is not place in the google spreadsheet itself but in a StandAlone file in AppsScript
I tried with this piece of code, but it is definitely incomplete. It raises this error:
TypeError: Cannot read property 'getDataRange' of undefined
If you are trying to directly execute the function getColumnId(sheet, namecol) by the script editor, such an error occurs, because sheet is undefined. I thought that this might be the reason for your current issue. From this situation, I thought that you might want to use sheet, IDCol and AFCol as the global variable. If my understanding is correct, how about the following sample script?
Sample script:
var sheet = SpreadsheetApp.openById('###').getSheets()[0];
function getColumnId(sheet, namecol) {
var data = sheet.getDataRange().getValues();
var col = data[0].findIndex((name) => name === namecol);
var transpose = data[0].map((_, c) => data.map(r => r[c]));
return transpose[col];
}
var IDCol = getColumnId(sheet, 'ID')
var AFCol = getColumnId(sheet, 'AF')
// Please run this function.
function myFunction() {
console.log(IDCol)
console.log(AFCol)
}
In this function, when myFunction is run, you can see the values at the log.
I'm currently working on a simple web scraping nodejs program. It is based on cheerio and I get items from a website and extract some information from there.
As far as I understand it all functions I call inside the foreach loop are sync so they should execute from top to bottom. And because the foreach loop is also only a normal loop, which executes sync in js, the function should return my finished array. But instead it is getting undefined and when I log it inside directly to console it works(?).
function getIntensiv(){
var intensivregister = [];
request.post({url: 'SOMEURL', form: {SOMEFORM}}, function(err,res,body){
var $ = cheerio.load(body);
$('#dataList').children('tbody').children('tr').each(function(i, elem){
var name = $(elem).children('td').first().text().trim().split("\n")[0].trim();
var zipcity = $(elem).children('td').first().children('small').last().text();
var streetnr = $(elem).children('td').first().children('br').last().prev().text();
intensivregister.push({'name': name, 'zipcity': zipcity, 'streetnr': streetnr});
});
console.log(intensivregister); //works and prints the finished array
return intensivregister; //returns undefined before function finished
});
}
I would appreciate it if you could explain me where my mistake is and help me fix it.
function getIntensiv(){
const cheerio = require('cheerio')
const request = require('request')
var intensivregister = [];
request.get({url: 'https://www.w3schools.com/html/html_tables.asp'}, function(err,res,body){
var $ = cheerio.load(body);
$('#customers').children('tbody').children('tr').each(function(i, elem){
var name = $(elem).children('td').first().text().trim().split("\n")[0].trim();
var zipcity = $(elem).children('td').first().children('small').last().text();
var streetnr = $(elem).children('td').first().children('br').last().prev().text();
intensivregister.push({'name': name, 'zipcity': zipcity, 'streetnr': streetnr});
});
console.log(intensivregister); //works and prints the finished array
return null; //returns undefined before function finished
});
return null; //***<---This is returning and not the above return. If no return statement is written then undefined is passed.***
};
var retrunVal = getIntensiv()
console.log(retrunVal);
Please find the highlighted comment
Ok I figured out that my idea of javascript was not how you should use it. I worked around my problem with getting rid of the idea of returning values from functions (which comes mainly from my experiences from async programming) and instead using callback parameters which I give to my function and call at the end of my request.
function getIntensiv(callback){
var intensivregister = [];
request.post(...);
**callback(intensivregister);**
}
What also is working (and I think a better solution) is working with promises e.g. with request-promise and calling the callback in the finally call.
I made a js with data in a const array as below
const messages = [
{ date: '2020-1-1', content:'message1'},
]
In order to make my file cleaner I decide to put the data in a Json file and want to call the Data in my Js in order to use it like before.
my Json is like this
[
{
"date":"2020-1-1",
"content":"message1"
}
]
In order to import my Json I put this code:
let messages = [];
$.getJSON("messages.json", function(data) {
messages = data;
console.log(messages);
});
The result is that my array is loaded in the console but the variable dont work, I tried things with Object.keys but no more result. I dont use framework also and dont find a solution on other questions here. Any help will be appreciated. Thank you very much!
I dont use framework
You are using a library, though, $ === jQuery
in order to make my file cleaner I decide to put the data in a Json file
You can just define a constants.js file and load that before your other scripts.
For example,
constants.js
const messages = [
{ date: '2020-1-1', content:'message1'},
]
main.js
alert(messages);
index.html
<script src="constants.js"></script>
<script src="main.js"></script>
#Alvin Stefanus
Because x,z and messages were undefined and didnt let the code work, I also add
let messages,x,z = [];
And now it works perfectly with your solution.
I will use it as you told also for the other operations.
Thank you very much it helped for my problem and gave me a new technique.
EDIT:
I also tried to delete this part
var start = x;
$.getJSON('messages.json', function(data) {
messages = data;
}); <-- this
var end = z;
And it also works! That means that the problem was not the async function but because I didnt put the loop within the curly bracket of
$.getJSON('messages.json', function(data) {
//The data is finished being filled here
}
Ok this is probably the issue. $.getJSON() is an async function, the code will not wait until the closing curly bracket of the method:
var start = x;
$.getJSON('messages.json', function(data) {
messages = data;
}); <-- this
var end = z;
The code will run var end = z; before the $.getJSON() finished getting the result, because it is asynchronous function. In other word, when the code is currently at var end = z, $.getJSON() is still working to get the data, and has not been finished. That is why the messages = data is not being called yet.
So here is what you want to do:
$.getJSON('messages.json', function(data) {
messages = data;
for (const item of messages) {
if (item.date === todayDay) {
console.log(item.content);
var newPara = document.createElement("p");
var textNode = document.createTextNode(item.content);
newPara.appendChild(textNode);
var nodeParent = document.getElementById("titre");
var nodeChild = document.getElementById("child1");
nodeParent.appendChild(newPara, nodeChild);
}
}
});
Do all your needed operations within the curly bracket of
$.getJSON('messages.json', function(data) {
//The data is finished being filled here
}
This is a callback function. You can learn more about callback function here
Some Info
Yes the loop has to be inside the callback function to run after the data has been retrieved. To give you better understanding about the async function, you can also put your loop outside within a timeout function, but you will never want this, because you will not know how long the operation for retrieving the data will run.
For example:
$.getJSON('messages.json', function(data) {
messages = data;
});
setTimeout(function() {
for (const item of messages) {
if (item.date === todayDay) {
console.log(item.content);
var newPara = document.createElement("p");
var textNode = document.createTextNode(item.content);
newPara.appendChild(textNode);
var nodeParent = document.getElementById("titre");
var nodeChild = document.getElementById("child1");
nodeParent.appendChild(newPara, nodeChild);
}
}
}, 2000); //run after 2 seconds
Im sure the code above will also work, the process of getting the data should not be longer than 2 seconds.
Again this is not a correct way to do it, just to give you better understanding of async function.
I am trying to see if a value in the "apply" sublist for customer payment data has changed and do some action based on it.
My SuiteScript is as follows:
define(['N/record', 'N/https'],
function(record,https)
{
function afterSubmit(context)
{
var oldRec = context.oldRecord;
log.debug({title: 'oldRec ', details: oldRec });
// This log shows that the JSON has an
// attribute called sublists which contains "apply" which has all the applied payments
// eg: {"id":"1234", "type":"customerpayment", "fields":{all the fields},
// "sublists": {"apply" : {"line 1"...}}}
var oldRecSublists = oldRec.sublists;
log.debug({title: 'oldRecApply ', details: oldRecSublists });
// This returns empty or null though there is data
What am I doing wrong here?
Basically what I am trying to achieve is compare the context.oldRecord.sublists.apply and context.newRecord.sublists.apply to find if the amt has changed or not.
Is there a better way to do this is SuiteScript 2.0?
Thanks in advance!
Part of what is going on there is that it looks like you are trying to spelunk the NS data structure by what you see in the print statement. You are not using the NS api at all.
When you send the NS object to the log function I believe it goes through a custom JSON.stringify process so if you want to just inspect values you can do:
var oldRecObj = JSON.parse(JSON.stringify(oldRec));
now oldRecObj can be inspected as though it were a simple object. But you won't be able to manipulate it at all.
You should be using the NS schema browser
and referring to the help docs for operations on N/record
A snippet I often use for dealing with sublists is:
function iter(rec, listName, cb){
var lim = rec.getLineCount({sublistId:listName});
var i = 0;
var getV = function (fld){
return rec.getSublistValue({sublistId:listName, fieldId:fld, line:i});
};
var setV = function(fld, val){
rec.setSublistValue({sublistId:listName, fieldId:fld, line:i, value:val});
};
for(; i< lim; i++){
cb(i, getV, setV);
}
}
and then
iter(oldRec, 'apply', function(idx, getV, setV){
var oldApplied = getV('applied');
});
I'm trying to write a little script to make my coworkers and mine lives easier. I am trying to append lines to a spreadsheet based on information entered into a custom form. The code posted below just the doPost block which should be appending the google spreadsheet.
function doPost(form) {
var PN = form.PartNumber;
var REV = form.Revision;
var DATE = form.RevisionDate;
var DESC = form.Description;
var NOTE = form.PartNotes;
var URL = form.myFile.getURL();
var ss = SpreadsheetApp.openById("ID HERE"); // removed ID for sake of safety (let me be paranoid)
var sheet = ss.getSheetName('Uploads');
sheet.appendRow([PN,REV,DATE,DESC,NOTE,URL]);
}
I am unsure why it isn't writing to the spreadsheet but it isn't throwing me any errors. If you can offer any insight as to what is wrong I would greatly appreciate it; there are many guides online but most seem to be based on deprecated functions/code/etc.
Thanks for your time.
Instead of using doPost, set up a "On form submit" trigger.
You need to get the namedValues to be able to pull specific values and take the first output.
Also, it should be "getSheetByName('Uploads')" .
As pointed out in the previous answer, it is unclear what you are trying to achieve by "form.myFile.getURL();" If you want to get the form url you might as well create it as a string, as it always stays the same.
Here is a working example of your code:
function doPost(form) {
var formResponses = form.namedValues;
var PN = formResponses.PartNumber[0];
var REV = formResponses.Revision[0];
var DATE = formResponses.RevisionDate[0];
var DESC = formResponses.Description[0];
var NOTE = formResponses.PartNotes[0];
//var URL = form.myFile.getURL(); //Not sure what you are tyring to get here as form URL will always be the same.
var URL = "Your form's url"; //You can put the form url in here so it will be pushed in to every row.
var ss = SpreadsheetApp.openById("ID HERE"); // removed ID for sake of safety (let me be paranoid)
var sheet = ss.getSheetByName('Uploads');
sheet.appendRow([PN,REV,DATE,DESC,NOTE,URL]);
}
The form fields are nested in a "parameter" property in the doPost parameter.
So, you should access them using:
function doPost(form) {
var actualForm = form.parameter;
var PN = actualForm.PartNumber;
//etc
To double check all parameters your receiving and their names, you could append to your sheet everything stringfied, like this:
sheet.appendRow([JSON.stringify(form)]);
--edit
This form.myFile.getURL() also looks odd. I guess another good debugging trick you could do is to wrap everything in a try-catch and email yourself any errors you get. For example:
function doPost(form) {
try {
//all your code
} catch(err) {
MailApp.sendMail('yourself#etc', 'doPost error', err+'\n\n'+JSON.stringify(form));
}
}
On form submit
onFormSubmit works. "doPost" looks wrong.
Simple example:
function Initialize() {
var triggers = ScriptApp.getProjectTriggers();
for(var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendGoogleForm")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendGoogleForm(e)
{
try
{
Full example - Scroll down to the code http://www.labnol.org/internet/google-docs-email-form/20884/ (Note: example sends email)
Trigger docs: https://developers.google.com/apps-script/guides/triggers/events
Notes: I think the problem is doPost, Does it work with google Forms? Never seen it used with google forms.
First and foremost, thank you everyone who has responded with information thus far. None of the solutions posted here worked for my particular implementation (my implementation is probably to blame, it is very crude), but they definitely set me down the path to a working version of my form which we now lightly use. I have posted some of the code below:
function sheetFill(form, link) {
try {
var formResponses = form.namedValues;
var toForm = [0,0,0,0,0,0,0];
for (i=0;i < form.PartNumber.length;i++){
toForm[0] = toForm[0]+form.PartNumber[i];
}
... (several for loops later)
var d = new Date();
var ss = SpreadsheetApp.openById("IDHERE");
var sheet = ss.getCurrentSheet;
ss.appendRow([toForm[0], toForm[1], toForm[2], toForm[3], toForm[4], toForm[5], toForm[6], link, d]);
} catch(err) {
MailApp.sendEmail('EMAIL', 'doPost error', err+'\n\n'+JSON.stringify(form));
}
}
It is not very versatile or robust and isn't elegant, but it is a starting point.