I have the following code to sort a table using Javascript. What I want to do is make the last row of the table for totals and therefore need to append my code to remove the last row from sorting. I am having troubles with this task.
var TableSort = function ( tableId ) {
var that = this;
// Store a reference to the table node as a property
this.table = document.getElementById(tableId);
// Validate the table node
if ( this.table && this.table.nodeType != 1 ) {
throw new Error("TableSort: Table id is not a DOM element.");
}
if ( this.table.tagName != "TABLE" ) {
throw new Error("TableSort: Table id is not a table element.");
}
// Set three global properties
this.sortColumn = "";
this.sortUp = true;
this.imageNodes = [];
// Get the header nodes
var headers = this.table.getElementsByTagName("th");
// Loop through the header nodes
var header, columnName, imageNode, imageClick;
for ( var i = 0; i < headers.length; i++) {
header = headers[i];
// Create a node for the image
imageNode = document.createElement("img");
// Add the "sort arrow" images to the header column
// And set the first column as the default sort column
columnName = header.className;
if ( i == 0 ) {
this.sortColumn = columnName;
imageNode.src = "arrows_up.png";
} else {
imageNode.src = "arrows_off.png";
}
// Get the event handler for the image
imageClick = this.imageClickHandler(columnName);
// Add the event handler to the image
jsLib.event.add(imageNode, "click", imageClick);
// Add the image node to column header and to array of image nodes
header.appendChild(imageNode);
this.imageNodes[columnName] = imageNode;
}
// Sort the table
this.sort();
}
TableSort.prototype.imageClickHandler = function (columnName) {
var that = this;
// return an event handler
return function () {
// If current sort column, toggle the sort direction
if ( that.sortColumn == columnName ) {
that.sortUp = ! that.sortUp;
// Otherwise...
} else {
// Set the image for the old sort column
that.imageNodes[that.sortColumn].src = "arrows_off.png";
// Switch the current sort column and sort ascending
that.sortColumn = columnName;
that.sortUp = true;
}
// Set the appropriate arrows for the current sort column
if ( that.sortUp ) {
that.imageNodes[that.sortColumn].src = "arrows_up.png";
} else {
that.imageNodes[that.sortColumn].src = "arrows_down.png";
}
that.sort();
}
}
TableSort.prototype.sort = function () {
var that = this;
if ( this.sortColumn == "" ) return;
// Get the rows from the table
var rowNodes = this.table.getElementsByTagName("tr");
if (rowNodes.length <= 1 ) return;
// Store a reference to each row in the rows array
var rows = [];
for ( var i = 0; i < rowNodes.length; i++) {
rows.push( rowNodes[i] );
}
// Get a reference to the tbody node
var tbodyNode = rows[0].parentNode;
// Remove the header row from the array (but not the DOM)
rows.shift();
// Remove all rows except the header row from the DOM
var row;
for ( i = 0; i < (rows.length); i++ ) {
row = rows[i];
row.parentNode.removeChild(row);
}
// Define two functions that check for data types
var isMoney = function ( value ) {
var m = value.replace( /[$,]/g, "" );
return ! isNaN(m);
}
var isDate = function ( value ) {
var d = new Date(value);
return ! isNaN(d);
}
// Define the direction variable
var direction = ( that.sortUp ) ? 1 : -1;
// Define the function that compares the rows
var compareColumnValues = function (rowA, rowB) {
var valueA, valueB;
var i, cell;
// Get the cell value for row A
var cellsA = rowA.getElementsByTagName("td");
for ( i = 0; i < cellsA.length; i++ ) {
cell = cellsA[i];
if ( cell.className == that.sortColumn ) {
valueA = cell.firstChild.nodeValue;
break;
}
}
// Get the cell value for row B
var cellsB = rowB.getElementsByTagName("td");
for ( i = 0; i < cellsB.length; i++ ) {
cell = cellsB[i];
if ( cell.className == that.sortColumn ) {
valueB = cell.firstChild.nodeValue;
break;
}
}
// Convert the values to the appropriate data type
if ( ! isNaN(valueA) && ! isNaN(valueB) ) {
valueA = parseFloat(valueA);
valueB = parseFloat(valueB);
} else if ( isDate(valueA) && isDate(valueB) ) {
valueA = new Date(valueA);
valueB = new Date(valueB);
} else if ( isMoney(valueA) && isMoney(valueB) ) {
valueA = parseFloat(valueA.replace( /[$,]/g, "" ));
valueB = parseFloat(valueB.replace( /[$,]/g, "" ));
}
// Compare the two values and return the appropriate comparison value
if ( valueA < valueB ) {
return -1 * direction;
} else if ( valueB < valueA ) {
return 1 * direction;
} else {
return 0;
}
}
// Use the compareColumnValues function to sort the rows array
rows.sort( compareColumnValues );
// Add all rows back to the DOM
for ( i = 0; i < rows.length; i++) {
tbodyNode.appendChild( rows[i] );
}
}
You could use a bit more structure in your HTML markup to help your code figure out what can be sorted.
Put the totals row in a tfoot element and the sortable rows in a tbody element ... now you can target the sortable rows e.g.
var sortableRows = this.table.getElementsByTagName('tbody'),
rowNodes = sortableRows.getElementsByTagName('tr');
Update: Since you can't change the markup, remove the last row from your sorting array and cache it.
...
// Remove the header row from the array (but not the DOM)
rows.shift();
// And now we remove the footer row and cache it
var footerRow = rows.pop();
and then modify your code to write the sorted rows back to the DOM using insertBefore
...
// Add all rows back to the DOM, before the footer row
for ( i = 0; i < rows.length; i++) {
tbodyNode.insertBefore(rows[i], footerRow);
}
That loop is actually a bit of a performance problem, since you're making multiple writes to the DOM. It would be better to write all those sorted nodes to a documentFragment and just write to the DOM once, as follows:
...
var sortedFragment = document.createDocumentFragment();
// Write all the sorted rows to a fragment
for ( i = 0; i < rows.length; i++) {
sortedFragment.appendChild(rows[i]);
}
// Insert fragment containing sorted rows before footer
tbodyNode.insertBefore(sortedFragment, footerRow);
I found a great solution, which I actually used in code for my job! I can't take the credit for it however, but I just wanted to point y'all in the right direction:
http://www.kryogenix.org/code/browser/sorttable/
"1. Download the Javascript library here: http://www.kryogenix.org/code/browser/sorttable/sorttable.js
2.Include the Javascript library, by putting a link to it in the HEAD of your page, like so:
3.Mark your table as a sortable one by giving it a class of "sortable":
Note that the library's JavaScript file is called sorttable (two Ts), but the class you add to the table is sortable (one T)."
The setup is pretty straightforward and should allow for both ascending and descending sorts. Let me know if you have any questions.
Thanks! Paul
Related
I need to display unique elements of already binded data in that html table by using JQuery or JavaScript. Displaying unique value means that mearging the cells having same value in the rows. The table data is dynamic but the structure is fix.
So, here is given table:
Here, I need to merge the Red colored cells as shown below.
And the required table is:
So, I need to achieve this cell merging through Jquery or javascript. As this table is created with HTML table and the data is already binded in it. By using this binded data I need to achieve the reqiured table. I need to merge more columns like this.
I have tried some coding but it is not working correctly. The code is:
jQuery('tr.tblRow').each(function(i, obj)
{
current_name = jQuery(obj).text();
var current_name_arr = current_name.split(/\t|\n| /);
/* check for repeated names */
if (current_name_arr[22] == last_selected_compNo)
{
jQuery("td.headingSpas:contains('" + current_name_arr[22] + "')").each(function(index, object)
{
if (index == 0)
{
/* check if already has rowspan attribtue */
row_span = jQuery(object).attr('rowspan') ? jQuery(object).attr('rowspan') : 1;
/* add one */
row_span++;
/* include the new rowspan number */
jQuery(object).prop('rowspan', row_span);
}
else if(index < 2)
{
/* delete the other first name cells */
jQuery(object).remove();
}
});
}
}
Please help me.
I have written my own solution for this problem. It work for me..
/*Merging rows Start (updated by Suraj)*/
/*Some changes are made in HTML table's body like adding 'id' property in each column of the table.*/
var row = 0;
var col = 0;
jQuery('tr.tblRow').each(function(n, obj) {
/*Getting the first row as text*/
current_name = jQuery(obj).text();
/*Splitting the above text value into an array using 'Regex' by '/t' and '/n'*/
var current_name_arr = current_name.split(/\t|\n/);
/*Now storing the element into an array for getting the values easily.*/
var current_name_arr1 = [];
var j = 0;
for(var i = 0; i < current_name_arr.length; i++) {
if(current_name_arr[i] == ""){}
else {
current_name_arr1[j] = current_name_arr[i];
j++;
}
}
/*Setting column*/
if(n == 0) {
col = current_name_arr1.length;
}
});
jQuery('tr.tblRow').each(function(n, obj) {
var ele = ""
var removingEle = "";
/*Merging the cells by spaning the cells and removing the duplicate cells*/
for(var l = 0; l < col - 1; l++) {
if(l == 4 || l == 5) {
continue;
}
/*Getting the element by id*/
ele = $("#pdtDetail" + row + l);
/*Getting the removing element by id*/
removingEle = $("#pdtDetail" + (row + 1) + l);
if(ele.text() == removingEle.text()) {
/* check if already has rowspan attribtue */
row_span = ele.attr('rowspan') ? ele.attr('rowspan') : 1;
/* add one */
row_span++;
/* include the new rowspan number */
ele.prop('rowspan', row_span);
/* delete the other first name cells */
removingEle.remove();
}
}
/*If element get matched then increasing row by two because we have to leave the just after commming row.*/
if(ele.text() == removingEle.text()) {
row = row + 2;
}
else {
row++;
}
});
/*Merging rows End*/
The Final Table looks like as:
Im using REDIPS library so i can drag and drop some elements in a table. Following the provided examples by the library, I want to get the save_content() result as an array so I can insert those values inside my DB. This is my approach, but it doesn't work. Right now I get the value in an input displayed as text
save_content = function (tbl) {
var query = '', // define query parameter
tbl_start, // table loop starts from tbl_start parameter
tbl_end, // table loop ends on tbl_end parameter
tbl_rows, // number of table rows
cells, // number of cells in the current row
tbl_cell, // reference to the table cell
t, r, c, d; // variables used in for loops
// first sort tables array to it's original order
tables.sort(function (a, b) {
return a.idx - b.idx;
});
// if input parameter is undefined, then method will return content from all tables
if (tbl === undefined) {
tbl_start = 0;
tbl_end = tables.length - 1;
}
// if input parameter is out of range then method will return content from first table
else if (tbl < 0 || tbl > tables.length - 1) {
tbl_start = tbl_end = 0;
}
// else return content from specified table
else {
tbl_start = tbl_end = tbl;
}
// iterate through tables
for (t = tbl_start; t <= tbl_end; t++) {
// define number of table rows
tbl_rows = tables[t].rows.length;
// iterate through each table row
for (r = 0; r < tbl_rows; r++) {
// set the number of cells in the current row
cells = tables[t].rows[r].cells.length;
// iterate through each table cell
for (c = 0; c < cells; c++) {
// set reference to the table cell
tbl_cell = tables[t].rows[r].cells[c];
// if cells isn't empty (no matter is it allowed or denied table cell)
if (tbl_cell.childNodes.length > 0) {
// cell can contain many DIV elements
for (d = 0; d < tbl_cell.childNodes.length; d++) {
// childNodes should be DIVs, not \n childs
if (tbl_cell.childNodes[d].tagName === 'DIV') { // and yes, it should be uppercase
var value = $(tbl_cell).find("input").val();
query += 'p[]=' + tbl_cell.childNodes[d].id //obj id
+ '_' + value + '_' + r + '_' + c + '&';
}
}
}
}
}
}
// cut last '&'
query = query.substring(0, query.length - 1);
// return prepared parameters (if tables are empty, returned value could be empty too)
return query;
};
I would like to get the query value as an array so i can insert the values into the database without using $_REQUEST
function save() {
$('#moverCita').modal('toggle');
var content = REDIPS.drag.save_content();
document.getElementById("moverCitaRow").value = content;
}
I get the value as text inside the input
update.php
if (isset($_POST['submit'])){
if(isset($_POST['moverCitaRow'])){
if (!empty($_POST['moverCitaRow'])) {
$content = $_POST['moverCitaRow'];
$content = #$_REQUEST['p'];
if(is_array($content)) {
foreach ($content as $c) {
list($job_id, $job_bay_id, $row, $col) = explode('_', $c);
try {
update_calendar($job_id, $job_bay_id, $row, $col);
} catch (Exception $ex) {
$_SESSION["errorMsg"] = $ex->getMessage();
$_SESSION["errorType"] = "danger";
}
}
}
}
else {
echo '<center><strong style="color:red;">Llenar todos los campos
requeridos</strong></center><br>';
}
}
}
I have an unordered list with approx 50 li elements that is displayed in a four column grid with the addition of media queries that change the column count at different widths.
To support IE8&9, I am using fallback CSS and Modernizr to float the li elements left and specify widths and margin which simulates the CSS Column layout.
The only difference is that the items flow horizontally rather than vertically.
I want to perform some sort of "grid sort" re-shuffling of the $('.main-index li') array so that that the li elements run in an order like 1,14,27,40,2,15,28,41,3... rather than 1,2,3,4,5,6,7,8...
I have not got far, but this is that start I've made...
if (!Modernizr.csscolumns) {
// If CSS Columns is not supported, re-order li elements
// instead of 1,2,3,4,5,6,7,8... reorder to 1,14,27,40,2,15,28,41,3...
var columnCount = $('.main-index li').css('column-count'); // = 4
var itemCount = $('.main-index li').length; // = 50
}
I've included a jsFiddle here that i'm using to test.
Is this good enough?
$( document ).ready(function() {
// If CSS Columns is not supported, re-order li elements
// instead of 1,2,3,4,5,6,7,8... reorder to 1,14,27,40,2,15,28,41,3...
var items = $('.main-index li');
var columnCount = $('.main-index ul').css('column-count'); // = 4
if ( typeof columnCount != 'number' ) {
columnCount = 4;
}
var itemCount = items.length; // = 5
function reflow() {
var rowCount = Math.ceil(itemCount / columnCount);
items.detach();
$('.main-index ul').empty(); // Prevent build-up of dummy items
for (var row = 0; row < rowCount; row++) {
for (var column = 0; column < columnCount; column++) {
var index = row + (column * rowCount);
if ( index >= itemCount ) {
$('.main-index ul').append('<li class="dummy">');
} else {
$('.main-index ul').append(items[index]);
}
}
}
}
$(window).on('resize', function() {
var newColumnCount = $('.main-index ul').css('column-count'); // = 4
if ( typeof newColumnCount != 'number' ) {
newColumnCount = 4;
}
if ( newColumnCount != columnCount ) {
columnCount = newColumnCount;
reflow();
}
});
});
The topic of changing the Dojo DataGrid row background has been previously discussed on this board as well as the Dojo documentation, which gives a full example (http://dojotoolkit.org/reference-guide/1.9/dojox/grid/DataGrid.html).
My issue is a little bit different. I need to change the background color of the rows based on a common field - let's call it 'CityId'. If two rows have the same CityId, then they should have the same background. Note that this question is not concerned with the grouping nor ordering of the data in the grid, but only with the changing of the row style for adjacent rows sharing a common ID field.
The two main paths for this issue involve either hooking into the 'onStyleRow' event or the private '_onFetchComplete' event.
Here's my onStyleRow attempt:
var idx = 0;
var prevId = 0;
function myStyleRow(row) {
var item = grid.getItem(row.index);
if(item) {
var currId = store.getValue(item, "CityId", null);
if ( !!currId ) {
if ( prevId != currId ) {
prevId = currId;
idx++;
}
if ( idx%2 == 0 ) {
row.customStyles += 'background-color:#FFF000;';
} else {
//row.customStyles += 'background-color:#000;';
}
}
}
grid.focus.styleRow(row);
grid.edit.styleRow(row);
}
which gets called during the grid creation among other parameters
grid = new dojox.grid.DataGrid({onStyleRow:myStyleRow});
The reason why this approach is unstable is because the onStyleRow event gets triggered on mouse hover, causing the rows to be re-painted due to the prevId value. I would like to know whether/how I could disable onStyleRow from triggering on mouse hovers. This would solve my issue.
In the _onFetchComplete approach, I am stuck trying to find a way to access grid rows. Here's my code:
var idx = 0;
var prevId = 0;
grid.connect(grid, '_onFetchComplete', function() {
// wait until everything is loaded
for (var i in grid._pending_requests) {
if (grid._pending_requests[i]) {
return;
}
}
// parse through row data
for ( var j=0; j < grid.rowCount; j++) {
var item = grid.getItem(j);
if(item) {
var currId = store.getValue(item, "CityId", null);
if ( !!currId ) {
if ( prevId != currId ) {
prevId = currId;
idx++;
}
if ( idx%2 == 0 ) {
row.customStyles += 'background-color:#FFF000;';
} else {
//row.customStyles += 'background-color:#000;';
}
}
}
}
});
This is obviously a work in progress as there are two missing points: a way to iterate through the grid's rows (grid.rowCount does not work properly) and a way to fetch the grid's rows in order to apply the custom styles. I could not find something like grid.getRow() to address the issue.
Any pointers are appreciated.
Here's the final code:
var idx = 0;
var prevId = 0;
var passing = {};
var color = {};
dojo.connect(grid, 'onStyleRow', this, function (row) {
var item = grid.getItem(row.index);
if(item) {
var currId = store.getValue(item, "CityId", null);
if ( !passing[currId] ) {
passing[currId] = true;
if ( prevId != currId ) {
prevId = currId;
idx++;
}
if ( idx%2 == 0 ) {
row.customStyles += 'background-color:#FFF000;';
color[currId] = 'background-color:#FFF000;';
} else {
row.customStyles += 'background-color:#F2F7F7;';
color[currId] = 'background-color:#F2F7F7;';
}
} else {
row.customStyles += color[currId];
}
}
});
The 'passing' object is used to mark the first pass through the rows - this point is important because the 'prevId' value is updated correctly. The code works well with multiple calls to get more data into the grid because 'prevId' maintains its integrity. The 'color' object holds the values recorded for row backgrounds. This is needed because the onStyleRow event is called on mouseover, which implies the need to re-paint every time. I hope this helps someone else as well.
I'm using the following javascript to get the data in a clicked table cell:
var table = document.getElementById('ThisWeek'),
cells = table.getElementsByTagName('td');
for (var i=0,len=cells.length; i<len; i++)
{
cells[i].onclick = function()
{
document.getElementById("txtVal").value = this.innerText;
}
}
How can I modify this so I can also obtain the contents of the first cell in the clicked column and the first cell in the clicked row? E.g. if I click on "s" in the table below, I get the results "2" and "B" returned as two variables:
0 1 2 3
A q w e
B a s d
C z x c
Please note that I need a javascript solution, not jQuery. Ideally, I would also like clicks on the first row and first column to return empty strings.
This snippet uses the properties I've mentioned in my comment:
window.onload = function () {
var table = document.getElementById('table');
table.addEventListener('click', function (e) {
var target = e.target,
col = target.cellIndex,
row;
while (target = target.parentElement) {
if (!col && col !== 0) {
col = target.cellIndex;
}
if (target.tagName.toLowerCase() === 'tr') {
row = target.rowIndex;
break;
}
}
console.log(table.rows[row].cells[0].innerHTML + ', ' + table.rows[0].cells[col].innerHTML);
});
}
A live demo at jsFiddle.
I'd suggest:
function index(c){
var i = 0;
while (c.previousSibling){
/* if the previousSibling has a nodeType and that nodeType === 1
(indicating the previousSibling is an element) increment the i variable */
if (c.previousSibling.nodeType && c.previousSibling.nodeType === 1){
i++;
}
// reset c to the previousSibling
c = c.previousSibling;
}
/* i is the count of previous-siblings, and therefore the index
amongst those siblings: */
return i;
}
function getHeaders(e){
/* this is the table element,
e.target is the clicked element */
var el = e.target,
text = 'textContent' in document ? 'textContent' : 'innerText',
headers = [el.parentNode.getElementsByTagName('td')[0][text],this.getElementsByTagName('tr')[0].getElementsByTagName('td')[index(el)][text]];
// set the text of the nextElementSibling of the table:
this.nextElementSibling[text] = headers.join(', ');
}
document.getElementById('table').addEventListener('click', getHeaders);
JS Fiddle demo.
You can add this to your cells[i].onclick function:
var row = this.parentElement; // TR
var table = row.parentElement.parentElement; // TBODY > TABLE
document.getElementById("columnVal").value = row.rowIndex && this.cellIndex ? table.rows[0].cells[this.cellIndex].innerText : "";
document.getElementById("rowVal").value = row.rowIndex && this.cellIndex ? row.cells[0].innerText : "";