JavaScript Sorting a List with restrictions - javascript

I am making an app in Electron and have a customer list with their name and location. I have a button that allows the user to add a customer, but when doing so the table shows the index. I have another button that sorts out the names in alphabetical order, but if there are rows that have the index in them, it displays those first...
What I would like is some restriction that puts the rows with (0)(1) at the end of the list when sorted rather than the beginning.
Example:
The customers are sorted correctly, but all the rows with 0 come before the rows with actual words when I would like to have the words before the 0's.
Code:
for some reason in this code snippet it actually doesn't show the 0 or 1 index, but it still sorts the rows with nothing before the rows with text...
const back = document.getElementById('back');
const cust = document.getElementById('cust');
const custDiv = document.getElementById('custDiv');
const addCust = document.getElementById('addCust');
const inv = document.getElementById('inv');
const invDiv = document.getElementById('invDiv');
const addItem = document.getElementById('addItem');
// add customer
function appendRowCust() {
var custList = document.getElementById('custList'), // table reference
row = custList.insertRow(custList.rows.length), // append table row
i;
// insert table cells to the new row
for (i = 0; i < custList.rows[0].cells.length; i++) {
createCell(row.insertCell(i), i, 'row');
}
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(''); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
// sort customers
function sortCustTable() {
var custList, rows, switching, i, x, y, shouldSwitch;
custList = document.getElementById("custList");
switching = true;
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
// Start by saying: no switching is done:
switching = false;
rows = custList.getElementsByTagName("TR");
/* Loop through all table rows (except the
first, which contains table headers): */
for (i = 1; i < (rows.length - 1); i++) {
// Start by saying there should be no switching:
shouldSwitch = false;
/* Get the two elements you want to compare,
one from current row and one from the next: */
x = rows[i].getElementsByTagName("TD")[0];
y = rows[i + 1].getElementsByTagName("TD")[0];
// Check if the two rows should switch place:
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
// I so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}
if (shouldSwitch) {
/* If a switch has been marked, make the switch
and mark that a switch has been done: */
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
}
}
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
</table>
</div>
</div>

First, you need to summarize what your specifications are. To my understanding, they are this:
Your table needs to be sorted by the first column lexicographically...
Except for numeric or empty strings, which need to be ordered last in the column.
Now, the given answer by #IrkenInvader is correct in pointing out that you don't need to write your own sorting algorithm, but as far as implementing a correct and efficient solution, consider using the built-in algorithm for Array#sort() with some modifications:
function sortCustTable() {
var custList = document.getElementById('custList');
var rows = custList.getElementsByTagName('tr');
var parent = rows[0].parentElement;
var length = rows.length;
var data = [], ref, charCodes;
for (var index = 0; index < length; index++) {
ref = {
row: rows[index],
value: rows[index].firstElementChild.textContent.toUpperCase()
};
if (ref.value === '') {
ref.value = 'k'; // will sort after everything else, including numbers
} else if (!isNaN(ref.value)) {
charCodes = ref.value.split('').map(function (char) {
return Number(char) + 97; // charCode for 'a'
});
// for example, '05' would become 'af'
ref.value = String.fromCharCode.apply(String, charCodes);
}
data.push(ref);
}
data.sort(function (a, b) {
if (a.value > b.value) return 1;
if (a.value < b.value) return -1;
return 0;
});
for (var index = 0; index < length; index++) {
parent.appendChild(data[index].row);
}
}
I opted to use only ECMAScript 5 features, since a comment in your code indicates wanting to support Internet Explorer. I notice you're using const though, so feel free to modify using ES6 if you feel that would be easier.
Putting this together with the rest of your code, you can see it working below. I added some more default values to the table to give you an idea of how well it works:
const back = document.getElementById('back');
const cust = document.getElementById('cust');
const custDiv = document.getElementById('custDiv');
const addCust = document.getElementById('addCust');
const inv = document.getElementById('inv');
const invDiv = document.getElementById('invDiv');
const addItem = document.getElementById('addItem');
// add customer
function appendRowCust() {
var custList = document.getElementById('custList'), // table reference
row = custList.insertRow(custList.rows.length), // append table row
i;
// insert table cells to the new row
for (i = 0; i < custList.rows[0].cells.length; i++) {
createCell(row.insertCell(i), i, 'row');
}
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(''); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
// sort customers
function sortCustTable() {
var custList = document.getElementById('custList');
var rows = custList.getElementsByTagName('tr');
var parent = rows[0].parentElement;
var length = rows.length;
var data = [], ref, charCodes;
for (var index = 0; index < length; index++) {
ref = {
row: rows[index],
value: rows[index].firstElementChild.textContent.toUpperCase()
};
if (ref.value === '') {
ref.value = 'k'; // will sort after everything else, including numbers
} else if (!isNaN(ref.value)) {
charCodes = ref.value.split('').map(function (char) {
return Number(char) + 97; // charCode for 'a'
});
// for example, '05' would become 'af'
ref.value = String.fromCharCode.apply(String, charCodes);
}
data.push(ref);
}
data.sort(function (a, b) {
if (a.value > b.value) return 1;
if (a.value < b.value) return -1;
return 0;
});
for (var index = 0; index < length; index++) {
parent.appendChild(data[index].row);
}
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
<tr>
<td>Somebody</td>
<td>1</td>
</tr>
<tr>
<td></td>
<td>1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>someone else</td>
<td>1</td>
</tr>
<tr>
<td>somebody else</td>
<td>1</td>
</tr>
</table>
</div>
</div>
Now, to clarify why it sorts this way, let's look at the table values and how we modify them before sorting:
Customers | Main Location
---------------+---------------
Someone | something
Somebody | 1
| 1
0 | 1
someone else | 1
somebody else | 1
We'll discard the second column since we don't use that, and set all the customers to uppercase as well:
Customers |
---------------|
SOMEONE |
SOMEBODY |
|
0 |
SOMEONE ELSE |
SOMEBODY ELSE |
Next we check each string to see if it's empty, if so, we give it the value 'k':
Customers |
---------------|
SOMEONE |
SOMEBODY |
k |
0 |
SOMEONE ELSE |
SOMEBODY ELSE |
Then lastly, we modify any numbers by adding their numeric value to 97, and converting the resulting charCode into a character:
Customers |
---------------|
SOMEONE |
SOMEBODY |
k |
a |
SOMEONE ELSE |
SOMEBODY ELSE |
Sorting lexicographically, we get:
Customers |
---------------|
SOMEBODY |
SOMEBODY ELSE |
SOMEONE |
SOMEONE ELSE |
a |
k |
And putting back in the original values, we get:
Customers | Main Location
---------------+---------------
Somebody | 1
somebody else | 1
Someone | something
someone else | 1
0 | 1
| 1

I added a method to draw your table from an array of cell contents. Sorting the rows and calling drawTableRows will recreate the table in any order the rows array ends up. I added some code to insert mock data every third row so sorting numbers to the bottom can be seen.
This is a bigger change than I usually like to give in answers but I thought you might appreciate seeing a different approach.
var rows = [[ 'Someone', 'something' ]];
function drawTableRows() {
var custList = document.getElementById('custList'); // table reference
custList.innerHTML = '';
for(var i = 0; i < rows.length; i++) {
var row = rows[i];
var tableRow = custList.insertRow(i); // append table row
for(var j = 0; j < row.length; j++) {
createCell(tableRow.insertCell(j), row[j], 'row');
}
}
}
// add customer
function appendRowCust(customer = 0, location = 1) {
//throw in mock data every 3 rows (just a test - remove later)
if(rows.length % 3 === 0) {
customer = 'Real customer ' + rows.length;
location = 'Real location ' + rows.length;
}
rows.push([customer, location]);
drawTableRows();
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(text); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
function sortCustTable() {
rows.sort(function(a,b){
//sort by first column
var aVal = a[0];
var bVal = b[0];
//sort by cell content - if content is a number push to bottom.
if((bVal > aVal) || !isNaN(bVal)) {
return -1;
}
if((aVal > bVal) || !isNaN(aVal)) {
return 1;
}
return 0;
});
drawTableRows();
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
</table>
</div>
</div>

You can filter out the differences by attempting to cast to numeric and determining if the current iterated value is an integer. Afterwards simply sort and concatenate the two result sets.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
numeric = list.filter(value => Number.isInteger(+value)),
alpha = list.filter(value => !Number.isInteger(+value)),
result = alpha.sort().concat(numeric.sort());
To optimize the above you can filter once and push to a separately declared array alpha if the result is false.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
alpha = [],
numeric = list.filter(value => {
let torf = Number.isInteger(+value);
if (!torf) alpha.push(value);
return torf;
}),
result = alpha.sort().concat(numeric.sort());
Determining the difference between the two would be a micro-optimization that I doubt would be necessary in any situation and the former is more verbose and clear. My suggestion would be to use the first option.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
numeric = list.filter(value => Number.isInteger(+value)),
alpha = list.filter(value => !Number.isInteger(+value)),
result = alpha.sort().concat(numeric.sort());
console.log(result);

Related

Javascript set first column style

I'm using javascript to modify a page's styling and it's working great. The next step however is to change the styling of the first column of a table. How can I identify and set the styling of the first column only? My other changes so far are based on ID or based on a number of items having a class. In this example I just know they are TH or TD elements, and I want to change the ones in the first column.
In case anyone asks, this is my code so far... this is working and doesn't include anything to do with setting the style of the first column
function rotate_headers() {
const collection = document.getElementsByTagName("th");
for (let i = 0; i < collection.length; i++)
{
collection[i].innerHTML =
'<div style="padding-left: 100%;"><div style="transform: translate(7px, 3px) rotate(315deg); width: 30px;"><span style="border-bottom: 1px solid #ccc; padding: 5px 10px; color:grey;"' + collection[i].innerHTML + '</span> </div></div>';
// collection[i].style.background = "#6877c3"; //
//collection[i].style.height = "100px"; //
}
const collection2 = document.getElementsByClassName("table-bordered");
for (let i = 0; i < collection2.length; i++)
{ collection2[i].style.border = "0px";
collection2[i].style.marginTop = "95px";
}
const collection3 = document.getElementsByClassName("highlight");
for (let i = 0; i < collection3.length; i++)
{ collection3[i].classList.remove("highlight"); }
const collection4 = document.getElementsByClassName("table-content");
for (let i = 0; i < collection4.length; i++)
{ collection4[i].style.padding = "1rem 1rem"; }
const collection5 = document.getElementsByClassName("table-content");
for (let i = 0; i < collection5.length; i++)
{ collection5[i].style.width = "100px";
collection5[i].style.position = "relative";
collection5[i].style.whiteSpace = "nowrap";
collection5[i].style.overflowX = "scroll";
collection5[i].style.overflowY = "hidden";
}
}
The below code does what I want, but only if my table has an ID... which mine typically do not.
var table = document.getElementById('test');
for (var i = 1; i < table.rows.length; i++) {
var firstCol = table.rows[i].cells[0]; //first column
firstCol.style.background = 'red'; // or anything you want to do with first col
}
The code below does not work... which is my problem
var table = document.getElementsByTagName("table");
for (var i = 1; i < table.rows.length; i++) {
var firstCol = table.rows[i].cells[0]; //first column
firstCol.style.background = 'red'; // or anything you want to do with first col
}
You can just use document.querySelectorAll('tr th:first-child, tr td:first-child') then iterate the result setting the styles you want.
let firstCol = document.querySelectorAll('tr th:first-child, tr td:first-child')
for (let i = 0; i < firstCol.length; i++) {
firstCol[i].style.color = 'red'
}
<table>
<tr>
<th>one</th>
<th>two</th>
<th>three</th>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
I think you will find answer in next article:
https://developer.mozilla.org/en-US/docs/Learn/HTML/Tables/Basics
Styling without
There is one last feature we'll tell you about in this article before we move on. HTML has a method of defining styling information for an entire column of data all in one place — the and elements. These exist because it can be a bit annoying and inefficient having to specify styling on columns — you generally have to specify your styling information on every or in the column, or use a complex selector such as :nth-child.

Cant get addClass and removeClass to work properly, creating the "15 game" [duplicate]

This question already has answers here:
jQuery click event not working after adding class
(7 answers)
Closed 7 years ago.
I'm trying to code "the 15 game" using jquery in html, See this page. The game is about to get a table of "boxes" in order from 1-15.
My code:
// Insert numbers from 1 to arraysize
function insertElements(myArr, arraysize){
for (var i = 1; i < arraysize; i++ ) {
myArr.push(i);
}
}
// Check if the two cells is in range
function canMove(col, row, empty_row, empty_col){
if((row == empty_row+1) && (col == empty_col) ||
(row == empty_row-1) && (col == empty_col) ||
(row == empty_row) && (col == empty_col+1) ||
(row == empty_row) && (col == empty_col-1)){
return true;
}
else
return false;
}
// Swap elements in array
function swapElements(myArr, indexA, indexB){
// Check bounds
if((indexA > myArr.length) || (indexA < 0) || (indexB > myArr.length) || (indexB < 0)) {
alert("Out of bounds");
}
else{
var temp = myArr[indexA];
myArr[indexA] = myArr[indexB];
myArr[indexB] = temp;
}
}
// Wait for the page to finish loading
$(document).ready(function(){
// Create array
var myArr = [];
var arraysize = 17;
var lastelement = arraysize-1;
var empty_row;
var empty_col;
var empty_cell;
var col;
var row;
var nonempty_cell;
// Insert the elements
insertElements(myArr, arraysize);
// Number of shuffles
var shuffleNum = 10000;
// Shuffle the array
for (var i = 0; i < shuffleNum; i++ ) {
var f = Math.floor((Math.random()*16)+0);
var s = Math.floor((Math.random()*16)+0);
swapElements(myArr, f, s);
}
//printarray(myArr, myArr.length);
i = 0;
// For each td in the table
$(".maincontainer td").each(function() {
// Get the radnom value from the array
val = myArr[i];
// If the value is the last element
// assign this cell to emptycell and add the class empty
if(val == lastelement)
{
empty_cell = $(this);
empty_cell.addClass("empty");
empty_row = this.parentNode.rowIndex;
empty_col = this.cellIndex;
}
// Else, assign the value val to its text and add the class nonempty to it
else
{
$(this).text(val);
$(this).addClass("nonempty");
}
++i;
});
// If one of the nonempty boxes is clicked
$(".nonempty").click(function(){
// assign the cell that has been clicked to nonempty cell
nonempty_cell = $(this);
row = this.parentNode.rowIndex;
col = this.cellIndex;
// If the cell is in range of the empty cell
if(canMove(col, row, empty_row, empty_col)){
// Swap empty cell and the non emptycell clicked
var temp = empty_cell;
empty_cell = nonempty_cell;
nonempty_cell = temp;
// Swap coordinates
var temprow = row;
row = empty_row;
empty_row = temprow;
var tempcol = col;
col = empty_col;
empty_col = tempcol;
// Get text from the old empty cell
var new_value = $(empty_cell).text();
// Assign the value to the nonempty cell text and change class to nonempty
$(nonempty_cell).text(new_value);
$(nonempty_cell).removeClass("empty").addClass("nonempty");
// "Erase" the textstring and change class to empty
$(empty_cell).removeClass("nonempty").addClass("empty");
$(empty_cell).text("");
}
else
{
alert("Cant move!");
}
});
$(".empty").click(function(){
alert("Clicked empty cell!");
});
});
<!DOCTYPE html>
<html lang='sv'>
<head>
<meta charset="UTF-8" >
<title>The 15 game</title>
<style>
.maincontainer
{
width: 35%;
border: 10px solid black;
}
.maincontainer td
{
width: 100px;
height: 100px;
text-align: center;
font-size: 100px;
border: 3px solid black;
}
.nonempty
{
background-color: red;
}
.empty
{
background-color: #C0C0C0;
}
.nonempty:hover
{
border: 3px solid white;
}
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<!-- External javascript with the game -->
<script type="text/javascript" src="javascript/15game.js"></script>
</head>
<body>
<!-- Table which is the maincontainer of the 16 boxes -->
<table class="maincontainer" >
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
The code is kind of working correctly. When I click on a cell that is in range of the empty cell, the two cells are being swapped. The only problem is that the cell that first is assigned the class "empty" seems to keep the class "empty" when I try to replace its class to the class "nonempty".
I'm sorry for any typos, english is not my first language. Thanks for any help!
You are binding the events to the initial classes of the elements. Changing the classes later won't change the event bindings.
You can use delegated events instead, that way they detect the class at the time of the event:
$(".maincontainer").on("click", ".nonempty", function(){
and
$(".maincontainer").on("click", ".empty", function(){
Demo:
// Insert numbers from 1 to arraysize
function insertElements(myArr, arraysize){
for (var i = 1; i < arraysize; i++ ) {
myArr.push(i);
}
}
// Check if the two cells is in range
function canMove(col, row, empty_row, empty_col){
if((row == empty_row+1) && (col == empty_col) ||
(row == empty_row-1) && (col == empty_col) ||
(row == empty_row) && (col == empty_col+1) ||
(row == empty_row) && (col == empty_col-1)){
return true;
}
else
return false;
}
// Swap elements in array
function swapElements(myArr, indexA, indexB){
// Check bounds
if((indexA > myArr.length) || (indexA < 0) || (indexB > myArr.length) || (indexB < 0)) {
alert("Out of bounds");
}
else{
var temp = myArr[indexA];
myArr[indexA] = myArr[indexB];
myArr[indexB] = temp;
}
}
// Wait for the page to finish loading
$(document).ready(function(){
// Create array
var myArr = [];
var arraysize = 17;
var lastelement = arraysize-1;
var empty_row;
var empty_col;
var empty_cell;
var col;
var row;
var nonempty_cell;
// Insert the elements
insertElements(myArr, arraysize);
// Number of shuffles
var shuffleNum = 10000;
// Shuffle the array
for (var i = 0; i < shuffleNum; i++ ) {
var f = Math.floor((Math.random()*16)+0);
var s = Math.floor((Math.random()*16)+0);
swapElements(myArr, f, s);
}
//printarray(myArr, myArr.length);
i = 0;
// For each td in the table
$(".maincontainer td").each(function() {
// Get the radnom value from the array
val = myArr[i];
// If the value is the last element
// assign this cell to emptycell and add the class empty
if(val == lastelement)
{
empty_cell = $(this);
empty_cell.addClass("empty");
empty_row = this.parentNode.rowIndex;
empty_col = this.cellIndex;
}
// Else, assign the value val to its text and add the class nonempty to it
else
{
$(this).text(val);
$(this).addClass("nonempty");
}
++i;
});
// If one of the nonempty boxes is clicked
$(".maincontainer").on("click", ".nonempty", function(){
// assign the cell that has been clicked to nonempty cell
nonempty_cell = $(this);
row = this.parentNode.rowIndex;
col = this.cellIndex;
// If the cell is in range of the empty cell
if(canMove(col, row, empty_row, empty_col)){
// Swap empty cell and the non emptycell clicked
var temp = empty_cell;
empty_cell = nonempty_cell;
nonempty_cell = temp;
// Swap coordinates
var temprow = row;
row = empty_row;
empty_row = temprow;
var tempcol = col;
col = empty_col;
empty_col = tempcol;
// Get text from the old empty cell
var new_value = $(empty_cell).text();
// Assign the value to the nonempty cell text and change class to nonempty
$(nonempty_cell).text(new_value);
$(nonempty_cell).removeClass("empty").addClass("nonempty");
// "Erase" the textstring and change class to empty
$(empty_cell).removeClass("nonempty").addClass("empty");
$(empty_cell).text("");
}
else
{
alert("Cant move!");
}
});
$(".maincontainer").on("click", ".empty", function(){
alert("Clicked empty cell!");
});
});
<!DOCTYPE html>
<html lang='sv'>
<head>
<meta charset="UTF-8" >
<title>The 15 game</title>
<style>
.maincontainer
{
width: 35%;
border: 10px solid black;
}
.maincontainer td
{
width: 100px;
height: 100px;
text-align: center;
font-size: 100px;
border: 3px solid black;
}
.nonempty
{
background-color: red;
}
.empty
{
background-color: #C0C0C0;
}
.nonempty:hover
{
border: 3px solid white;
}
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<!-- External javascript with the game -->
<script type="text/javascript" src="javascript/15game.js"></script>
</head>
<body>
<!-- Table which is the maincontainer of the 16 boxes -->
<table class="maincontainer" >
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
Your problem here is that jQuery line here:
$(".empty").click(function(){
alert("Clicked empty cell!");
});
See, the line matches all elements against '.empty' and adds the click event handler to them. When you change classes on the one element, the event handler isn't reset.
What you need is an event handler on a larger scope, say the table, or the document. Then check to see if the event target has the class.

Handle cells with rowspan when hiding table rows

I have a table containing cells with rowspan attributes, I would like to:
Whenever a tr is hidden, the table will rearrange itself correctly
Whenever a tr is shown again, it will be restored to original state
So if you have a table like this clicking on X shouldn't destroy the layout.
and click a come back button, should restore the original layout.
(try removing all rows from bottom-up, and than restoring them from right-to-left, this is a desired flow)
I had some semi-solutions, but all seem too complicated, and i'm sure there is a nice way to handle this.
OK I really spent a hell of a long time over this question, so here goes...
For those of you who just want to see the working solution, click here
Update: I've changed the visual columns calculation method to iterate over the table and create a 2-dimensional array, to see the old method which used the jQuery offset() method, click here. The code is shorter, but more time costly.
The problem exists because when we hide a row, whilst we want all the cells to be hidden, we want the pseudo-cells — that is, the cells that appear to be in the following rows due to the cells rowspan attribute — to persist. To get around this, whenever we come across a hidden cell with a rowspan, we try to move it down the the next visible row (decrementing it's rowspan value as we go). With either our original cell or it's clone, we then iterate down the table once more for every row that would contain a pseudo-cell, and if the row is hidden we decrement the rowspan again. (To understand why, look at the working example, and note that when the blue row is hidden, red cell 9's rowspan must be reduced from 2 to 1, else it would push green 9 right).
With that in mind, we must apply the following function whenever rows are shown/hidden:
function calculate_rowspans() {
// Remove all temporary cells
$(".tmp").remove();
// We don't care about the last row
// If it's hidden, it's cells can't go anywhere else
$("tr").not(":last").each(function() {
var $tr = $(this);
// Iterate over all non-tmp cells with a rowspan
$("td[rowspan]:not(.tmp)", $tr).each(function() {
$td = $(this);
var $rows_down = $tr;
var new_rowspan = 1;
// If the cell is visible then we don't need to create a copy
if($td.is(":visible")) {
// Traverse down the table given the rowspan
for(var i = 0; i < $td.data("rowspan") - 1; i ++) {
$rows_down = $rows_down.next();
// If our cell's row is visible then it can have a rowspan
if($rows_down.is(":visible")) {
new_rowspan ++;
}
}
// Set our rowspan value
$td.attr("rowspan", new_rowspan);
}
else {
// We'll normally create a copy, unless all of the rows
// that the cell would cover are hidden
var $copy = false;
// Iterate down over all rows the cell would normally cover
for(var i = 0; i < $td.data("rowspan") - 1; i ++) {
$rows_down = $rows_down.next();
// We only consider visible rows
if($rows_down.is(":visible")) {
// If first visible row, create a copy
if(!$copy) {
$copy = $td.clone(true).addClass("tmp");
// You could do this 1000 better ways, using classes e.g
$copy.css({
"background-color": $td.parent().css("background-color")
});
// Insert the copy where the original would normally be
// by positioning it relative to it's columns data value
var $before = $("td", $rows_down).filter(function() {
return $(this).data("column") > $copy.data("column");
});
if($before.length) $before.eq(0).before($copy);
else $(".delete-cell", $rows_down).before($copy);
}
// For all other visible rows, increment the rowspan
else new_rowspan ++;
}
}
// If we made a copy then set the rowspan value
if(copy) copy.attr("rowspan", new_rowspan);
}
});
});
}
The next, really difficult part of the question is calculating at which index to place the copies of the cells within the row. Note in the example, blue cell 2 has an actual index within its row of 0, i.e. it's the first actual cell within the row, however we can see that visually it lies in column 2 (0-indexed).
I took the approach of calculating this only once, as soon as the document is loaded. I then store this value as a data attribute of the cell, so that I can position a copy of it in the right place (I've had many Eureka moments on this one, and made many pages of notes!). To do this calculation, I ended up constructing a 2-dimensional Array matrix which keeps track of all of the used-visual columns. At the same time, I store the cells original rowspan value, as this will change with hiding/showing rows:
function get_cell_data() {
var matrix = [];
$("tr").each(function(i) {
var $cells_in_row = $("td", this);
// If doesn't exist, create array for row
if(!matrix[i]) matrix[i] = [];
$cells_in_row.each(function(j) {
// CALCULATE VISUAL COLUMN
// Store progress in matrix
var column = next_column(matrix[i]);
// Store it in data to use later
$(this).data("column", column);
// Consume this space
matrix[i][column] = "x";
// If the cell has a rowspan, consume space across
// Other rows by iterating down
if($(this).attr("rowspan")) {
// Store rowspan in data, so it's not lost
var rowspan = parseInt($(this).attr("rowspan"));
$(this).data("rowspan", rowspan);
for(var x = 1; x < rowspan; x++) {
// If this row doesn't yet exist, create it
if(!matrix[i+x]) matrix[i+x] = [];
matrix[i+x][column] = "x";
}
}
});
});
// Calculate the next empty column in our array
// Note that our array will be sparse at times, and
// so we need to fill the first empty index or push to end
function next_column(ar) {
for(var next = 0; next < ar.length; next ++) {
if(!ar[next]) return next;
}
return next;
}
}
Then simply apply this on page load:
$(document).ready(function() {
get_cell_data();
});
(Note: whilst the code here is longer than my jQuery .offset() alternative, it's probably quicker to calculate. Please correct me if I'm wrong).
Working solution - http://codepen.io/jmarroyave/pen/eLkst
This is basically the same solution that i presented before, i just changed how to get the column index to remove the restriction of the jquery.position, and did some refactor to the code.
function layoutInitialize(tableId){
var layout = String();
var maxCols, maxRows, pos, i, rowspan, idx, xy;
maxCols = $(tableId + ' tr').first().children().length;
maxRows = $(tableId + ' tr').length;
// Initialize the layout matrix
for(i = 0; i < (maxCols * maxRows); i++){
layout += '?';
}
// Initialize cell data
$(tableId + ' td').each(function() {
$(this).addClass($(this).parent().attr('color_class'));
rowspan = 1;
if($(this).attr('rowspan')){
rowspan = $(this).attr("rowspan");
$(this).data("rowspan", rowspan);
}
// Look for the next position available
idx = layout.indexOf('?');
pos = {x:idx % maxCols, y:Math.floor(idx / maxCols)};
// store the column index in the cell for future reposition
$(this).data('column', pos.x);
for(i = 0; i < rowspan; i++){
// Mark this position as not available
xy = (maxCols * pos.y) + pos.x
layout = layout.substr(0, xy + (i * maxCols)) + 'X' + layout.substr(xy + (i * maxCols) + 1);
}
});
}
Solution: with jquery.position() - http://codepen.io/jmarroyave/pen/rftdy
This is an alternative solution, it assumes that the first row contains all the information about the number of the table columns and the position of each on.
This aproach has the restriction that the inizialitation code must be call when the table is visible, because it depends on the visible position of the columns.
If this is not an issue, hope it works for you
Initialization
// Initialize cell data
$('td').each(function() {
$(this).addClass($(this).parent().attr('color_class'));
$(this).data('posx', $(this).position().left);
if($(this).attr('rowspan')){
$(this).data("rowspan", $(this).attr("rowspan"));
}
});
UPDATE
According to this post ensuring the visibility of the table can be manage with
$('table').show();
// Initialize cell data
$('td').each(function() {
$(this).addClass($(this).parent().attr('color_class'));
$(this).data('posx', $(this).position().left);
if($(this).attr('rowspan')){
$(this).data("rowspan", $(this).attr("rowspan"));
}
});
$('table').hide();
As Ian said, the main issue to solve in this problem is to calculate the position of the cells when merging the hidden with the visible rows.
I tried to figure it out how the browser implements that funcionality and how to work with that. Then looking the DOM i searched for something like columnVisiblePosition and i found the position attributes and took that way
function getColumnVisiblePostion($firstRow, $cell){
var tdsFirstRow = $firstRow.children();
for(var i = 0; i < tdsFirstRow.length; i++){
if($(tdsFirstRow[i]).data('posx') == $cell.data('posx')){
return i;
}
}
}
The js code
$(document).ready(function () {
add_delete_buttons();
$(window).on("tr_gone", function (e, tr) {
add_come_back_button(tr);
});
// Initialize cell data
$('td').each(function() {
$(this).addClass($(this).parent().attr('color_class'));
$(this).data('posx', $(this).position().left);
if($(this).attr('rowspan')){
$(this).data("rowspan", $(this).attr("rowspan"));
}
});
});
function calculate_max_rowspans() {
// Remove all temporary cells
$(".tmp").remove();
// Get all rows
var trs = $('tr'), tds, tdsTarget,
$tr, $trTarget, $td, $trFirst,
cellPos, cellTargetPos, i;
// Get the first row, this is the layout reference
$trFirst = $('tr').first();
// Iterate through all rows
for(var rowIdx = 0; rowIdx < trs.length; rowIdx++){
$tr = $(trs[rowIdx]);
$trTarget = $(trs[rowIdx+1]);
tds = $tr.children();
// For each cell in row
for(cellIdx = 0; cellIdx < tds.length; cellIdx++){
$td = $(tds[cellIdx]);
// Find which one has a rowspan
if($td.data('rowspan')){
var rowspan = Number($td.data('rowspan'));
// Evaluate how the rowspan should be display in the current state
// verify if the cell with rowspan has some hidden rows
for(i = rowIdx; i < (rowIdx + Number($td.data('rowspan'))); i++){
if(!$(trs[i]).is(':visible')){
rowspan--;
}
}
$td.attr('rowspan', rowspan);
// if the cell doesn't have rows hidden within, evaluate the next cell
if(rowspan == $td.data('rowspan')) continue;
// If this row is hidden copy the values to the next row
if(!$tr.is(':visible') && rowspan > 0) {
$clone = $td.clone();
// right now, the script doesn't care about copying data,
// but here is the place to implement it
$clone.data('rowspan', $td.data('rowspan') - 1);
$clone.data('posx', $td.data('posx'));
$clone.attr('rowspan', rowspan);
$clone.addClass('tmp');
// Insert the temp node in the correct position
// Get the current cell position
cellPos = getColumnVisiblePostion($trFirst, $td);
// if is the last just append it
if(cellPos == $trFirst.children().length - 1){
$trTarget.append($clone);
}
// Otherwise, insert it before its closer sibling
else {
tdsTarget = $trTarget.children();
for(i = 0; i < tdsTarget.length; i++){
cellTargetPos = getColumnVisiblePostion($trFirst, $(tdsTarget[i]));
if(cellPos < cellTargetPos){
$(tdsTarget[i]).before($clone);
break;
}
}
}
}
}
}
// remove tmp nodes from the previous row
if(rowIdx > 0){
$tr = $(trs[rowIdx-1]);
if(!$tr.is(':visible')){
$tr.children(".tmp").remove();
}
}
}
}
// this function calculates the position of a column
// based on the visible position
function getColumnVisiblePostion($firstRow, $cell){
var tdsFirstRow = $firstRow.children();
for(var i = 0; i < tdsFirstRow.length; i++){
if($(tdsFirstRow[i]).data('posx') == $cell.data('posx')){
return i;
}
}
}
function add_delete_buttons() {
var $all_rows = $("tr");
$all_rows.each(function () {
// TR to remove
var $tr = $(this);
var delete_btn = $("<button>").text("x");
delete_btn.on("click", function () {
$tr.hide();
calculate_max_rowspans();
$(window).trigger("tr_gone", $tr);
});
var delete_cell = $("<td>");
delete_cell.append(delete_btn);
$(this).append(delete_cell);
});
}
function add_come_back_button(tr) {
var $tr = $(tr);
var come_back_btn = $("<button>").text("come back " + $tr.attr("color_class"));
come_back_btn.css({"background": $(tr).css("background")});
come_back_btn.on("click", function () {
$tr.show();
come_back_btn.remove();
calculate_max_rowspans();
});
$("table").before(come_back_btn);
}
if you have any questions or comments let me know.
I'm assuming you want the the rows to shift upward when you hide the row but you do not want the cells to shift left.
Here is what I got http://codepen.io/anon/pen/prDcK
I added two css rules:
#come_back_container{height: 30px;}
td[rowspan='0']{background-color: white;}
Here is the html I used:
<div id="come_back_container"></div>
<table id="dynamic_table" cellpadding=7></table>
<table id="dynamic_table2" cellpadding=7>
<tr style="background-color: red">
<td rowspan="5">a</td>
<td rowspan="1">b</td>
<td rowspan="5">c</td>
<td rowspan="1">d</td>
<td rowspan="2">e</td>
</tr>
<tr style="background-color: grey">
<td rowspan="0">f</td>
<td rowspan="1">g</td>
<td rowspan="0">h</td>
<td rowspan="1">i</td>
<td rowspan="0">j</td>
</tr>
<tr style="background-color: blue">
<td rowspan="0">k</td>
<td rowspan="1">l</td>
<td rowspan="0">m</td>
<td rowspan="1">n</td>
<td rowspan="1">o</td>
</tr>
<tr style="background-color: yellow">
<td rowspan="0">p</td>
<td rowspan="1">q</td>
<td rowspan="0">r</td>
<td rowspan="1">s</td>
<td rowspan="2">t</td>
</tr>
<tr style="background-color: green">
<td rowspan="0">u</td>
<td rowspan="1">v</td>
<td rowspan="0">w</td>
<td rowspan="1">x</td>
<td rowspan="0">y</td>
</tr>
</table>
The first rule is just to keep the top edge of the table in the same place. The second rule is to make the cells appear blank by blending in with the background, so change accordingly.
Finally here is the js:
$(function () {
//firstTable()
var myTb2 = new dynamicTable();
myTb2.createFromElement( $("#dynamic_table2") );
myTb2.drawTable()
$(window).on("tr_hide", function (e,data){
var tbl = data.ctx,
rowIndex = data.idx;
tbl.hideRow.call(tbl, rowIndex);
})
$(window).on("tr_show", function (e,data){
var tbl = data.ctx,
rowIndex = data.idx;
tbl.showRow.call(tbl, rowIndex);
})
})
function dynamicTableItem(){
this.height = null;
this.content = null;
}
function dynamicTableRow(){
this.color = null;
this.items = []
this.show = true
this.setNumColumns = function(numCols){
for(var i=0;i<numCols;i++){
var item = new dynamicTableItem();
item.height = 0;
this.items.push(item)
}
}
this.addItem = function(index, height, content){
var item = new dynamicTableItem();
item.height = height;
item.content = content;
if(index>=this.items.length){ console.error("index out of range",index); }
this.items[index] = item;
}
}
function dynamicTable(){
this.element = null;
this.numCols = null;
this.rows = []
this.addRow = function(color){
var row = new dynamicTableRow();
row.color = color;
row.setNumColumns(this.numCols)
var length = this.rows.push( row )
return this.rows[length-1]
}
this.drawTable = function(){
this.element.empty()
var cols = [],
rowElements = [];
for(var i=0;i<this.numCols;i++){
cols.push( [] )
}
for(var r=0; r<this.rows.length; r++){
var row = this.rows[r]
if(row.show){
var $tr = $("<tr>"),
delete_cell = $("<td>"),
delete_btn = $("<button>").text("x")
var data = {ctx: this, idx: r};
delete_btn.on("click", data, function(e){
$(window).trigger("tr_hide", e.data);
})
delete_cell.addClass("deleteCell");
$tr.css( {"background": row.color} );
delete_cell.append(delete_btn);
$tr.append(delete_cell);
this.element.append($tr);
rowElements.push( $tr );
for(var i=0; i<row.items.length; i++){
cols[i].push( row.items[i] );
}
}
}
for(var c=0; c<cols.length; c++){
var cellsFilled = 0;
for(var r=0; r<cols[c].length; r++){
var item = cols[c][r]
var size = item.height;
if(r>=cellsFilled){
cellsFilled += (size>0 ? size : 1);
var el = $("<td>").attr("rowspan",size);
el.append(item.content);
rowElements[r].children().last().before(el);
}
}
}
}
this.hideRow = function(rowIndex){
var row = this.rows[rowIndex]
row.show = false;
var come_back_btn = $("<button>").text("come back");
come_back_btn.css( {"background": row.color} );
var data = {ctx:this, idx:rowIndex};
come_back_btn.on("click", data, function(e){
$(window).trigger("tr_show", e.data);
$(this).remove();
});
$("#come_back_container").append(come_back_btn);
this.drawTable();
}
this.showRow = function(rowIndex){
this.rows[rowIndex].show = true;
this.drawTable();
}
this.createFromElement = function(tbl){
this.element = tbl;
var tblBody = tbl.children().filter("tbody")
var rows = tblBody.children().filter("tr")
this.numCols = rows.length
for(var r=0;r<rows.length;r++){
var row = this.addRow( $(rows[r]).css("background-color") );
var items = $(rows[r]).children().filter("td");
for(var i=0;i<items.length;i++){
var item = $(items[i]);
var height = parseInt(item.attr("rowspan"));
var contents = item.contents();
row.addItem(i,height,contents);
}
}
//console.log(this);
}
}
function firstTable(){
var myTable = new dynamicTable();
myTable.element = $("#dynamic_table");
myTable.numCols = 5
var red = myTable.addRow("red");
red.addItem(0,5);
red.addItem(1,1);
red.addItem(2,5);
red.addItem(3,1);
red.addItem(4,2);
var white = myTable.addRow("grey");
//white.addItem(0,0);
white.addItem(1,1);
//white.addItem(2,0);
white.addItem(3,1);
//white.addItem(4,0);
var blue = myTable.addRow("blue");
//blue.addItem(0,3); //try uncommenting this and removing red
blue.addItem(1,1);
//blue.addItem(2,0);
blue.addItem(3,1);
blue.addItem(4,1);
var yellow = myTable.addRow("yellow");
//yellow.addItem(0,0);
yellow.addItem(1,1);
//yellow.addItem(2,0);
yellow.addItem(3,1);
yellow.addItem(4,2);
var green = myTable.addRow("green");
//green.addItem(0,0);
green.addItem(1,1);
//green.addItem(2,0);
green.addItem(3,1);
//green.addItem(4,0);
myTable.drawTable();
}
I tried to use clear variable and method names but if you have any quests just ask.
PS- I know there is no easy way to add content to the cells right now but you only asked for disappearing rows.

Determining cells that reside in a table column underneath a cell

in the following table:
<table>
<thead>
<tr>
<th>Th1</th>
<th colspan='2'>Th23</th>
<th>Th4</th>
</tr>
</thead>
<tbody>
<tr>
<td>Td1</td>
<td>Td2</td>
<td>Td3</td>
<td>Td4</td>
</tr>
</tbody>
</table>
For the table cell containing text "Th23", I'd like to know which cells reside beneath it. In this case, the answer would be the cells containing text "Td2", and "Td3" respectively.
Are there any DOM properties or built-ins that help with this type of calculation?
#Matt McDonald has a more general solution.
This is what I ended up with:
// get tbody cell(s) under thead cell (first arg)
// if rowIndex===undefined, get from all rows; otherwise, only that row index
// NOTE: does NOT work if any cell.rowSpan != 1
var columnCells = function( th, rowIndex ) {
// get absolute column for th
for( var absCol=0, i=0; true; i++ ) {
if( th.parentNode.cells[i] == th ) break;
absCol += th.parentNode.cells[i].colSpan;
}
// look in tBody for cells; all rows or rowIndex
var tBody = th.parentNode.parentNode.nextSibling;
var cells = [];
for( var r=((rowIndex==undefined)?0:rowIndex); true; r++ ) {
if( rowIndex!==undefined && r>rowIndex ) break;
if( rowIndex==undefined && r>=tBody.rows.length ) break;
for( var c=0; true; c+=tBody.rows[r].cells[c].colSpan ) {
if( c < absCol ) continue;
if( c >= absCol+th.colSpan ) break;
cells.push(tBody.rows[r].cells[c]);
}
}
return cells;
}
Right off the bat, you need to do three things:
Give the table an id attribute for easy selection.
Give the target cell an id attribute for easy selection as well.
Select the cell's parentNode (row)
These three things will enable easier table-related calculations.
Next up is a function that grabs pseudo-properties of the specified cell. In this case, we're looking for its "start index" (in terms of columns), its "end index" (in terms of columns), and its "width" (end - start, in columns as well).
From there, you can traverse through the table's rows and check which cells fall between the start and the end indexes.
HTML:
<table id="foo">
<colgroup span="1">
<colgroup span="2">
<colgroup span="1">
<thead>
<tr>
<th>foo</th>
<th id="example" colspan="2">bar</th>
<th>baz</th>
</tr>
</thead>
<tbody>
<tr>
<td>bing</td>
<td>bang</td>
<td>boom</td>
<td>bong</td>
</tr>
</tbody>
</table>
JS (bear with me):
function getCellSpanProps(table, row, cell)
{
var isRow = (function()
{
var i = 0, currentRow;
for(i;i<table.rows.length;i++)
{
currentRow = table.rows[i];
if(currentRow === row)
{
return true;
}
currentRow = null;
}
return false;
}()),
cellHasCorrectParent, i = 0,
currentCell, colspanCount = 0,
props;
if(isRow)
{
cellHasCorrectParent = (function()
{
return cell.parentNode === row;
}());
if(cellHasCorrectParent)
{
for(i;i<row.cells.length;i++)
{
currentCell = row.cells[i];
if(currentCell === cell)
{
props = {"start": colspanCount,
"end": colspanCount + cell.colSpan,
"width": (colspanCount + cell.colSpan) - colspanCount};
break;
}
colspanCount += currentCell.colSpan;
currentCell = null;
}
row = null;
}
return props;
}
}
function findCellsUnderColumn(table, props)
{
var i = 0, j = 0, row, cell,
colspanCount = 0, matches = [],
blacklist = {"": true, "NaN": true, "null": true, "undefined": true,
"false": true};
if(blacklist[props.start] || blacklist[props.end] || blacklist[props.width])
{
return false;
}
for(i;i<table.rows.length;i++)
{
row = table.rows[i];
colspanCount = 0;
for(j=0;j<row.cells.length;j++)
{
cell = row.cells[j];
if(colspanCount >= props.start && colspanCount < props.end)
{
matches.push(cell);
}
colspanCount += cell.colSpan;
cell = null;
}
row = null;
}
return matches;
}
var table = document.getElementById("foo"),
example = document.getElementById("example"),
targetRow = example.parentNode,
props = getCellSpanProps(table, targetRow, example),
matches = findCellsUnderColumn(table, props);
console.log(matches);
Demo: http://jsbin.com/ohohew/edit#javascript,html
This will determine which cells reside inside the particular column you're looking for (including the example). You can customize the function to fit your needs if that's not exactly what you're looking for.
You need to know the column index of your cell. I'll name it ci. Then read its colspan (if empty, set it to 1). Then find the cells on the next line that have a column index >= ci and < ci + colspan. For such a complex need, using a JS framework is very useful. I'll suppose you can use JQuery, since it's the most frequently used.
Computing the colum index has several solutions on SO.
Reading the colspan attribute is just cell.attr('colspan') with jQuery.
Finding the next row is cell.closest('tr').next('tr').
The last step is to iterate over every element of the line and compute their column index. You could use the same function as above, but if it's not efficient enough, it should be easy to adapt its code so that it does not return an integer, but add elements to an array.

How can I get the col's id of a td (not column number of a td)?

In this example:
<table border="1">
<col id="col0" style="background-color: #FFFF00"/>
<col id="col1" style="background-color: #FF0000"/>
<tr><td rowspan="2">1</td><td>2</td><td>3</td></tr>
<tr><td>4</td><td>5</td><td>6</td></tr>
<tr><td>7</td><td>8</td><td>9</td></tr>
</table>
How can I get the col’s id of td 4?
If I get it's column number with this jquery command:
var cn = $(this).parent().children().index($(this));
cn will be 0, but it’s style shows that it belongs to col1
and I need a commend like td.col.id
when I set rowspan="2" at the td above a td (eg. td 4) this td's column number will be different from it's order of col(or colgroup) and I set background color to show it.
Edit:
I believe there is a way to solve this problem, because when td knows about it's col(colgroup) there must be a way to ask it from td at dom tree. (Td4 you show style of a specific col, who is that col?)
<td>4</td> is the first child of the second tablerow, so you should indeed get column 0.
instead of elaborating a complex function that detects rowspans etc, it might be advisable to just assign ids to each table cell, or create another custom solution for your table.
e.g. you know in advance how many columns each specific row has? Or you use the actual background color or a 'secret' css attribute as identification.
ps. my useless fiddle until I understood the actual problem.
edit (read discussion below):
as described here, you are not supposed to create custom css attributes; these are often ignored by the browser (and not available via .attr()).
Sahar's solution was to mark each element affected by a merging of rows to remember for how many columns the element should count.
You first have to calculate the column number of the td itself.
This is done by counting the number of tds before our td; taking colspan attributes into account:
function getElementColumn(td)
{
var tr = td.parentNode;
var col = 0;
for (var i = 0, l = tr.childNodes.length; i < l; ++i) {
var td2 = tr.childNodes[i];
if (td2.nodeType != 1) continue;
if (td2.nodeName.toLowerCase() != 'td' && td2.nodeName.toLowerCase() != 'th') continue;
if (td2 === td) {
return col;
}
var colspan = +td2.getAttribute('colspan') || 1;
col += colspan;
}
}
Then you can iterate the col elements and return the one matching the column number.
We first have to find the colgroup element. Then it's similar to computing the column number of the td:
function getCol(table, colNumber)
{
var col = 0;
var cg;
for (var i = 0, l = table.childNodes.length; i < l; ++i) {
var elem = table.childNodes[i];
if (elem.nodeType != 1) continue;
if (elem.nodeName.toLowerCase() != 'colgroup') continue;
cg = elem;
break;
}
if (!cg) return;
for (var i = 0, l = cg.childNodes.length; i < l; ++i) {
var elem = cg.childNodes[i];
console.log(elem);
if (elem.nodeType != 1) continue;
if (elem.nodeName.toLowerCase() != 'col') continue;
if (col == colNumber) return elem;
var colspan = +elem.getAttribute('span') || 1;
col += colspan;
}
}
With these two function you should be able to do this:
var id = getCol(table, getElementColumn(td)).id;
http://jsfiddle.net/wHyUQ/1/
jQuery version
function getElementColumn(td)
{
var col = 0;
$(td).prevAll('td, th').each(function() {
col += +$(this).attr('colspan') || 1;
});
return col;
}
function getCol(table, colNumber)
{
var col = 0, elem;
$(table).find('> colgroup > col').each(function() {
if (colNumber == col) {
elem = this;
return false;
}
col += +$(this).attr('span') || 1;
});
return elem;
}
http://jsfiddle.net/wHyUQ/2/
Resolving rowspans or colspans would be incredibly complex. I suggest you to iterate over all col-elements, set a width of 0px to them and check if this affected the width of your td or th element. If so, this is the related column.
Example:
// Your table elements
$table = $('yourTableSelector');
$cell = $('td or th');
$cols = $table.find('colgroup > col');
// determine the related col
// by setting a width of 0px. the
// resulting width on the element should be negative or zero.
// this is hacky, but the other way would
// be to resolve rowspans and colspans, which
// would be incredibly complex.
var $relatedColumn = $();
$cols.each(function(){
var $col = $(this);
var prevStyle = $col.attr('style') === 'string' ? $col.attr('style'): '';
$col.css('width', '0px');
if($cell.width() <= 0){
$relatedColumn = $col;
$col.attr('style', prevStyle); // reset
return false;
} else {
$col.attr('style', prevStyle); // reset
}
});

Categories