I want to initiate a range of worksheet with an array of values. I have the following code.
function initiate(address, values) {
return Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getActiveWorksheet();
var range = sheet.getRange(address);
var range.values = values;
return ctx.sync()
}
}
My tests show that this works only when values has exactly the same dimension (ie, height, width) as address, or when values is a single value. Otherwise, there will be an error.
Could anyone confirm that?
If so, I need to adjust address to suit the dimension of values. It is easy to get the dimension of values, but I cannot find a function to get a range from eg, 1 top-left cell + 1 number of rows + 1 number of columns, or 1 top-left cell and 1 bottom-right cell.
Am I missing something?
As CompuChip said, there is not (yet) an API for resizing a range to a particular absolute size, though it is forthcoming.
That said: if you have an array, just start with a single cell and then resize it by array's row-count-minus-1 (i.e., array.length - 1), followed by columns-minus-1 (array[0].length - 1)
const values = [
[1, 2, 3],
["Hello", "Bonjour", "Привет"]
]
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const range = sheet.getRange("D3").getResizedRange(
values.length - 1, values[0].length - 1);
range.values = values;
await context.sync();
});
You can try this snippet live in literally five clicks in the new Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/Zlatkovsky/6bc4a7ab36a81be873697cab0fa0b653. See more info about importing snippets to Script Lab.
In VBA you would use Range.Resize. In OfficeJS there seems to be a function called getResizedRange which
[g]ets a Range object similar to the current Range object, but with its bottom-right corner expanded (or contracted) by some number of rows and columns.
Unfortunately it accepts the delta values, so you need to calculate the difference between the current range size and the target size.
For example, if you wanted to obtain a range of rows by cols cells, you could try something along the lines of
var originalRange = sheet.getRange(address);
var range = originalRange.getResizedRange(
rows - originalRange.rowCount, cols - originalRange.columnCount);
Related
I receive a daily Excel file that has around 15K rows and 22 columns. There are a few hundred different values in column A. I want to copy or move certain rows to predefined tabs. For example, everything with the value "Home" would be moved to Sheet2, "Work" would be moved to Sheet3 etc.
Any guides to for follow for this or ideas?
Here's my attempt at a script that gets the result I think you want. Note the use of the 'getUsedRange()' method. It helps you access ranges of various sizes. There are probably several other ways to do this in typescript/javascript or by doing the filtering with Excel APIs.
function main(workbook: ExcelScript.Workbook) {
//get the used range, assuming there is one worksheet
let worksheet = workbook.getActiveWorksheet();
let range = worksheet.getUsedRange();
let rangeValues = range.getValues();
// create an array to hold all the unique values in the first column skipping the header row
let firstCol: string[] = [];
rangeValues.forEach((curRow, index) => {
if (index > 0) {
firstCol.push(curRow[0].toString());
}
});
let uniqueFirstCol = firstCol.filter((val, ind, arr) => arr.indexOf(val) === ind);
// run thru all the unique values, filter as appropriate and add filtered values to new worksheets
uniqueFirstCol.forEach(v => {
let tempValues = rangeValues.filter((row, index) => row[0] === v);
let newSheet = workbook.addWorksheet(v);
let newRange = newSheet.getRangeByIndexes(1,0, tempValues.length, tempValues[0].length);
newRange.setValues(tempValues);
newSheet.getRangeByIndexes(0,0, 1, rangeValues[0].length).setValues([rangeValues[0]])
});
}
I have a spreadsheet with 2 tabbed sheets. I am trying to run a macro so that when the user inputs a name in B2 of the 2nd sheet, it is matched with every instance of that name in the 1st sheet, column B. I then need to copy all of the data that appears in the matched cell's rows and have that pasted in the 2nd sheet starting with cell B3.
I have limited experience with VBA, but none with JS/Google-apps-script. Any help with how to write this would be greatly appreciated! Here is my first shot:
function onSearch() {
// raw data sheet
var original = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses 2");
// search for student sheet
var filtered = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Student Progress Search");
// retrieving the values in the raw data array of names
var searchColumn = 2;
var lr = original.getLastRow();
var searchRange = original.getRange(2,searchColumn, lr, 1).getValues();
// retrieving the name submitted on search
var inputName = filtered.getRange(2, 2).getValue();
// loop through all the names in the raw data and identify any matches to the search name
for (var i = 0; i < lr; i++){
var dataValue = searchRange[i];
var r = dataValue.getRow();
var line = [[r]];
var paste = filtered.getRange(3, 3);
// if the data is a match, return the value of that cell in the searched sheet
if (dataValue == inputName){ return paste.setValues(line);
}
}
}
Not sure if the built-in QUERY function would work for you. This here does exactly what you are looking for:
=QUERY(Sheet1!B:B,"select B where LOWER(B) like LOWER('%" &B2& "%')")
For example, if a user enters 'joe', the function will match any entry containing 'joe', regardless of case.
In Excel macro you can do something simple like:
Range("C8:G8,C12:H12,C19:I19").Value = 1
this gives me an opportunity to create a complex range selection and reduce the number of api calls and sync queue.
But when I do
const range = activeWorkSheet.getRange("C8:G8,C12:H12,C19:I19");
range.format.fill.color = "yellow";
I get
InvalidArgument: The argument is invalid or missing or has an
incorrect format.
There is also no getJoinRange or getUnionRange I could use.
Is there a workaround? I am having some performance issue as I do thing like format a row based on odd/even.
There is a beta feature forthcoming, which will allow multi-area ranges. Its syntax is still TBD, it might be exactly what you wrote (with range being allowed to be a multi-area range), or perhaps we'll keep Range a single contiguous object and have parallel methods like worksheet.getMultiAreaRange("C8:G8, C12:H12, C19:I19) to do what you would like.
Also, what version of Office do you have and are you on Insider Fast, by any chance?
Re. performance, can you post your exact use-case as a minimal snippet? There may be some optimizations you can do, even barring multi-area ranges.
Update
If all you're doing is a 3x3 or a 5x5 (i.e., not something super-huge), you don't need multi-area support. You can just do:
await Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const rowCount = 5;
const columnCount = 5;
const range = sheet.getRangeByIndexes(0, 0, rowCount, columnCount);
for (let row = 0; row < rowCount; row = row + 2) {
range.getRow(row).format.fill.color = "purple";
}
await context.sync()
});
I'm building an office-js add-in for Excel. I need to select two non-adjacent cells (e.g A1 and C3). The following code works to select the multi-cell range starting at A1 and ending at C3.
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getItem('sheet1');
var range = sheet.getRange('a1:c3');
range.select();
return ctx.sync();
});
However, I'm looking to select only the two cells (A1 and C3). In VBA the syntax is
worksheets("sheet1").range("a1,c3").select
But I cannot find anything analogous in office-js. I've tried as similar syntax with office-js:
Excel.run(function (ctx) {
var sheet = ctx.workbook.worksheets.getItem('sheet1');
var range = sheet.getRange('a1,c3');
range.select();
return ctx.sync();
});
but it fails with: {"code":"InvalidArgument","message":"The argument is invalid or missing or has an incorrect format.","errorLocation":"Worksheet.getRange"}
An API to work with discontinuous ranges is not yet available in Office.js. We are working on it and are finalizing the design right now. In the meantime, you will have to create separate range objects for the two cells and operate on each of them with duplicate commands.
Actually on the latest insiders fast (16.0.9327.2006 ) deployed just a few days ago you can actually try our implementation for Areas (aka discontinuous ranges. )
please make sure to use our preview cdn to test this.(https://appsforoffice.microsoft.com/lib/beta/hosted/office.js)
but basically you can do things like:
function run() {
return Excel.run(function (context) {
var range = context.workbook.getSelectedRange();
range.load("address");
return context.sync()
.then(function () {
console.log("The range address was \"" + range.address + "\".");
});
});
}
and you will see that if you select a non-continuous range you will get something like: "Sheet1!C6:C14,Sheet1!F12:H22".
you can pass a similar string on the getRange method to create an Area and simultaneously format it etc.
Please give it a try and send us your feedback! thanks!
context.workbook.getSelectedRange()
is used for contiguous ranges.
If you want to get the range for a discontiguous range you should use:
context.workbook.getSelectedRanges()
Situation:
1 spreadsheet
multiple sheets
1 cell selected (may vary)
What I'd like to do is to find and set focus to the next cell in any sheet that matches the selected cell (case insensitive) upon clicking a button-like image in the spreadsheet. Sort of like a custom index MS Word can create for you.
My approach is:
- set value of the selected cell as the variable (succeeded)
- find the first cell that matches that variable (not the selected cell) (no success)
- set value of found cell as variable2 (no success)
- set the focus of spreadsheet to variable2 (no success)
function FindSetFocus()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var activecell = sheet.getActiveCell();
var valueactivecell = activecell.getValue();
//here comes the code :)
}
I have found this snippet in the following topic, but I'm having a little trouble setting the input and doing something with the output: How do I search Google Spreadsheets?
I think I can replace 'value' with 'valueactivecell', but I don't know how to set the range to search through all sheets in the spreadsheet. Also, I'd like the output to be something I can set focus to using something like 'ss.setActiveSheet(sheet).setActiveSelection("D5");'
/**
* Finds a value within a given range.
* #param value The value to find.
* #param range The range to search in.
* #return A range pointing to the first cell containing the value,
* or null if not found.
*/
function find(value, range) {
var data = range.getValues();
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (data[i][j] == value) {
return range.getCell(i + 1, j + 1);
}
}
}
return null;
}
also found this but no luck on getting it to work on the selected cell and setting focus: How do I search for and find the coordinates of a row in Google Spreadsheets best answer, first code.
Please bear in mind that I'm not a pro coder :) If code samples are provided, please comment inline hehe.
Thanks in advance for any help.
Edit 24/10: Used the code from the answer below and edited it a bit. Now able to look through multiple sheets in a spreadsheet to find the value. The only problem with this bit is: My cell is highlighted yellow, but the cell with the value found isn't selected. See code below for hopping through sheets. I can't get my head around this :)
function SearchAndFind() {
//determine value of selected cell
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var cell = ss.getActiveCell();
var value = cell.getValue();
//create array with sheets in active spreadsheet
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
//loop through sheets to look for value
for (var i in sheets) {
//Set active cell to A1 on each sheet to start looking from there
SpreadsheetApp.setActiveSheet(sheets[i])
var sheet = sh.getActiveSheet();
var range = sheet.getRange("A1");
sheet.setActiveRange(range);
//set variables to loop through data on each sheet
var activeR = cell.getRow()-1;
var activeC = cell.getColumn()-1;
var data = sheets[i].getDataRange().getValues()
var step = 0
//loop through data on the sheet
for(var r=activeR;r<data.length;++r){
for(var c=activeC;c<data[0].length;++c){
step++
Logger.log(step+' -- '+value+' = '+data[r][c]);
if(data[r][c]==''||step==1){ continue };
if(value.toString().toLowerCase()==data[r][c].toString().toLowerCase()){
sheet.getRange(r+1,c+1).activate().setBackground('#ffff55');
return;
}
}
}
}
}
Here is an example of such a function, I inserted a drawing in my spreadsheet representing a button which I assigned the script so it's easy to call.
I added a feature to set a light yellow background on the resulting selected cell so it's easier to see the selected cell but this is optional.
Code
function findAndSelect(){
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getActiveSheet();
var cell = ss.getActiveCell();
cell.setBackground('#ffff55');// replace by cell.setBackground(null); to reset the color when "leaving" the cell
var activeR = cell.getRow()-1;
var activeC = cell.getColumn()-1;
var value = cell.getValue();
var data = ss.getDataRange().getValues();
var step = 0
for(var r=activeR;r<data.length;++r){
for(var c=activeC;c<data[0].length;++c){
step++
Logger.log(step+' -- '+value+' = '+data[r][c]);
if(data[r][c]==''||step==1){ continue };
if(value.toString().toLowerCase()==data[r][c].toString().toLowerCase()){
ss.getRange(r+1,c+1).activate().setBackground('#ffff55');
return;
}
}
}
}
Caveat
This code only searches 'downwards', i.e. any occurrence in a row that would precede the selected cell is ignored, same for columns...
If that's an issue for you then the code should be modified to start iterating from 0. But in this case if one need to ignore the initial starting cell then you should also memorize its coordinates and skip this value in iteration.