I'm attempting to limit the number of columns pulled from a data source. The source has 10 columns, I only need the first 4 columns. Seems I am confusing myself on userData and var j plus var k and var l when attempting to create an HTML table of the output.
When I use var i = 0; i < values.length; i++ and var j = 0; j < header.length; j++ with var k = 0; k < userData.length; k++ and var l = 0; l < userData[0].length; l++ the HTML output returns all the columns.
How can I limit the output to just the first 4 columns?
var sheetID = 'example';
var dataSheet = 'SOP Update ACK';
var emailHeader = 'Email Address';
var activeUser = Session.getActiveUser();
var ss = SpreadsheetApp.openById(sheetID);
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('Index').setTitle('My SOP Updates');
}
function currentUser() {
if (activeUser !== '') {
return activeUser.getEmail();
} else {
return "Couldn't detect user!!!";
}
}
function getData() {
var sheetName = dataSheet;
var activeSheet = ss.getSheetByName(sheetName);
var values = activeSheet.getDataRange().getDisplayValues();
var header = values[0];
var emailIndex = header.indexOf(emailHeader);
var userData = [];
for (var i = 0; i < values.length; i++) {
if (values[i][emailIndex] == activeUser) {
userData.push(values[i]);
}
}
if (userData.length > 0) {
var tableStart = '\n<table class="table table-hover table-sm">';
var tableHead = '\n<thead>\n<tr>';
for (var j = 0; j < header.length; j++) {
tableHead = tableHead + '\n<th>' + header[j] + '</th>';
}
tableHead = tableHead + '\n</tr>\n</thead>';
var tableBody = '\n<tbody>';
for (var k = 0; k < userData.length; k++) {
tableBody = tableBody + '\n<tr>';
for (var l = 0; l < userData[0].length; l++) {
tableBody = tableBody + '\n<td>' + userData[k][l] + '</td>';
}
tableBody = tableBody + '\n</tr>\n';
}
var tableEnd = '</tbody>\n</table>';
var tableHtml = tableStart + tableHead + tableBody + tableEnd;
return tableHtml;
} else {
return '<table class="table"><tbody><tr><td>No data found.</td></tr></tbody></table>';
}
}
I got this to work:
You will have to make some minor modification to get it to work for you because I tested it on my temp data since you did not provide any
function getData() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const [header, ...values] = sh.getDataRange().getDisplayValues();
const emailIndex = header.indexOf('Email Address');
const userData = [];
const activeUser = Session.getActiveUser();
for (let i = 0; i < values.length; i++) {
if (values[i][emailIndex] == activeUser) {
userData.push(values[i].slice(0,4));
}
}
if (userData.length > 0) {
let tableStart = '\n<table class="table table-hover table-sm">';
let tableHead = '\n<thead>\n<tr>';
for (let j = 0; j < header.length; j++) {
tableHead = tableHead + '\n<th>' + header[j] + '</th>';
}
tableHead += '\n</tr>\n</thead>';
let tableBody = '\n<tbody>';
for (let k = 0; k < userData.length; k++) {
tableBody = tableBody + '\n<tr>';
for (let l = 0; l < userData[0].length; l++) {
tableBody = tableBody + '\n<td>' + userData[k][l] + '</td>';
}
tableBody = tableBody + '\n</tr>\n';
}
let tableEnd = '</tbody>\n</table>';
let tableHtml = tableStart + tableHead + tableBody + tableEnd;
return tableHtml;
} else {
return '<table class="table"><tbody><tr><td>No data found.</td></tr></tbody></table>';
}
}
Related
i tried to find any info on it, i want to use height of the row in a cycle, i know that there is i.length, but is it one for height?
example with length:
for (var i = 2; i <= data.length+1; ++i)
i want it to look something like:
for (var g = 1; g<=grupa.height+1;g++)
this is my whole code, if you want for some reason to look at it:
function myFunction()
{
const ss = SpreadsheetApp.openByUrl ('my url');
var sheet = ss.getSheetByName("Розклад")
var data = sheet.getRange(2, 1, sheet.getLastRow()-1 , sheet.getLastColumn()).getValues();
Logger.log(data);
for (var i = 2; i <= data.length+1; ++i)
{
var data1 = sheet.getRange(i,1,1,13).getValues()
var data2 = sheet.getRange(i,8,1,13).getValues()
var grupas = data [i-2][4]
var cell= data [i-2][8]
var grupa = ss.getSheetByName(grupas)
var grupav = grupa.getRange(1,1,grupa.getLastRow(), grupa.getLastColumn()).getValues();
for (var g = 1; g<=grupa.height+1;g++)
{
var grupap = grupa[0][g-1]
Logger.log(grupap)
}
Logger.log(grupas)
Logger.log(grupav)
Logger.log(cell)
Logger.log(data1);
if ( cell ==="")
{
Logger.log('blank cell');
}
else
{
Logger.log(data2)
}
Logger.log(i-1);
}
}
var grupa = ss.getSheetByName(grupas)
var grupav = grupa.getRange(1,1,grupa.getLastRow(), grupa.getLastColumn()).getValues();
for (var j = 0; j < grupav.length; j++) {
/* grupav[j][0] */
}
Row loop
const col = 0;
const values = sheet.getDataRange().getValues();
for (const i = 0; i < values.length; i++) {
/* values[i][col] */
}
Column loop
const row = 0;
const values = sheet.getDataRange().getValues();
for (const j = 0; j < values[0].length; j++) {
/* values[row][j] */
}
Row & Column loop
const values = sheet.getDataRange().getValues();
for (const i = 0; i < values.length; i++) {
for (const j = 0; j < values[0].length; j++) {
/* values[i][j] */
}
}
I have a script that takes more than 5 minutes to execute. So it will be aborted after 5 minutes with the information that the execution time is too long.
Now I am looking for a solution to handle this. In the following example I would like to interrupt when var = i has reached a value of 300 and then continue with i = 301. Is this an option for a workaround? If so, how has the code to be adapted?
var xy = 1100;
for (var = i; i<= xy; i++){
{
do_some_stuff();
}
}
In practice my code is the following....and the loop is starting with for(var i = 3; i <= lastrow +1; i++) lastrow has a value of 1500.
function Create_geodata()
{
var Geodata = getSheetById(1702838837);
var Geojson_Data = getSheetById(1515926044);
var Control_Panel = getSheetById(1102907697);
Geodata.getRange('I4').setValue('Yes');
var Hyperlink_Geodata = Control_Panel.getRange('O18').getValue();
var document_missing_ID = Control_Panel.getRange('O24').getValue();
var Umap_Hyperlink = Control_Panel.getRange('L20').getValue();
var umap_zoom = Control_Panel.getRange('N22').getValue();
var Own_ID_Sort_Setting = Control_Panel.getRange('O30').getValue();
var Own_ID_Sort = Control_Panel.getRange('M32').getValue();
var Threshold_Altitude = Control_Panel.getRange('N28').getValue();
var Automatic_Altitude_Removal = Control_Panel.getRange('O26').getValue();
var Cat_A = Control_Panel.getRange('C36').getValue();
var Cat_B = Control_Panel.getRange('C38').getValue();
var Cat_C = Control_Panel.getRange('C40').getValue();
RESET_Geodata(); //Clear data before proceed
if (Own_ID_Sort_Setting)
{standard_sort_column = Own_ID_Sort}
else
{standard_sort_column = "name"}
var data = Geojson_Data.getDataRange().getValues();
var col1 = data[0].indexOf(standard_sort_column) + 1;
var col2 = data[0].indexOf('coordinates') + 1;
var col3 = data[0].indexOf('name') + 1;
var col4 = data[0].indexOf('description') + 1;
var col1_Char = columnToLetter(col1); //last row of column where sort-criteria is located
var col2_Char = columnToLetter(col2);
var col3_Char = columnToLetter(col3);
var col4_Char = columnToLetter(col4);
var lastrow_col1 = Geojson_Data.getRange(col1_Char + ':' + col1_Char).getValues();
lastrow_col1 = lastrow_col1.filter(String).length;
var lastrow = Geojson_Data.getLastRow();//last row of complete sheet
var lc = Geojson_Data.getLastColumn(); //last column
var lc_Char = columnToLetter(lc);
var dc = 0;
Geojson_Data.getRange('N1').setValue(col1);
if (!document_missing_ID)
{
for(var t = 2; t <= lastrow; t++)
{
if (Geojson_Data.getRange(t,col1).getValue() == "")
{
Geojson_Data.deleteRow(t);
}
}
}
else
{
for(var t = 2; t <= lastrow; t++)
{
if (Geojson_Data.getRange(t,col1).getValue() == "")
{
dc++;
var formattedNumber = ("000" + dc).slice(-4);
Geojson_Data.getRange(t,col1).setValue('ZZ_' + formattedNumber);
}
}
}
Geojson_Data.getRange('A2:' + lc_Char + lastrow).sort(col1);
Geojson_Data.getRange(col1_Char + 2 + ':' + col1_Char + lastrow).copyTo(Geodata.getRange('A3:A' + lastrow),{contentsOnly: true});
Geojson_Data.getRange(col2_Char + 2 + ':' + col2_Char + lastrow).copyTo(Geodata.getRange('F3:F' + lastrow),{contentsOnly: true});
Geojson_Data.getRange(col3_Char + 2 + ':' + col3_Char + lastrow).copyTo(Geodata.getRange('B3:B' + lastrow),{contentsOnly: true});
Geojson_Data.getRange(col4_Char + 2 + ':' + col4_Char + lastrow).copyTo(Geodata.getRange('D3:D' + lastrow),{contentsOnly: true});
var geodata_sorted = '';
var route_length = 0;
for(var i = 3; i <= lastrow +1; i++)
{
var pr = 0;
var fl = false;
var distance_neighbour_points = 0;
var Ar = Geodata.getRange('F' + i).getValue();
Ar = Ar.split(',');
var Ar_Anz = Ar.length - 1;
for(var b = 0; b <= Ar_Anz; b=b+2)
{
if ((Ar_Anz - b) != 0)
{
var E_Lat = Ar[b];
var E_Lon = Ar[b + 1];
if (fl)
{
var A_Lat = Ar[b - 2];
var A_Lon = Ar[b - 1];
distance_neighbour_points = distVincenty(A_Lat, A_Lon, E_Lat, E_Lon);
if (Automatic_Altitude_Removal && (distance_neighbour_points > Threshold_Altitude))
{
b++;
E_Lat = Ar[b];
E_Lon = Ar[b + 1];
A_Lat = Ar[b - 3];
A_Lon = Ar[b - 2];
}
route_length = route_length + distVincenty(A_Lat, A_Lon, E_Lat, E_Lon);
}
pr++;
fl = true;
}
geodata_sorted = geodata_sorted + E_Lat + ',' + E_Lon;
if ((Ar_Anz - b) < 2)
{
}
else
{
geodata_sorted = geodata_sorted + ',';
}
if ((Ar_Anz - b) < 2)
{
pr = parseInt(pr / 2) * 2 + 1;
var Ay = geodata_sorted.split(',');
var Geo_Mitte_Lon = Ay[pr - 1];
var Geo_Mitte_Lat = Ay[pr];
var googlemaps = 'https://maps.google.com/?q=' + Geo_Mitte_Lat + ',' + Geo_Mitte_Lon;
var umap = 'https://umap.openstreetmap.fr/de' + '/map/' + Umap_Hyperlink + '#' + umap_zoom + '/' + Geo_Mitte_Lat + '/' + Geo_Mitte_Lon;
Geodata.getRange(i, 3).setValue(route_length / 1000);
Geodata.getRange(i, 6).setValue(geodata_sorted);
Geodata.getRange(i, 7).setValue(googlemaps);
Geodata.getRange(i, 8).setValue(umap);
//Verlinkung
if (Hyperlink_Geodata)
{
var displayName1 = Geodata.getRange(i,1).getValue();
var displayName2 = Geodata.getRange(i,2).getValue();
Geodata.getRange(i,1).setFormula('=HYPERLINK(\"' + googlemaps + '\";\"' + displayName1 + '\")');
Geodata.getRange(i,2).setFormula('=HYPERLINK(\"' + umap + '\";\"' + displayName2 + '\")');
}
geodata_sorted = '';
route_length = 0;
}
}
//Set categories Cat_A,B,C...
var description_content = Geodata.getRange(i,4).getValue();
var regExpA = new RegExp('/*' + Cat_A, 'gi');
var regExpB = new RegExp('/*' + Cat_B, 'gi');
var regExpC = new RegExp('/*' + Cat_C, 'gi');
var compareA = regExpA(description_content);
var compareB = regExpB(description_content);
var compareC = regExpC(description_content);
if (compareC == Cat_C) Geodata.getRange(i,5).setValue(Cat_C);
if (compareB == Cat_B) Geodata.getRange(i,5).setValue(Cat_B);
if (compareA == Cat_A) Geodata.getRange(i,5).setValue(Cat_A);
}
SpreadsheetApp.getActiveSpreadsheet().setActiveSheet(Geodata);
Geodata.getRange('I4').setValue('No');
}
Here is my code:
function dataValidation()
{
var ss1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("2019년 출결 정보");
var ss2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("원생 정보");
var array = [];
for (var c = 0; c <= 1000; c++)
{
array[c] = [];
}
var count = 0;
var lastRow1;
var data1 = ss1.getRange("C:C").getValues();
for (var k = data1.length-1; k >= 0; k--)
{
if (data1[k][0] != null && data1[k][0] != '')
{
lastRow1 = k+1;
break;
}
}
var lastRow2;
var data2 = ss2.getRange("A:A").getValues();
for (var n = data2.length-1; n >=0; n--)
{
if (data2[n][0] != null && data2[n][0] != '')
{
lastRow2 = n+1;
break;
}
}
for (var i = 2; i <= lastRow1; i++)
{
for (var j = 2; j <= lastRow2; j++)
{
if (ss1.getRange(i, 3).getDisplayValue() == ss2.getRange(j, 1).getDisplayValue())
{
array[i-1][count] = ss2.getRange(j, 2).getDisplayValue();
count++;
}
}
var rule = SpreadsheetApp.newDataValidation().requireValueInList(array[i-1], true).setAllowInvalid(false).build();
ss1.getRange(i, 4).setDataValidation(rule);
count = 0;
}
}
I am so concerned because the compile speed takes too long... It takes about a minute to fully execute :(
Execution hint suggests to change from line 71:
for (var i = 2; i <= lastRow1; i++)
{
for (var j = 2; j <= lastRow2; j++)
{
if (ss1.getRange(i, 3).getDisplayValue() == ss2.getRange(j, 1).getDisplayValue())
{
array[i-1][count] = ss2.getRange(j, 2).getDisplayValue();
count++;
}
}
Personally I think this is because of nested loops but I don't have any clever ideas to replace this loops, nor I have much knowledge about google apps script.
What else I can do to decrease compile time?
Looking to get values from several cells in the first sheet (POTemplate) of my Google Sheet file that serves as an order entry form. Line 22 is the first line in the form that collects data from our user regarding items requested for order.
The tab to which I'd like to set these values (POHistory) will serve as a running log of all order details keyed into the POTemplate tab with each order. Each entry recorded in the PO template log should include each orders' unique PO No. (found in cell N3 of the POTemplate tab) & PO Date (cell N4 of the POTemplate tab). I sincerely appreciate the help.
function submit() {
var app = SpreadsheetApp;
var tplSheet = app.getActiveSpreadsheet().getSheetByName("POTemplate");
var tplFRow = 22, tplLRow = tplSheet.getLastRow();
var tplRowsNum = tplLRow - tplFRow + 1;
var tplFCol = 1, tplLCol = 16;
var tplColsNum = tplLCol - tplFCol + 1;
var rangeData = tplSheet.getRange(22, 1, tplRowsNum, tplColsNum).getValues();
var colIndexes = [0, 3, 10, 12, 15];
var fData = filterByIndexes(rangeData, colIndexes);
var target = "POHistory";
var targetSheet = app.getActiveSpreadsheet().getSheetByName(target);
var tgtRow = targetSheet.getLastRow() + 1;
var tgtRowsNum = fData.length - tgtRow + 1;
var tgtCol = 1;
var tgtColsNum = fData[0].length - 1 + 1;
targetSheet.getRange(tgtRow, tgtCol, tgtRowsNum,
tgtColsNum).setValues(fData);
}
function filterByIndexes(twoDArr, indexArr) {
var fData = [];
twoDArr = twoDArr.transpose();
for(var i=0; i<indexArr.length; i++) {
fData.push(twoDArr[indexArr[i]]);
}
return fData.transpose();
}
Array.prototype.transpose = function() {
var a = this,
w = a.length ? a.length : 0,
h = a[0] instanceof Array ? a[0].length : 0;
if (h === 0 || w === 0) {return [];}
var i, j, t = [];
for (i = 0; i < h; i++) {
t[i] = [];
for (j = 0; j < w; j++) {
t[i][j] = a[j][i];
}
}
return t;
}
I can offer you a modification of the submit() function you have given.
function process(){
submit();
submitDate();
}
function submitDate() {
var app = SpreadsheetApp;
var tplSheet = app.getActiveSpreadsheet().getSheetByName("POTemplate");
var tplFRow = 1, tplLRow = 21;
var tplRowsNum = tplLRow - tplFRow + 1;
var tplFCol = 1, tplLCol = 16;
var tplColsNum = tplLCol - tplFCol + 1;
var rangeData = tplSheet.getRange(tplFRow, tplFCol, tplLRow, tplColsNum).getValues();
var colIndexes = [13];
var fData = filterByIndexes(rangeData, colIndexes);
var target = "POHistory";
var targetSheet = app.getActiveSpreadsheet().getSheetByName(target);
var tgtRow = targetSheet.getLastRow() + 1;
var tgtRowsNum = fData.length - tgtRow + 1;
var tgtCol = 1;
var tgtColsNum = fData[0].length - 1 + 1;
Logger.log(fData)
fData=fData.transpose();
Logger.log(fData)
targetSheet.getRange(tgtRow-1, 5, fData.length,
fData[0].length).setValues(fData);
}
function submit() {
var app = SpreadsheetApp;
var tplSheet = app.getActiveSpreadsheet().getSheetByName("POTemplate");
var tplFRow = 22, tplLRow = tplSheet.getLastRow();
var tplRowsNum = tplLRow - tplFRow + 1;
var tplFCol = 1, tplLCol = 16;
var tplColsNum = tplLCol - tplFCol + 1;
var rangeData = tplSheet.getRange(22, 1, tplRowsNum, tplColsNum).getValues();
var colIndexes = [0, 3, 10, 12, 15];
var fData = filterByIndexes(rangeData, colIndexes);
var target = "POHistory";
var targetSheet = app.getActiveSpreadsheet().getSheetByName(target);
var tgtRow = targetSheet.getLastRow() + 1;
var tgtRowsNum = fData.length - tgtRow + 1;
var tgtCol = 1;
var tgtColsNum = fData[0].length - 1 + 1;
targetSheet.getRange(tgtRow, tgtCol, tgtRowsNum,
tgtColsNum).setValues(fData);
}
function filterByIndexes(twoDArr, indexArr) {
var fData = [];
twoDArr = twoDArr.transpose();
for(var i=0; i<indexArr.length; i++) {
fData.push(twoDArr[indexArr[i]]);
}
return fData.transpose();
}
Array.prototype.transpose = function() {
var a = this,
w = a.length ? a.length : 0,
h = a[0] instanceof Array ? a[0].length : 0;
if (h === 0 || w === 0) {return [];}
var i, j, t = [];
for (i = 0; i < h; i++) {
t[i] = [];
for (j = 0; j < w; j++) {
t[i][j] = a[j][i];
}
}
return t;
}
Basically, similar code runs twice. I could be criticized for doing this, but perhaps it is convenient for you this way.
I have a script where I'm syncing my google contacts with google spreadsheet.
I have around 11000 contacts in my account and I've written a function getContacts which fetches all contact information from the google contacts and sends it to a function printContacts,how ever as there are too many contacts the script fails to execute to completion.
I tried using the code to create a trigger everytime the max execution time is reached but now I'm facing a new problem,as I have too many ContactsApi call the script is showing an error saying 'user quota exceeded'
If anyone can get this code to work,it'd be a great help
//Function gets the contacts from the Google Contacts and stores in an array. Calls printContacts function with that array and no. of rows as argument
function getContacts() {
var startTime= (new Date()).getTime();
Logger.log("start time set")
var scriptProperties = PropertiesService.getScriptProperties();
var num_contacts = scriptProperties.getProperty('num_contacts');
Logger.log("script property num_contacts : "+Number(num_contacts))
setHeader();
var groupName = 'System Group: My Contacts';
var group = ContactsApp.getContactGroup(groupName);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var contacts = ContactsApp.getContactsByGroup(group);
var allContacts = [];
var contactDetails = [];
var data, numData, dataArray = [], extraArray = [];
var contact_length = contacts.length;
Logger.log("contact_length : "+contact_length);
Logger.log("contacts.length : "+contacts.length);
var MAX_RUNNING_TIME = 300000;
var REASONALBLE_TIME_TO_WAIT = 60000;
var limit = contact_length;
Logger.log("limit :"+limit)
for(var i=Number(num_contacts); i <= contacts.length; i++) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME){
Logger.log("Value of i in if section : "+i)
scriptProperties.setProperty("num_contacts",i);
ScriptApp.newTrigger("getContacts")
.timeBased()
.at(new Date(currTime+REASONALBLE_TIME_TO_WAIT))
.create();
Logger.log("trigger created for i = "+i)
break;
}
else{
if(i < contacts.length)
{
allContacts[i] = [];
contactDetails[0] = contacts[i].getGivenName();
contactDetails[1] = contacts[i].getFamilyName();
data = contacts[i].getEmails();
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getAddress();
}
contactDetails[2] = dataArray.join("\n");
dataArray = [];
data = contacts[i].getPhones(ContactsApp.Field.MOBILE_PHONE);
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getPhoneNumber();
}
contactDetails[3] = dataArray.join("\n");
dataArray = [];
data = contacts[i].getPhones(ContactsApp.Field.WORK_PHONE);
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getPhoneNumber();
}
contactDetails[5] = dataArray.join("\n");
dataArray = [];
// new section for home_phone ----- start
data = contacts[i].getPhones(ContactsApp.Field.HOME_PHONE);
numData = data.length;
for(var j = 0; j < numData; j++){
dataArray[j] = data[j].getPhoneNumber();
}
contactDetails[6] = dataArray.join("\n");
dataArray = [];
// new section for home_phone ----- end
data = contacts[i].getCompanies();
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getCompanyName();
extraArray[j] = data[j].getJobTitle();
}
contactDetails[7] = dataArray.join("\n");
dataArray = [];
contactDetails[8] = extraArray.join("\n");
data = contacts[i].getAddresses();
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getAddress();
}
contactDetails[9] = dataArray.join("\n");
dataArray = [];
contactDetails[10] = contacts[i].getNotes();
data = contacts[i].getCustomFields();
numData = data.length;
for(var j = 0; j < numData; j++) {
dataArray[j] = data[j].getValue();
}
contactDetails[11] = dataArray.join("\n");
dataArray = [];
data = [];
data = contacts[i].getContactGroups();
numData = data.length;
for(var j = 0; j < numData; j++) {
if(data[j].getName() == 'System Group: My Contacts')
{
continue;
}
else
{
dataArray[j] = data[j].getName();
}
}
contactDetails[4] = dataArray.join("\n");
dataArray = [];
contactDetails[12] = 'saved';
contactDetails[13] = contacts[i].getId();
for(var j = 0; j < 14; j++) {
allContacts[i][j] = contactDetails[j];
}
}
}
}
Logger.log("last value of i "+i);
Logger.log("num of contacts "+allContacts.length)
printContacts(allContacts, contacts.length);
setProperties_getContacts();
return;
}
//Function to print contacts to the spreadsheet.
function printContacts(contactsArray, i) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange(2, 1, i, 14);
range.setValues(contactsArray);
}