I have the following table.
As you can see, for some columns I have to show Average/Total in the table footer. My code works well for columns without special formatting but when there is a special formatting like we have to show NEG for negative values, in that case the calculated value is not right. So I added data-value attribute for all cell. Now I want to access the data-value attribute of each cell for the function in footerCallback.
This is html code of an example cell in table
<td data-value="-3.78" class="red">
NEG
</td>
This is my footerCallback code.
"footerCallback": function(row, data, start, end, display) {
var api = this.api(),
data;
// Remove the formatting to get integer data for summation
var intVal = function(i) {
if (typeof i === 'string') {
//remove most useless characters
i = i.replace(/[\$,\),\-,\%,\NEG]/g, '');
//now replace ( with MIN
i = i.replace('(', 'MIN');
//check if i contains MIN, then remove it and multiply it with -1 to make it a negative number
if (i.includes("MIN")) {
i = i.replace('MIN', '');
i = i * (-1);
}
}
return typeof i === 'string' ? i.replace(/[\$,\),\%,\NEG]/g, '') * 1 : typeof i === 'number' ? i : 0;
};
var functionColumns = [#(string.Join(",", MathableColumns.Where(x => !string.IsNullOrEmpty(x))))];
for (var i = 0; i < functionColumns.length; i++) {
var colIndex = functionColumns[i];
total = api
.column(colIndex)
.data()
.reduce(function(a, b) {
return intVal(a) + intVal(b);
}, 0);
total = total.toFixed(2);
if (total < 0)
{
total = total * -1;
$(api.column(colIndex).footer()).html(
'('+total+')'
);
}
else
{
$(api.column(colIndex).footer()).html(
total
);
}
}
},
I've done some research but could not find solid solution for this.
Have a look at this fiddle - http://live.datatables.net/dovirovo/1/edit, this is doing, I believe, what you want. The key part of the code is:
totalValues = api.column(2).nodes().toArray().map(function(node) {
return $(node).attr('data-value');
});
This collects the jQuery nodes for that column, extracts the attribute, which can then be summed.
This is only doing a sum for the full column, but it should be straightforward to use this as a template to do the rows just on the current page, it just needs blending your original code and this.
Related
I have a script that I need to improve. The script goes through all the rows on column A. Then, it inserts a value on the next cell based on value. For example: If the value on cell A2 is 4,9, then it will insert UNDER10 to the cell B2. It works. But, it works so slowly. If I have thousands of rows on column A sometimes the script times out. Does anybody know a way to make this script faster?
Below is my script:
function myFunction() {
const ss = SpreadsheetApp.getActive().getActiveSheet()
const lastRow = ss.getLastRow();
for (var i = 1; i < lastRow +1; i++) {
var value = ss.getRange(i,1).getValue();
var newValue = ss.getRange(i,2);
if (value < 10) {
newValue.setValue("UNDER10");
} else if (value < 20) {
newValue.setValue("UNDER20");
} else if (value > 20) {
newValue.setValue("OVER20");
}
}
}
This improvement should work. Note that I assumed column A include numbers (google sheet refer 4,9 as string. therefor, the statement if(value < 10) is not realy valid).
To test my code, I used 4.9, 14.9, etc.
function myFunction() {
const ss = SpreadsheetApp.getActive().getActiveSheet()
const lastRow = ss.getLastRow();
// get all the range at once
let range = ss.getRange(2, 1, lastRow -1, 2);
// get all the values in 2D array
let values = range.getValues();
// for each pair of values [price, custom value], calculate the custom value
values.forEach((value)=> {
// NOTE that i parse float out of price.
// Google sheet refer 4,9 as string (i assume you ment 4.9)
value[0] = parseFloat(value[0])
if (value[0] < 10) {
value[1] = "UNDER10"
} else if (value[0] < 20) {
value[1] = "UNDER20";
} else if (value[0] > 20) {
value[1] = "OVER20";
}
})
// set the new values into the spreadsheet
range.setValues(values)
}
If you ment to compare each number in each row (for example, in 'A2' cell: if(4 < 10 && 9 < 10)) please comment and I'll fix accordingly.
There are two ways that i am able to add an auto increment column. By auto-increment, i mean that if column B has a value, column A will be incremented by a numeric value that increments based on the previous rows value.
The first way of doing this is simple, which is to paste a formula like the one below in my first column:
=IF(ISBLANK(B1),,IF(ISNUMBER(A1),A1,0)+1)
The second way i have done this is via a GA script. What i found however is performance using a GA script is much slower and error prone. For example if i pasted values quickly in the cells b1 to b10 in that order, it will at times reset the count and start at 1 again for some rows. This is because the values for the previous rows have not yet been calculated. I assume that this is because the GA scripts are probably run asynchronously and in parallel. My question is..is there a way to make sure each time a change happens, the execution of this script is queued and executed in order?
OR, is there a way i should write this script to optimize it?
function auto_increment_col() {
ID_COL = 1;
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
//only increment column 1 for sheets in this list
var auto_inc_sheets = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("auto_inc_sheets").getValues();
auto_inc_sheets = auto_inc_sheets.map(function(row) {return row[0];});
var is_auto_inc_sheet = auto_inc_sheets.indexOf(spreadsheet.getSheetName()) != -1;
if (!is_auto_inc_sheet) return;
var worksheet = spreadsheet.getActiveSheet();
var last_row = worksheet.getLastRow();
var last_inc_val = worksheet.getRange(last_row, ID_COL).getValue();
//if auto_inc column is blank and the column next to auto_inc column (col B) is not blank, then assume its a new row and increment col A
var is_new_row = last_inc_val == "" && worksheet.getRange(last_row, ID_COL+1).getValue() != "";
Logger.log("new_row:" + is_new_row + ", last_inc_val:" + last_inc_val );
if (is_new_row) {
var prev_inc_val = worksheet.getRange(last_row-1, ID_COL).getValue();
worksheet.getRange(last_row, ID_COL).setValue(prev_inc_val+1);
}
}
There is my vision of auto increment https://github.com/contributorpw/google-apps-script-snippets/tree/master/snippets/spreadsheet_autoincrement
The main function of this is
/**
*
* #param {GoogleAppsScript.Spreadsheet.Sheet} sheet
*/
function autoincrement_(sheet) {
var data = sheet.getDataRange().getValues();
if (data.length < 2) return;
var indexCol = data[0].indexOf('autoincrement');
if (indexCol < 0) return;
var increment = data.map(function(row) {
return row[indexCol];
});
var lastIncrement = Math.max.apply(
null,
increment.filter(function(e) {
return isNumeric(e);
})
);
lastIncrement = isNumeric(lastIncrement) ? lastIncrement : 0;
var newIncrement = data
.map(function(row) {
if (row[indexCol] !== '') return [row[indexCol]];
if (row.join('').length > 0) return [++lastIncrement];
return [''];
})
.slice(1);
sheet.getRange(2, indexCol + 1, newIncrement.length).setValues(newIncrement);
}
But you have to open the snippet for details because this doesn't work without locks.
Currently working on an application which requires to display a set of values in different currencies. This is part of an application but everything I provide here should be enough as this is the main section I am working with. I have a json file which is read in and it is stored into an array called valuesArray, this array has all the information, such as the amount, currency, etc. With the currencies being sorted with highest first to the lowest on display like this:
EUR 500.00
USD 200.00
This is the code that I have created but it seems like this wouldn't be effective the more currencies I have. I've just put an array declaration above the function but just above this function is where I do all the json stuff and adding it into the array. $scope.valuesArray has data at this point.
$scope.valuesArray =[];
$scope.total = function()
{
var eur_total = 0;
var usd_total = 0;
if (typeof $scope.valuesArray != 'undefined')
{
var length = $scope.valuesArray.length;
for (var i = 0; i<length ; i++)
{
switch($scope.valuesArray[i].currency)
{
case "USD":
usd_total += parseFloat($scope.valuesArray[i].value);
break;
default:
eur_total += parseFloat($scope.valuesArray[i].value);
break;
}
}
}
var cost_total= [usd_total,eur_total];
total.sort(function(a, b){return b-a});
return format_to_decimal(total[0]) + "\x0A" + format_to_decimal(total[1]);
}
In my for loop I go through every single data in the array and break each currency down within the switch statement and finding the total amount of each currencies.
The last bit is kind of temporary as I couldn't figure out a different way of how to do it. I sort the totals for the currencies I have from the highest at the top.
I return the function with a function call for format_numeric_with_commas which gives me the value in proper currency format and this displays the value. Will update this and add that code when I get to it. But I have used the indexes as a rough logic to show what I want to get out of it. So in this case, total[0] should be 500.00 and total[1] should be 200.00.
On top of this I want to be able to display the currency type for each. So like the example above.
You can try to save all the calculations in the array with currency index.
$scope.valuesArray = [];
$scope.total = function () {
var totalsArray = [];
if (typeof $scope.valuesArray != 'undefined') {
var length = $scope.valuesArray.length
for (var i = 0; i < length; i++) {
if (!totalsArray[$scope.valuesArray[i].currency]) {
totalsArray[$scope.valuesArray[i].currency] = 0;
}
totalsArray[$scope.valuesArray[i].currency] += $scope.valuesArray[i].value;
}
}
var cost_total = [];
for (var k in totalsArray) {
cost_total.push(currency:k,value:totalsArray[k]);
}
cost_total.sort(function (a, b) {
return b.value - a.value
});
return format_to_decimal(cost_total[0].value)+cost_total[0].currency + "\x0A" + format_to_decimal(cost_total[1].value);
I have the code to total a column, but I have subgroups that already have totals. Can I total each number in the column EXCEPT the gray total rows?
var table = $('#datatable');
var leng = table.find("tr:first").children().length;
// add totals to new row
for (var i = 0; i < leng; i++) {
var total = api
.column(i)
.data()
.reduce(function (a, b) {
// return if it's not a value from the gray row
return intVal(a) + intVal(b);
});
// correct any html mistakes that slip through
if (isNaN(intVal(total)))
total = '';
table.find("tfoot tr:first th").eq(i).html(total);
};
Why not just use the :not selector on the rows() API method and calculate the sum based on the remaining rows? Very small example, add the sum of col#1 to the footer in a callback :
var table = $('#example').DataTable({
drawCallback: function () {
var api = this.api(),
sum = 0;
api.rows(":not('.sgrouptotal')").every(function() {
sum += parseFloat(this.data()[0]);
});
$(api.column(0).footer()).text(sum);
}
});
demo -> http://jsfiddle.net/j38bmagj/
The above should be fairly easy to extend to multiple columns. Calculate the sum for col #4 in the same loop like sum4 += this.data()[4] and so on.
What about just doing that?
i = 0;
$('#datatable tr:first tr').each(function(key, value){
if(!$(this).hasClass("sgrouptotal"){
i += parseInt(value.text());
}
});
I'm building a jQuery table sorting script. Yes, yes, I know there are plug-ins for that, but where's the fun in ridding someone else's coat tails (not to mention an entire lack of learning and understanding)?
So I've got a good sort going on for alpha types, I'm now working on a numeric sort.
So quick down and dirty. I get the column values and push them into an array:
var counter = $(".sort tr td:nth-child("+(columnIndex+1)+")").length;
for (i = 1; i <= counter; i++) {
columnValues.push($(".sort tr:eq("+i+") td:eq("+columnIndex+")").text());
}
I then sort they array:
columnValues.sort(function(a,b){
return a-b
});
I then check for unique entries (this was mainly for same names):
$.each(columnValues, function(i, el){
if($.inArray(el, uniqueColumns) === -1) uniqueColumns.push(el);
});
I then use the array as a list of keys to get the data from the table and push into another array. This is where the problem comes in. For names it works just fine, but with number (i.e. 3, 30, 36) it doesn't. With the sorted list starting with 3 it sees the 3 in the 30 and/or 36 and grabs it.
Here is what I have tried:
for (i = 0; i < counter; i++) {
var key = uniqueColumns[i];
$(".sort tr:contains("+key+") td").each(function(){
rowValues.push($(this).text());
});
}
And:
for (i = 0; i < counter; i++) {
var key = uniqueColumns[i];
$(".sort tr td").filter(function(i){
if($(this).text() === key) {
rowValues.push($(this).text());
}
});
}
Here is the fiddle running the code with the first set of code (which works better then the second):
UPDATE:
Also just tried this (still not working, works for initial sort but not subsequent):
for (i = 0; i < counter; i++) {
var key = uniqueColumns[i];
var found = false;
$(".sort tr:contains("+key+")").filter(function(j){
$(this).children().each(function(){
if ($(this).text() === key) {
found = true;
}
});
$(this).children().each(function(){
if (found) {
rowValues.push($(this).text());
}
});
});
}
Figured it out on my own. Decided on a completely different approach. I created a multidimensional array out of the table data, then created a custom sort function that works for both numeric and alpha data. Here is the function that gets the data, sorts the data, and rewrites the table.
function sort(column) {
var columnIndex = $(column).index(),
rowValues = [];
/* Get Data */
$(".sort tr").not(":first-child").each(function () {
var innerArray = [];
$(this).find('td').each(function () {
innerArray.push($(this).text());
});
rowValues.push(innerArray);
});
/* Sort Data */
rowValues.sort((function(index){
return function(a, b){
if (!isNaN(a[index])) {
a[index] = parseInt(a[index]);
}
if (!isNaN(b[index])) {
b[index] = parseInt(b[index]);
}
return (a[index] === b[index] ? 0 :
(a[index] < b[index] ? -1 : 1));
};
})(columnIndex));
/* Replace Data */
$(".sort tr").not(":first-child").each(function(i){
$(this).find("td").each(function(j){
$(this).replaceWith("<td>"+rowValues[i][j]+"</td>");
});
});
}