Order by sorted array - javascript

I have a page that contains a table like the following (automatically sorted by "Name" column)
Name Open Num Total Num
-----------------------------------
Doe, John 0 0
Smith, Sarah 4 3
Tyler, Rose 7 8
The second tr would look like this:
<tr id="1"><td class="N">Smith, Sarah</td><td class="O">4</td><td class="T">3</td></tr>
Where the row ID is a counter (first row = 0, second = 1, third = 2, etc.) and the cell class is to grab the column using jQuery ($(".O") gives Open Num column)
I'm trying to get the table to sort based off of the numerical columns (Open Num and Total Num). So output would look like this (sorted by Open Num or Total Num):
Name Open Num Total Num
-----------------------------------
Tyler, Rose 7 8
Smith, Sarah 4 3
Doe, John 0 0
So far, I store the numbers into an array arrQuick and I store the row number in a different array rowCount. I then use the Quick Sort method to sort the data, at the same time sorting the second array, which works perfectly. So now I have the sorted data and the order that my rows should be in.
The Problem
I cannot figure out how to get the table rows to update correctly.
So far I have this.
for(var i=0;i<rowCount.length;i++){
var tmpHolder=$("#"+i).html();
$("#"+i).html($("#"+rowCount[rowCount.length-(i+1)]).html());
$("#"+rowCount[rowCount.length-(i+1)]).html(tmpHolder);
}
When stepping through I can see that initially the updating is working. However, eventually it gets to some point rows are getting updated to places they shouldn't be and I'm not sure why.

You can sort the rows based on the values of table cells. The following method accepts a className of the cells and sorts the rows based on the text contents of that cells.
$.fn.sortTable = function(cls) {
this.find('tr').sort(function(a, b){
return $(a).find('td.'+cls).text() > $(b).find('td.' + cls).text();
}).appendTo(this.find('tbody'));
}
$('table').sortTable('O');
Updated method for supporting ascending and descending orders.
$.fn.sortTable = function (opt) {
var sort = typeof opt.sortType !== 'undefined' ? opt.sortType : 'asc',
cls = opt.cls;
if (!cls) return;
var $tr = this.find('tbody').children().sort(function(a, b){
return $(a).find('.' + cls).text() > $(b).find('.' + cls).text();
});
if (sort === 'des') {
$tr = $tr.toArray().reverse();
}
this.find('tbody').append($tr);
}
$('table').sortTable({
cls: 'N', // className of the cells
sortType: 'des' // order 'asc' || 'des'
});
http://jsfiddle.net/Af7mG/

Related

JQuery Access Arrays - Logical mistake possible

so i'm sitting here for hours and now i have to get the Eyes of the Internet to help me out...
So i'm trying to create a Shift Plan. I created a Table (each cell a jQuery Dropdown button with the possible Shifts). on every selection i write the selected Shifts into an array.
The following code snippet is how i do that. I need this in the on click because it will show the total shift time in n extra field.
Later i want save the arrays to a Database an because of that i need to access the calFirstRow array for each employee.
var calFirstRow = [,];
$(document).ready(function(){
$('.dropdown-menu a').on('click', function(){
// Do the following stuff for every Employee
$.each( jqEmployees, function(index, value){
var name = value['NAME'];
// create array from first row
$( ".first-row-"+ name +" td button" ).each(function( index ) {
calFirstRow[name, index] = $( this ).text();
});
});//End each Employee
});//End Dropdown click
Here i try to Access the array
});//End ready
the problem is, no matter what i do, in every calFirstRow[name] i get the array from the last Employee.
If i print the Array i get something like [User1: Array, User2: Array] but in each, User1 and 2 is the data of User2 saved...
Im new to jQuery and i maybe miss somethin really fundamental....
It looks like you're trying to treat calFirstRow as a two dimensional array, but the way you're accessing it doesn't make any sense. If you pop open your browser's development console and type [,], you'll see this:
> [,]
< [undefined × 1]
It's got only a single dimension, and JavaScript treats the comma as coming after an element, which you haven't provided, so you have an array containing a single undefined.
The next problem happens when you access calFirstRow[name, index]. Arrays can only be accessed by a single integer index, and JavaScript doesn't know what to do with name, so it ignores it and just uses index:
> a = ['a', 'b', 'c']
> a[1, 2]
< 'c' // The element at index 2
> a[2, 0]
< 'a' // The element at index 0
The result is that your loops aren't creating a two dimensional array that matches your table, they're just overwriting calFirstRow again and again, which is why your last user's data ends up in it. It looks like it's in every row, but really when you access calFirstRow['User1', 0] or calFirstRow['User2', 0], you're just accessing calFirstRow[0] twice.
Since you're trying to index by the employee's name, and then their column in the table, I think you want to start with an object. calFirstRow doesn't seem like the name for what you really want, so let's call it shiftData:
var shiftData = {}
The keys in the object will be your employees' names, and the values will be their array of shifts. For example:
> shiftData['Kristjan'] = [1,2,3]
< [1, 2, 3]
> shiftData['Joel'] = [4,5,6]
< [4, 5, 6]
> shiftData
< Object {Kristjan: Array[3], Joel: Array[3]}
Joel: Array[3]
0: 4
1: 5
2: 6
length: 3
Kristjan: Array[3]
0: 1
1: 2
2: 3
length: 3
In your click handler, you can map over each employee's row in the table and set their named entry to the result.
$.each( jqEmployees, function(employeeIndex, value){
var name = value['NAME'];
shiftData[name] = $( ".first-row-"+ name +" td button" ).map(function( columnIndex ) {
return $( this ).text();
});
});
This will result in a shiftData object like I showed in the console above. Each iteration of map returns the text of the td button in the user's row and column. Those values are aggregated into an array and assigned to shiftData[name].

Synchronize independent spreadsheet rows, filled by IMPORTRANGE()

I need to synchronize the contents of 2 spreadsheets that reference each other, keeping their rows in sync if a new row is added in one of the sheets.
I've got 2 spreadsheets in Google Sheets (although if there is a cross spreadsheet solution, both Excel and GS that would be great):
Spreadsheet1 has data in A:F and party1 (a set of users) writes their data in it.
Spreadsheet2 is and import range of A:F from spreadsheet1 and then has further details written in G:M, the data is written in by party2.
The way it works is party1 writes in their data in rows A1-F10 then party2 writes their additional data in spreadsheet2 based on what party1 has written in.
For example if Spreadsheet1 A1:F10 was a name, price, est delivery time, qty etc. of an item, Spreadsheet2 G1:M10 might be a bunch of data on order date, delivered (yes / no) etc.
The issue I'm currently having is that when the spreadsheets are setup they read across fine i.e. 1-10 in spreadsheet1 lines up with 1-10 in spreadsheet2, but after a while some new rows get added into spreadsheet1 between the old rows 2-5. This throws out the order in spreadsheet2 (now row 4 in spreadsheet1 doesn't line up with the row 4 in spreadsheet2 and the data becomes out of line). Is there away around this so that even if someone adds additional rows in the middle of existing rows both spreadsheets will update?
This is a classic problem in database design; how to associate information in two tables. The usual solution is to use key data; one or more columns that exist in both tables and provide a unique identifier, or key, to associate rows.
We can adapt that idea to your situation, with a script that will adjust the location of rows in Spreadsheet 2 to synchronize with Spreadsheet 1. To do that, we need to identify a key - say the Name column - which must exist in both spreadsheets.
This entails a small change in spreadsheet 2, where a Name column will now appear in column G, following the imported range in columns A-F.
A B C D E F G H I J
| Name | Price | est delivery time | qty | etc. of | an item | Name | order date | delivered | blah blah |
< - - - - - - - - - - - - Imported - - - - - - - - - - - > *KEY* < - - - - - - sheet 2 - - - - - >
Demo
Here's how that would look in action! This example is using two sheets in the same spreadsheet, just for convenience. In the demo, a new "Item" row is added in the middle of sheet 1, which automatically appears on sheet 2 thanks to the =IMPORTRANGE() function. The synchronizing function is running on a 1-minute timed Trigger, and you'll see it move things around about 20 seconds in.
You can grab a copy of the spreadsheet + embedded script here.
Code
/**
* Call syncTables() with the name of a key column.
*/
function doSyncTables() {
syncTables( "Name" );
}
/*
* Sync "Orders" spreadsheet with imported rows from "Items" spreadsheet.
*
* From: http://stackoverflow.com/a/33172975/1677912
*
* #param {String} keyName Column header used as key colum, appears
* at start of "Orders" data, following
* "Items" data.
*/
function syncTables( keyName ) {
var sheet2 = SpreadsheetApp.openById( sheetId2 ).getSheetByName('Orders');
// Get data
var lastCol = sheet2.getLastColumn();
var lastRow = sheet2.getLastRow(); // Includes all rows, even blank, because of =importRange()
var headers = sheet2.getRange(1, 1, 1, lastCol).getValues()[0];
var keyCol = headers.lastIndexOf( keyName ) + 1;
var itemKeys = sheet2.getSheetValues(1, 1, lastRow, 1).map(function(row) {return row[0]});
var itemData = sheet2.getSheetValues(1, 1, lastRow, keyCol-1);
var orderData = sheet2.getSheetValues(1, keyCol, lastRow, lastCol-keyCol+1);
var ordersByKey = []; // To keep track of orders by key
// Scan keys in orderData
for (var row=1; row<orderData.length; row++) {
// break loop if we've run out of data.
var orderKey = orderData[row][0];
if (orderKey === '') break;
ordersByKey[ orderKey ] = orderData.slice(row, row+1)[0];
var orderKey = orderData[row][0];
}
var newOrderData = []; // To store reordered rows
// Reconcile with Items, fill out array of matching orders
for (row = 1; row<itemData.length; row++) {
// break loop if we've run out of data.
var itemKey = itemData[row][0];
if (itemKey === '') break;
// With each item row, match existing order data, or add new
if (ordersByKey.hasOwnProperty(itemKey)) {
// There is a matching order row for this item
newOrderData.push(ordersByKey[itemKey]);
}
else {
// This is a new item, create a new order row with same key
var newRow = [itemKey];
// Pad out all columns for the new row
for (var col=1; col<orderData[0].length; col++) newRow.push('');
newOrderData.push(newRow);
}
}
// Update spreadsheet with reorganized order data
sheet2.getRange(2, keyCol, newOrderData.length, newOrderData[0].length).setValues(newOrderData);
}
the current answer by mogsdad is great as always. i just wanted to point out a less complex alternative:
if you can live with preventing spreadsheet1 from allowing insertions or deletion of rows, you will avoid the issue. instead of removing rows you could use a column to mark "deleted" for example (and use filters to remove from view).
to prevent row insertions and deletions in spreadsheet1, simply select an entire unused column to the right, and create a protected range so none of the editors have permission. that prevents modifying at the row level up to the last existing row (but new rows can still be inserted below the range)
it also doesnt prevent users from swapping two row's data. but its still good to know about this simpler alternative.

Sorting a jquery datatable based on a calcul between two columns

I have a jquery datatable that's initialized and populated using ajax.
I have a menu on the left with various "complex" sorting options.
My datatable contains a total price as well as the number of units sold.
I don't have and can't add a column "unit price" but i still would like my menu item "Sort by unit price" to work as intended which means that if i have the following table :
Name Units Price
----------------------
James 1 10
Eric 2 19
Greg 10 110
James 5 45
And i click on that button, i want it to be sorted like that :
Name Units Price
----------------------
James 5 45
Eric 2 19
James 1 10
Greg 10 110
I want to do this purely in javascript cause i don't want to mess with the controllers and models.
I managed to do this using the following:
$.fn.dataTableExt.afnSortData['unit-price'] = function ( oSettings, iColumn ) {
return $.map( oSettings.oApi._fnGetTrNodes(oSettings), function (tr, i) {
var price = parseInt($('td:eq('+iColumn+')', tr).text());
if(isNaN(price)) return 0;
var units = parseInt($('td:eq(1)', tr).text());
if(isNaN(units)) return 0;
var result = Math.round(1000*price/units);
return result;
} );
};
For now i'm keeping the fact that the units column is at index 1 but one could use a class for instance to keep this dynamic.
And then initializing in the datatable with:
theTable.dataTable({
...
"aoColumns": [
null,
null,
null,
null,
null,
{ "sSortDataType": "unit-price" }
]
});
I can now sort it with an external button using:
theTable.dataTable().fnSort([[5,'asc']]);

How to randomly return associative array names based on its values

I've a set of campaigns that correspond to a number of "points." Looks something like this:
[ {"c1":4, "c2":8, "c3":25} ]
I want to randomly pick a campaign from this set. I'm assuming rand() will come into play at some point. However, I want the value of each to affect its chances of being picked.
So, for example, campaign "c2" should be picked twice as often as campaign "c1" (roughly). "c3" would be king, being most likely to be picked.
The number of campaigns and corresponding values may or may not be the same every time the script runs.
What's a good way to go about this?
That's easy.
Just create another map that have CDF value for each campaign. For your example it will be:
0.108: C1
0.324: C2
1: C3
Then get a random number between 0 and 1. Go through the map and find the smallest number that is larger than the random number (you can binary search it or create a sorted hash map that can give smallest larger number also)
Note that by adding the probabilities the last entry may not add to 1 (can be 0.999). Just set it to 1 manually.
Here's a function that solves this for you. It creates a weighting array that you can use with a random number to properly weight each item per it's weighted value.
var campaigns = {"c1":4, "c2":8, "c3":24};
function getWeightedRandomCampaign(list) {
var weighting = [];
var total = 0;
for (var item in list) {
weighting.push({key: item, value: list[item]});
total += list[item];
}
// generate random number between 1 and total
var rand = Math.floor(Math.random() * total);
// figure out which weighted slot it fits in
var cum = 0;
for (var i = 0; i < weighting.length; i++) {
cum += weighting[i].value;
if (rand < cum) {
return(weighting[i].key);
}
}
return(weighting[weighting.length - 1]);
}
You can see it work here: http://jsfiddle.net/jfriend00/ffwqQ/
Here's how it works.
It starts with the campaigns objects and the weighting values.
var campaigns = {"c1":4, "c2":8, "c3":24};
Then, it builds a temporary data structure that looks like this:
var weighting = [{key: "c1", value: 4}, {key: "c2", value: 8}, {key: "c3", value: 24}];
While creating that data structure, it keeps track of the running total of all weight values.
It then creates a random number between 0 and that total.
It then walks through the weighting array adding up the values to find the first cumulative value that exceeds the random number. When it finds that, this is the slot that was selected.

reading / updating array of arrays object in javascript / jquery

I am trying to do the following but I am not sure how to do it. I have a grid (like excel) where the user will enter information. The grid has columns like "empno", "paycode", "payrate", "hours" etc. The user will enter a separate record in the grid for each paycode for each employee in the system. so the grid will look like the following
empno paycode payrate hours
1 R 25.00 40.00
2 R 12.00 40.00
3 R 15.00 18.00
1 v 25.00 12.00
2 PTO 25.00 18.00
Below this grid I will have two grids. One that displays "Employee totals" and one that displays "Totals by paycode". so as the user enters the data in the grid, I am planning to add / update the values into a two dimensional array. Then when the user clicks on a particular record on the grid, I will read the empno from that record and display the employee totals by binding the array to the html table. As you can see the data entered by the user might not be in the order of empno. The user might first enter the data for paycode "R", then paycode "V" etc.
Please let me know your ideas.
Why don't you just store it in a JavaScript object that's keyed off of your employee numbers. For instance...
var emps = {};
emps['1'] = [
{
empno : '1',
paycode : 'R',
payrate : '25.00',
hours : '40.00'
},
{
empno : '1',
paycode : 'G',
payrate : '30.00',
hours : '10.00'
}
];
function getEmp( empno ) {
if ( emps[empno] && typeof emps[empno] === "object" ) {
return emps[empno];
} else {
return false;
}
}
function addRecord( record ) {
if ( !emps[record.empno] || typeof emps[record.empno] !== "object" ) {
emps[record.empno] = [];
}
emps[record.empno].push( record );
}
It sounds like you want to loop through the input fields, and update the Employee total fields.
If the number of employee number records is unknown, I would use an id for each input similar to this: id=empno_x where x is a unique ID for each staff member. You can then use jquery to get the values using:
//Loop through all employee number input fields
$(":input[id^='empno_']").each function(){
//Do whatever you need to do with the data (add it to an array etc)
});
So you would do that for the empno, paycode, payrate, and hours fields.
Once you have captured the data being input, it's just a matter of manipulating the HTML using Jquery.
Hope this helps!

Categories