Basically I have a script that is in 4 blocks:
1. Copies within a range each row provided it meets a criteria
2. Removes all empty rows
3. Sets all numbers as percentage
4. Applies conditional cell formatting to one of the columns
The 4th part is the one that is causing me issues. The script runs without any error message AND block 4 works perfectly fine if it's in another script alone with the same variables defined but as soon as it is inside the same function as the others it simply doesn't run without any error message of any kind.
Tried changing the name of the variables to single use ones to ensure it wasn't because one of the "var" was modified above it, removing the "else if" to keep only an "if" in the loop, moving it around to other parts of the script but if the block 1 is in the script then block 4 won't apply (will apply if it is only with 2 & 3.
2 & 3 which follow the same structure work well with 1.
Does any one have any clue what's wrong with my script ? :)
Each block is commented with what it does
function copy() {
//Set variables & criterion to choose which rows to copy
var s = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1bEiLWsbFszcsz0tlQudMBgTk5uviyv_wDx7fFa8txFM/edit');
var ssSource = s.getSheetByName('Variations');
var ssDest = s.getSheetByName('Email');
var lastRowSource = ssSource.getLastRow();
var lastRowDest = ssDest.getLastRow();
var lastColSource = ssSource.getLastColumn()
var criteria = 0;
var titles = ssSource.getRange(1,1,1, lastColSource).getValues()
//Copies the range
ssDest.getRange(1,1,1, lastColSource).setValues(titles)
for (var i = 2; i < lastRowSource; i++ ) {
var test = ssSource.getRange(i ,1);
Logger.log(test.getValue()+ ' <? ' + criteria);
if (ssSource.getRange(i ,6).getValue() > criteria) {
ssSource.getRange(i ,1,1,ssSource.getLastColumn()).copyTo(ssDest.getRange(i ,1,1,ssSource.getLastColumn()), {contentsOnly:true}); // copy/paste content only
}
}
//Removes empty rows
var data = ssDest.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};
Logger.log(data[n].join().replace(/,/g,''))
}
ssDest.getDataRange().clear();
ssDest.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);
//Formats numbers as percentages
var rangePercent = ssDest.getRange(1,1,ssDest.getLastRow(),ssDest.getLastColumn());
var rowsPercent = rangePercent.getNumRows();
var colsPercent = rangePercent.getNumColumns();
for(var rowPercent = 1; rowPercent <= rowsPercent; rowPercent++) {
for(var colPercent = 1; colPercent <= colsPercent; colPercent++) {
var cellPercent = rangePercent.getCell(rowPercent, colPercent);
var valuePercent = cellPercent.getValue();
if(typeof(valuePercent) == 'number') {
cellPercent.setNumberFormat("##.#%");
}
}
}
//Adds conditional background colours
for (var z = 2; z < lastRowDest+1;z++) {
var avgCpc = 4;
var rangeColour = ssDest.getRange(z,avgCpc);
var dataColour = rangeColour.getValue()
if (dataColour < 0) {
ssDest.getRange(z,avgCpc).setBackground('#d9ead3')
}
else if (dataColour > 0) {
ssDest.getRange(z,avgCpc).setBackground('#f4cccc')
}
}
//Centers Values
}
The problem you're having is your code has performance issues because you're calling too many times methods such as getRange() and getValue() inside various loops, therefore Apps Script can't keep up with all those calls. Please check Best Practices.
Having said that, I modified your code in order to make it more efficient. Besides your copy function, I added two more functions to make the code more readable.
function copy
As before this function sets the variables, but now it calls two other functions, which are setPositiveCostValues and formatCells
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues
This will take the values where the cost is positive and it will get rip off of the cells with empty values and "n/a" values.
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells
This will format the cells in the cost col as a percentage and will set the right color in your avgCpc col.
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
Code
Your whole code now it will look like this:
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
I have written that has a for loop that will run additional code if (sheetData[i] !== "").
Ideally, I would like to run the following code after the condition, but then loop back around and run it again for the next item that matches the condition. Any ideas on how I can achieve this?
function getSheetSectionDataTest(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Params"); // get sheet
var sheetData = sheet.getDataRange().getValues(); //get all sheet data
var sectionNames = normalizeHeaders(normalizeHeaders(sheet.getRange('A1:A').getValues()));
var sectionData = []; // main array to contain all section data
// create an array for each section
for(h = 0; h < sectionNames.length; h++) {
sectionData[sectionNames[h]] /*property name or key of choice*/
= [];
}
for (var i = 0; i < sheetData.length; i++){ //loop through each row in the spreadsheet
var sectionName = normalizeHeaders(sheetData[i]); //return normalized camelCase section name found in column A.
if (sheetData[i] !== ""){ // Test - stop at a cell that matches the criteria and return the data table.
var headerRow = normalizeHeaders(sheetData[i+1]); //define and normalize the table headers
for (var j = i+2; j < sheetData.length; j++) { //loop through each row in the data table.
if (sheetData[j][1] !== ""){ //if there are contents in the table keep looping.
var obj = {};
sectionData[sectionName[0]].push(obj); // Need to replace ranges with a dynamic variable that gives us the current sectionName value as an object?
for (var rowColumn = 0; rowColumn < headerRow.length; rowColumn++) { //loop through each column in the current row of the table.
obj[headerRow[rowColumn]]=sheetData[j][rowColumn+1];
}
}
else { //stop when an empty cell is reached
return sectionData; //when the data table loop runs into an empty cell stop loop and return the data
}
}
}
}
};
I'm trying to dynamically create a table using JS and jQuery. (I know I can hard code it in html but I don't want to do that.)
I have an extra row at the table that I'm having trouble removing.
Can someone please provide an answer/answers on how best to solve this in my code?
My code is posted below.
var game = {
matrix: [],
startGame: function()
{
this.doDomStuff(); //build board
},
doDomStuff: function(row, column)
{
for(var i = 0; i < 7; i++)
{
var row = $('<div>');
row.attr('id', 'data-row' + (i + 1));
row.addClass('data-row');
for(var j = 0; j < 6; j++)
{
var column = $('<div>');
column.attr('id', 'data-column' + (j + 1));
column.addClass('data-column');
column.addClass('column');
row.append(column);
}
$('body').prepend(row);
}
}
};
If you are making a game, and each row can have its own logic and behaviour, so it will be faster to operate array neither DOM structure. If your logic will be more complecated, you will see the difference in perfomance. So, call the DOM structure if you are really need to update objects there. For example:
function SuperTable(){
this.tablerows = [];
this.DOMObject = $("body");
}
SuperTable.prototype.AddRow = function() {
this.tablerows[this.tablerows.length] = new SuperRow(this.tablerows.length,this.DOMObject);
}
SuperTable.prototype.RemoveRow = function(rowIndex) {
this.tablerows[rowIndex].DOMObject.remove(); // remove element from DOM
this.tablerows.splice(rowIndex,1); // remove element from logic of the game
}
function SuperRow(rownumber,parent) {
this.DOMObject = $("<div>");
this.DOMObject.addClass('data-row');
parent.prepend(this.DOMObject);
}
mytable = new SuperTable()
mytable.AddRow() // create row on 0 position
mytable.RemoveRow(0) // remove row from 0 position
You can use JQuery's .remove() function.
For example, if you want to remove the last row: $("#data-row7").remove()
I have made an array with objects that get there info from three different user variables, however on one of these there are many sub variables that i don't want it to repeat itself every time the user presses the select button(which updates the table) instead i want it to just add onto (or take away) from the sections that it already has in the table. thanks(if you need the variable code let me know) please help!! i really need a solution!!
//creating array
var gProducts = new Array();
var gTotalCost = 0;
// Adding Products to array gProducts
function addProduct
{
var product = new Object();
product.name = name;
product.cost = cost;
gProducts.push(product);
gTotalCost += parseInt(cost)
}
//Getting products from array, use of for in loop setting new table rows in blank var for each array item
function renderProducts()
{
var HTMLadd = ""
for (var i in gProducts)
{
if( gProducts[i].cost > 0){
HTMLadd = HTMLadd +
"<tr>"+
"<td class='tableSettings00' id=tableRow2 >" + gProducts[i].name +
"</td>"+
"<td class='tableSettings'>€<span id=tableRow2part2>" + gProducts[i].cost +
"</span></td>"+
"</tr>";
}
else
{
}
}
document.getElementById('tableRow').innerHTML = HTMLadd;
}
In the loop you always iterate through the whole gProducts, hence the values already placed to the table are doubled every time you execute the function.
This example uses some methods in HTMLTableElement to create new rows and cells. It also has a counter (index) holding the starting position of each iteration.
var index = 0; // Counter for holding the current position in gProducts
function renderProducts () {
var n, row, cell, span;
for (n = index; n < gProducts.length; n++) { // Start looping from the first new index in gProducts
if (gProducts[n].cost > 0) {
row = table.insertRow(-1); // Inserts a row to the end of table
cell = row.insertCell(-1); // Inserts a cell to the end of the newly-created row
cell.className = 'tableSettings00'; // Sets class for the cell
cell.id = 'tableRow' + index; // Sets an unique id for the cell
cell.innerHTML = gProducts[n].name; // Sets the content for the cell
cell = row.insertCell(-1); // Create another cell to row
cell.className = 'tableSettings';
cell.id = 'tableRowPart' + index;
cell.innerHTML = '€';
span = document.createElement('span'); // Creates an empty span element
span.id = 'tableRow2part' + index; // Sets an unique id for span
span.appendChild(document.createTextNode(gProducts[n].cost)); // Appends some text to the span
cell.appendChild(span); // Appends the span to the cell
}
}
index = n; // Sets counter equal to the length of gProducts
}
A live demo at jsFiddle.
As a sidenote, personally I prefer not to use ids for elements within a table. If ids are needed, you probably have constructed your table incorrectly, or the data is not suitable being shown in a table.
You can use indices and table.rows and rows.cells collections to find cells, and if some elements has to be searched for from a cell, use firstElementChild and nextElementSibling properties of a cell to find them.
I'm trying to create a select option bar that when onChange event is triggered, it returns the index of the selected file. For the first few selections, I get the correct number for the location of its index. However, after the third selection, the index being returned becomes 1 everytime I make a selection on the selection bar. Is there a way to fix this?
function handleUtilities(selection){
var index = selection.selectedIndex;
var selected = selection.options[index].value;
accountIndex = getOneUtility(data, selected);
}
function getOneUtility(array, utility){
var start = [];
var end = [];
var cost = [];
var usage = [];
var row = 0;
utility = utility.substring(0, utility.indexOf(")")+1);
for(row = 0; row < array.length; row++){
data = array[row][0];
if(data.indexOf(utility) != -1){
row += 3;
break;
}
}
return row;
}
I believe you are overwriting data in your for loop and the update to data doesn't impact functionality until the third run.
I suggest changing data = array[row][0]; to var data = array[row][0]; based on the supplied information.