I have a string that I am splitting using string.split(' '); in order to turn the string into an array.
suppose I have these two tables, table1 and table2.
<table border="1" id="table1">
<tr>
<th colspan="2">Image One</th>
</tr>
<tr>
<td style="width:40%;"><img src="airplane.jpg" alt="Image 1"></td>
<td>
<dl>
<dt>airplane</dt>
<dt>flight</dt>
<dt>travel</dt>
<dt>military</dt>
<dt>word war</dt>
<dt>GI</dt>
</dl>
</td>
</tr>
</table>
<table border="1" id="table2">
<tr>
<th colspan="2">Image Two</th>
</tr>
<tr>
<td style="width:40%;"><img src="apple.jpg" alt="Image 1"></td>
<td>
<dl id="tags">
<dt>red</dt>
<dt>apple</dt>
<dt>round</dt>
<dt>fruit</dt>
<dt>healthy</dt>
<dt>doctor</dt>
</dl>
</td>
</tr>
</table>
right now for testing purposes I have an id of tags on table2's dl.
I am using a function to turn that DL (#tags) into an array
function getArray(id) {
var node, list, arrValue;
array = [];
for (node = document.getElementById(id).firstChild;
node;
node = node.nextSibling) {
if (node.nodeType == 1 && node.tagName == 'DT') {
array.push(node.innerHTML);
}
}
console.log(array)
}
in order to check it against my original string to see if any of the values match.
However, I am going to have multiple DT's that the string is going to be check against. Would it be correct to add all the tables into a 3d array and then check the values in the string against the 3d array? or is there a better approach?
UPDATE
The problem is:
I am eventually going to have tables filled with an image and tags. Essentially I want to be able to search those tags against my string (which will be separated into an array) then return the image with the most tags in the string. I am trying to figure out the best way to do that.
Thank you
Rather than an array I would use an Object to store the list of tags, where the keys are the tags and the values are irrelevant.
This would give you O(1) lookup to check whether some other string exists in that list, as opposed to an O(n) lookup if you were using array.indexOf().
The function below will find every DT on the page and then return an object containing a map from each DT's text to the ID of its parent DL.
function makeMap() {
var map = {};
var dls = document.getElementsByTagName('DL');
for (var i = 0, n = dls.length; i < n; ++i) {
var dl = dls[i];
var id = dl.id;
var node = dl.firstChild;
while (node) {
if (node.nodeType == 1 && node.tagName == 'DT') {
var tag = node.textContent || node.innerText; // latter for MSIE
map[tag] = id;
}
node = node.nextSibling;
}
}
return map;
}
Alternatively, in jQuery (with some pure JS mixed in for efficiency):
function makeMap2() {
var map = {};
var $dt = $('dl > dt');
$dt.each(function() {
var tag = this.textContent || this.innerText;
map[tag] = this.parentNode.id;
});
return map;
}
You wouldn't use a three-dimensional array, but only a two-dimensional one with tables and their tags. Or, as Alnitak already mentioned, even better a lookup object:
var map = {};
var dls = document.getElementsByTagName('dl');
for (var i = 0, i < dls.length; i++) {
var tableid = dls[i].id; // identifier?
var dts = dls[i].getElementsByTagName('dt'); // assuming you don't nest them
for (var j = 0; j < dts.length; j++) {
var text = dts[j].textContent || dts[i].innerText;
var tags = text.split(/\s+/);
for (var k=0; k<tags.length; k++)
if (tags[k] in map)
map[tags[k]].push(tableid);
else
map[tags[k]] = [tableid]; // an array
}
}
/* now, map could look like this:
{
word: ["table1"],
war: ["table1"],
red: ["table2"],
double: ["table1", "table2"], // tags in more than one table
…
}
*/
To get the table with the most tags in the string you now can use a function like this, which returns the respective tableids sorted by tag occurence:
function getHighestTables(string) {
var tags = string.split(/\s+/);
var tablecounts = {};
for (var i=0; i<tags.length; i++) {
var tables = map[tags[i]] || [];
for (var j=0; j<tables.length; j++) {
var tableid = tables[j];
if (tableid in tablecounts)
tablecounts[tableid]++;
else
tablecounts[tableid] = 1;
}
}
/* tablecounts might now look like this:
{
table1: 2
table2: 5
}
*/
return Object.keys(tablecounts).sort(function (a, b) {
return tablecounts[b] - tablecounts[a];
});
}
Related
I have a page containing multiple tabs per region.
Each row in the table has a class with each region that its impacted by.
<tr class="apac emea americas">...</tr>
<tr class="apac emea">...</tr>
When a tab is clicked, it filters out the table and removes anything where the condition is not met.
$('#' + tab).find("#trainingEvents .results tr:not(.Americas.EMEA.APAC)").remove(); <- This is the ALL tab
Each of the tabs is pretty easy to understand except for "Multiple" which is what my question relates to.
The condition needs to be, remove rows that do not contain 2 of the 3 possible regions.
For example:
<tr class="amea apac"></tr> = True
<tr class="apac">...</tr> = False, Remove it
How can I accomplish this filter? Just needs to meet any 2 combinations of the 3 possible options
I'd suggest the following:
// collating the 'regions':
var regions = ['americas', 'emea', 'apac'],
// initialising an array to use, later:
foundClasses = [];
// iterating over the 'tr' elements, filtering them:
$('tr').filter(function () {
// using Array.prototype.forEach to filter the classList of the element:
foundClasses = Array.prototype.filter.call(this.classList, function (c) {
// 'c' is the current class in the classList we're iterating over,
// if it's in the array we return that array to the 'foundClasses':
if (regions.indexOf(c) > -1) {
return c;
}
});
// we keep the the element in the jQuery collection (of 'tr' elements),
// if we have only 1 (or less...) classes found:
return foundClasses.length < 2;
// removing those 'tr' elements:
}).remove();
var regions = ['americas', 'emea', 'apac'],
foundClasses = [];
$('tr').filter(function () {
foundClasses = Array.prototype.filter.call(this.classList, function (c) {
if (regions.indexOf(c) > -1) {
return c;
}
});
return foundClasses.length < 2;
}).remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr class="americas emea">
<td>americas emea</td>
</tr>
<tr class="apac">
<td>apac</td>
</tr>
<tr class="emea">
<td>emea</td>
</tr>
<tr class="americas">
<td>americas</td>
</tr>
<tr class="apac emea">
<td>apac emea</td>
</tr>
</tbody>
</table>
To account for those browsers without access to Array.prototype.filter(), and possibly element.classList:
var regions = ['americas', 'emea', 'apac'],
classes,
foundClasses = [];
$('tr').filter(function() {
// creating an array by splitting the className property by white-space:
classes = this.className.split(/\s+/);
// crudely emptying the initialised array:
foundClasses = [];
// iterating over the array of classes using a for-loop:
for (var i = 0, len = classes.length; i < len; i++) {
// if the current element in the classes array is in the
// foundClasses array:
if (regions.indexOf(classes[i]) > -1) {
// we push the current class into the foundClasses array:
foundClasses.push(classes[i]);
}
}
// as above:
return foundClasses.length < 2;
}).remove();
var regions = ['americas', 'emea', 'apac'],
classes,
foundClasses = [];
$('tr').filter(function() {
classes = this.className.split(/\s+/);
foundClasses = []; // crudely emptying the array
for (var i = 0, len = classes.length; i < len; i++) {
if (regions.indexOf(classes[i]) > -1) {
foundClasses.push(classes[i]);
}
}
return foundClasses.length < 2;
}).remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr class="americas emea">
<td>americas emea</td>
</tr>
<tr class="apac">
<td>apac</td>
</tr>
<tr class="emea">
<td>emea</td>
</tr>
<tr class="americas">
<td>americas</td>
</tr>
<tr class="apac emea">
<td>apac emea</td>
</tr>
</tbody>
</table>
References:
JavaScript:
Array.prototype.indexOf().
Array.prototype.filter().
Array.prototype.push().
element.classList.
jQuery:
filter().
remove().
You use for that the function "filter": (UPDATE after another requirement to filter it)
$("tr").
filter(function(index){
var classes = $(this).attr("class").split(" ");
var regions = "americas,emea,apac,";
var counter = 0;
for(var i = 0, j = classes.length;i < j;i++){
if(regions.indexOf(classes[i] + ",") >= 0){counter++;}
}
return counter != 2;
})
.remove();
That code will remove all the rows with less or more than 2 region classes.
FIDDLE HAS BEEN UPDATED TOO: http://jsfiddle.net/fac5tapz/8/
If you were using a custom attribute, it would be possible to spare some code:
<table border="1">
<tr regions="apac emea"><td>first row do not remove</td></tr>
<tr regions="apac emea"><td>second row do not remove</td></tr>
<tr regions="apac"><td>third will be removed</td></tr>
<tr regions="apac emea americas"><td>fourth will be remove</td></tr>
<tr regions="apac emea"><td>fifth row do not remove</td></tr>
<tr regions="apac emea americas"><td>sixth will be removed</td></tr>
<tr regions="apac"><td>seventh will be removed</td></tr>
<tr regions="americas emea"><td>eighth row do not remove</td></tr>
</table>
$("tr").
filter(function(index){
var regions = $(this).attr("regions").split(" ");
return regions.length != 2;
})
.remove();
Another fiddle for this version: http://jsfiddle.net/dkseknyw/2/
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.
I've a GridView with three rows like this
<tr>
<th>SlNo</th>
</tr>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
I've the following code to traverse through the rows
var GridViewRow=GridView.getElementsByTagName('tr')
Here the row length is 3.
I travese through the GridViewRow using for loop .Here how will i get the tag name of the current element ie (th or td).
If the tagname is "TH" it should return and if it is "TD" it should take the value of TD.
How about this
var table = document.getElementById("mytab1");
for (var i = 0, cell; cell = table.cells[i]; i++) {
//iterate through cells
//cells would be accessed using the "cell" variable assigned in the for loop
}
you can also try out
var tbl = document.getElementById('yourTableId');
var rows = tbl.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++)
{
if(rows[i].getElementsByTagName('td').length > 0)
{
//code to execute
}
else
{
continue;
}
}
var GridViewRow = GridView.getElementsByTagName('tr');
$(GridViewRow).each(function() {
var $this = $(this), td = $this.find('td');
if (td.length === 1) {
console.log(td.text());
}
});
this works for <tr> in which you have exactly one <td> if you use jquery, otherwise in plain javascript try this:
var GridViewRow = GridView.getElementsByTagName('tr'),
len = GridViewRow.length,
td;
while (--len) {
td = GridViewRow[len].getElementsByTagName('td');
if (td.length === 1) {
console.log(td[0].innerHTML);
}
}
});
You can check the tag name with jQuery :
$(this).attr("tag");
Later edit:
For raw javascript, use tagName:
element.tagName
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.
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
}
});