Updating totalRows in pager after ajaxProcessing - tablesorter

I am building my table using tablesorter with external ajaxProcessing call.
My pager output shows filteredRows and totalRows
.tablesorterPager({
output: '{startRow} - {endRow} / {filteredRows} filtered ({totalRows} total) ',
ajaxUrl:"{% url 'stats_json' %}{page}?{filterList:filter}&{sortList:column}&pagesize={size}",
}
I am returning the table rows and number of filtered rows in ajaxProcessing:
ajaxProcessing: function(data){
var rows=[];
....
rows.push(row);
return [data.filtered_rows_no,rows];
}
After filtering, my filteredRows is always the same as totalRows number but they should be different.
How should I update totalRows?

When returning extra values from within the ajaxProcessing function don't return an array. Instead, return an object containing the total, filtered rows and any extra values as follows (ref):
return {
total: data.total_rows_no,
filteredRows: data.filtered_rows_no,
rows: rows // array of arrays
};
For the total, I am guessing that the value is contained in data.total_rows_no as I don't think the actual total would equal the rows.length value.

Related

Google Sheets: Group rows and columns into cells by an ID

I have a table of tasks where rows are grouped using ID's. I want to represent these groupings in another table where there is one row for each ID and all data rows and columns are grouped for each ID in a single cell. I need a formula that can group certain rows into a cell using newlines, and certain columns into cells using delimiters. My data source is something like this:
The formula needs to:
Filter rows by Condition.
Put Task ID's in their own column, one row for each individual ID.
Put all data in Hours, Mins, Customer and Description columns into a cell next to ID so that:
Hours and Mins are joined with a ":" as a delimiter to represent time values
Customer and Description values are then concatenated to time values using space as a delimiter.
The result should look like:
A Google Sheet containing example of the source table and desired result:
https://docs.google.com/spreadsheets/d/14p_HYpfb7XlWhRRgo3JN7SPRerwEaB2A7Dz1KWuBgL8/edit#gid=299935838
SUGGESTION:
You can try this custom formula GROUPDATA using the script below. Just add it as a bound script on your spreadsheet file:
Script:
function GROUPDATA(data) {
var temp = "";
var filtered = [];
var container = [];
var col2 =[];
var final = [];
data.forEach(function(data) {
if(data[0] == true){
filtered.push(data[1])
container.push([data[1],data[2]+":"+data[3]+" "+data[4]+" "+data[5]])
}
});
const unique = (value,index,self) =>
{return self.indexOf(value) ===index;}
var unique_list=filtered.filter(unique);
for(i=0; i<unique_list.length; i++){
container.forEach(function(x) {
if(x[0]== unique_list[i]){
temp = temp+ x[1]+"\n";
}
});
col2.push(temp);
temp = "";
}
for(y=0; y<unique_list.length; y++){
final.push([unique_list[y],col2[y]]);
}
return final;
}
DEMONSTRATION:
Used the custom formula =GROUPDATA(A:F) on cell G14

Formula to import multiple columns to 1 column if a name from a different list matches any name in those columns

Looking for a way to import a long range of columns in to 1 list if a name from a different list matches any name in that particular column. I've tried a couple variations of query, filter, vlookup... Can't seem to find the right combination. Example..
List A on sheet 1 contains the names Jim, John and James. On a separate sheet, there are 5 columns containing names. Column 1 contains Jim, Alex and Ben. Column 2 contains Harold, Bob and Jimmy. Column 3 contains James, Jeremy and Felix. Column 4 contains James, Eric and Evan. Column 5 contains Sara, Jamie and Xavier. The end result should display the list in 1 column to contain the names - Jim, John, James, Alex, Ben, Jeremy, Felix, Eric and Evan. Columns 1, 3 and 4 would be imported to a single list because at least 1 name within those columns matched a name in the original list.
Example sheet.
UPDATED
You can try this sample implementation below using Apps Script custom function. Just copy and paste it to your spreadsheet file as a bound script:
NOTE: This script will check each name listed on the "Main List" & if each of them has matches on multiple columns on the second sheet Sheet2, then it will place all of the names on columns that contains any matched name on the "Imported List". The only catch using this implementation is that it'll run a bit slow when there's a huge amount of data to be processed.
SCRIPT
function onOpen(){ //Runs every time you open the spreadsheet
FINDMATCH();
}
function onEdit(){//Runs every time you make an edit to the sheet
FINDMATCH();
}
function FINDMATCH() { //Function to check each columns on "Sheet2" to see if it has names that match any names on the "Main List"
var main = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var mainList = [].concat.apply([], main.getRange("A4:A").getValues()).filter(String); //Main list data starts at A4
var sheet2 = SpreadsheetApp.getActive().getSheetByName("Sheet2"); //Name of the Sheet2
var result = mainList;
for(col=1; col <= sheet2.getDataRange().getLastColumn(); col++){
// E.G. if you only want to set the maximum of 2 rows as seen below, the "currentCol" code will only scan range A1:F3 on Sheet2 because the number 3 on the third parameter of the getRange() method will be the maximum row to be scanned.
var currentCol = [].concat.apply([], sheet2.getRange(1,col,2,1).getValues()).filter(String);
mainList.forEach(function(list){
currentCol.forEach(function(data){
if(data == list){
result.push(currentCol.toString());
return;
}
});
});
}
var data = FILTER(result.toString().trim().split(","));
main.getRange("AG4:AG").clearContent(); //Clears the old "Imported List" data before pasting updated list of names
main.getRange(4,33,data.length,1).setValues(data); //Updates the "Imported List"
}
function FILTER(array) { //Function that filters duplicate names
var data = array;
var newData = [];
var formattedData = [];
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if (row === newData[j] && row === newData[j]) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
newData.forEach(function(i){
formattedData.push([i]);
});
return formattedData;
}
Sample Result:
Sample Sheet
When spreadsheet gets updated with new data

Finding lowest value with no overlapping dates

I have a spreadsheet with criteria, a start and end date, and a value. The goal is to find the lowest value for each unique criteria and start date without overlapping dates (exclusive of end date). I made a pivot table to make it easier for myself but I know there is probably a way to highlight all valid rows that meet the above requirements with some formula or conditional formatting.
I have attached a google drive link where the spreadsheet can be found here and I have some images of the sheet as well. I know that it might be possible with conditional formatting but I just don't know how to combine everything I want it to do in a single formula.
Example below:
Row 2 is a valid entry because it has the lowest value for Item 1 starting on 03-15-2021, same with row 9.
Row 5 is valid because the start date does not fall within the date range of row 2 (exclusive of end date)
Row 7 is not valid because the start date is between the start and end date of row 6
You may add a bounded script to your project. Then you can call it either with a picture/drawing that has the function assigned (button-like), or adding a menu to Google Sheets.
From what you said in the question and the comments, this seems to do what you are trying. Notice that this requires the V8 runtime (which should be the default).
function validate() {
// Get the correct sheet
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const sheet = spreadsheet.getSheetByName('Sheet1')
// Get the data
const length = sheet.getLastRow() - 1
const range = sheet.getRange(2, 1, length, 4)
const rows = range.getValues()
const data = Array.from(rows.entries(), ([index, [item, start, end, value]]) => {
/*
* Row Index
* 1 Criteria 1
* 2 Item 1 0
* 3 Item 1 1
* 4 Item 1 2
*
* row = index + 2
*/
return {
row: index + 2,
criteria: item,
start: start.getTime(),
end: end.getTime(),
value: value
}
})
// Sort the data by criteria (asc), start date (asc), value (asc) and end date (asc)
data.sort((a, b) => {
let order = a.criteria.localeCompare(b.criteria)
if (order !== 0) return order
order = a.start - b.start
if (order !== 0) return order
order = a.value - b.value
if (order !== 0) return order
order = a.end - b.end
return order
})
// Iterate elements and extract the valid ones
// Notice that because we sorted them, the first one of each criteria will always be valid
const valid = []
let currentCriteria
let currentValid = []
for (let row of data) {
if (row.criteria !== currentCriteria) {
// First of the criteria
valid.push(...currentValid) // Move the valids from the old criteria to the valid list
currentValid = [row] // The new list of valid rows is only the current one (for now)
currentCriteria = row.criteria // Set the criteria
} else {
const startDateCollision = currentValid.some(valid => {
row.start >= valid.start && row.start < valid.end
})
if (!startDateCollision) {
currentValid.push(row)
}
}
}
valid.push(...currentValid)
// Remove any old marks
sheet.getRange(2, 5, length).setValue('')
// Mark the valid rows
for (let row of valid) {
sheet.getRange(row.row, 5).setValue('Valid')
}
}
Algorithm rundown
We get the sheet that we have the data in. In this case we do it by name (remember to change it if it's not the default Sheet1)
We read the data and transform it in a more an array of objects, which for this case makes it easier to manage
We sort the data. This is similar to the transpose you made but in the code. It also forces a priority order and groups it by criteria
Iterate the rows, keeping only the valid:
We keep a list of all the valid ones (valid) and one for the current criteria only (currentValid) because we only have to check data collisions with the ones in the same criteria.
The first iteration will always enter the if block (because currentCriteria is undefined).
When changing criteria, we dump all the rows in currentValid into valid. We do the same after the loop with the last criteria
When changing criteria, the CurrentValid is an array with the current row as an element because the first row will always be valid (because of sorting)
For the other rows, we check if the starting date is between the starting and ending date of any of the valid rows for that criteria. If it's not, add it to this criteria's valid rows
We remove all the current "Valid" in the validity row and fill it out with the valids
The cornerstone of the algorithm is actually sorting the data. It allows us to not have to search for the best row, as it's always the next one. It also ensures things like that the first row of a criteria is always valid.
Learning resources
Javascript tutorial (W3Schools)
Google App Scripts
Overview of Google Apps Script
Extending Google Sheets
Custom Menus in Google Workspace
Code references
Class SpreadsheetApp
Class Sheet
Sheet.getRange (notice the 3 overloads)
let ... of (MDN)
Spread syntax (...) (MDN)
Arrow function expressions (MDN)
Array.from() (MDN)
Array.prototype.push() (MDN)
Array.prototype.sort() (MDN)
Date.prototype.getTime() (MDN)
String.prototype.localeCompare() (MDN)

How to transpose some rows to columns?

There were a lot of questions with transpose but I have some specifics and can't create the right formula.
So I have near 100k rows in the following format
https://docs.google.com/spreadsheets/d/146-6YHv69DDOnPKheKeRFZIQeZYIgO6UwCMd7X9VeKU/edit?usp=sharing
I need ARRAYFORMULA or something like that to make this 100k rows in the format that you can see on the "Expected Outcome" tab.
=ARRAYFORMULA({UNIQUE(INDIRECT("source!A2:A"&COUNTA(source!A2:A))),
QUERY(SPLIT(TRANSPOSE(SPLIT(QUERY("♦"&INDEX(SPLIT(TRANSPOSE(SPLIT(
TRIM(QUERY(TRANSPOSE(QUERY(TRANSPOSE(
IF(source!B2:J<>"", "♦"&source!A2:A&"♠"&{"♥"&source!B2:B, source!C2:J}, ))
,,999^99)),,999^99)), "♦")), "♠"),,2),,999^99), "♥")), "♦"),
"where Col2 is not null", 0)})
You have a vast number of records but there are 19 data fields to each record spread over 10 columns & 12 rows. You suggested an arrayformula (and much more clever people than I can probably do that), but I suggest a script which takes the data in its current form on one sheet (say the 'source'), and which outputs the data to a new sheet (say, the 'target').
There are a couple of issues to be addressed in "mapping" the data:
how many records are represented on the input sheet - required to enable looping. I used the Javascript Math.floor method to calculate the number of products.
identify the correct row/column combination for each field. The data is in three segments
8 fields in contiguous columns in the first row,
2 fields in 2 rows in the ninth column, and
9 fields in nine contiguous rows in the tenth column.
Total = 12 rows/10 columns. To do this, in order to navigate the rows, I took the counter (i) multiplied by the number of rows, plus 1; the columns are more intuitive.
for efficiency,
get data once only at the beginning of the script;
use arrays to progressively build the output; and
update the output (setValues) once at the end of the script.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcesheetname = "source";
var targetsheetname = "target";
var source = ss.getSheetByName(sourcesheetname);
var target = ss.getSheetByName(targetsheetname);
var sourcerange = source.getDataRange();
var sourcedata = sourcerange.getValues();
var sourceheaders = 1;
var rowsperproduct = 12;
var sourcelr = source.getLastRow();
var integerPart = Math.floor((sourcelr-sourceheaders)/rowsperproduct);
//Logger.log("DEBUG: number of products = "+integerPart);
var rowdata = [];
// look thought the number of products
for (var i = 0; i<integerPart; i++){
// use a temporary array to build the data for the row.
var temp = [];
// row 1, copy first eight columns (0-7)
temp.push(sourcedata[(i*12)+1][0]);//ID
temp.push(sourcedata[(i*12)+1][1]);//GID
temp.push(sourcedata[(i*12)+1][2]);//NAME
temp.push(sourcedata[(i*12)+1][3]);//PRICE
temp.push(sourcedata[(i*12)+1][4]);//BRAND
temp.push(sourcedata[(i*12)+1][5]);//URL
temp.push(sourcedata[(i*12)+1][6]);//country
temp.push(sourcedata[(i*12)+1][7]);//instock
// row 2 & 3 in Column I(8)
temp.push(sourcedata[(i*12)+2][8]);//url1
temp.push(sourcedata[(i*12)+3][8]);//url2
// rows 4 - 12 in Column J(9)
temp.push(sourcedata[(i*12)+4][9]);// tech name
temp.push(sourcedata[(i*12)+5][9]);// size
temp.push(sourcedata[(i*12)+6][9]);// inches
temp.push(sourcedata[(i*12)+7][9]);// mm
temp.push(sourcedata[(i*12)+8][9]);// depth
temp.push(sourcedata[(i*12)+9][9]);// BB
temp.push(sourcedata[(i*12)+10][9]);// ref1
temp.push(sourcedata[(i*12)+11][9]);// ref2
temp.push(sourcedata[(i*12)+12][9]);// ref3
// Update the rowdata for this row
rowdata.push(temp);
}
// update data to target
target.getRange(2,1,integerPart,19).setValues(rowdata);
}

sfDoctrinePager with custom query returned only one row

I've tried to combine my Doctrine_Query and sfDoctrinePager, but when I fetch the results, it's always return one row (when I run the generated query in my database it was normal)
this is my code:
$pelanggan = Doctrine_Query::create()
->addSelect('l.id as id_transaksi,
l.jenis_bbm as jenis_bbm,
SUM(volume) as vol,
k.nama_kapal as nama_kapal
')
->from('laut_request_message l')->innerJoin('l.Kapal k')
->groupBy('l.id_kartu')
->orderBy('id_edc')
->where('volume > 0');
$pager = new sfDoctrinePager('laut_request_message', sfConfig::get('app_max_row_per_page',20));
$pager->setPage($request->getParameter('page',1));
$pager->setQuery($pelanggan);
$pager->init();
When i get the total row using $pager->count() it return all row in that query, but when I get the total result in the current page (count($pager->getResults()) it is only return a row.
Could you guys give me an advice? or may be another solutions ?

Resources