Hello all,
I have a very simple html table with collapsible rows and
sorting
capabilities. The collapsible row is hidden with css rule
(display:none). When one clicks in the left of the
expandable row,
the hidden row is made visible with css. The problem is when
i sort
the rows, the hidden rows get sorted as well which i don't
want and
want to be moved (while sorting) relative to their parent
rows. The
following is my complete html code with javascript. Just
copy the
entire code to new html file and see the problem yourselves.
Any
suggestions/help would be greatly appreciated. PS: The
javascript
code is not written by me, i just plugged them to here.
[code]
<html>
<head>
<title>Expandible row test in
firefox</title>
<STYLE type="text/css">
.collapsed
{
DISPLAY: none;
}
</STYLE>
<script language="javascript"
type="text/javascript">
//***collapsible rows
function outliner(evt) {
evt = (evt) ? evt : (window.event) ? window.event :
"";
var oMe;
if (evt.srcElement) {
oMe = evt.srcElement;
} else if (evt.target) {
oMe = evt.target;
}
if (evt.srcElement) {
//for IE
var child = document.all[oMe.getAttribute
("child",false)];
}
else {
//for Firefox
var child = document.all[oMe.getAttribute
("child",false)];
}
//get child element
//if child element exists, expand or collapse it.
if (null != child)
child.className = child.className
== "collapsed" ? "expanded" :
"collapsed";
}
function changepic(evt) {
evt = (evt) ? evt : (window.event) ? window.event :
"";
var uMe;
if (evt.srcElement) {
uMe = evt.srcElement;
} else if (evt.target) {
uMe = evt.target;
}
var check = uMe.src.toLowerCase();
if (check.lastIndexOf("expand.gif") != -1)
{
uMe.src = "collapse.gif";
}
else
{
uMe.src = "expand.gif";
}
}
//*** SORTABLE ROWS
var ts_version = "1.26";
var ts_browser_agt = navigator.userAgent.toLowerCase();
var ts_browser_is_ie =
((ts_browser_agt.indexOf("msie") != -1) &&
(ts_browser_agt.indexOf("opera") == -1));
var ml_tsort = {
// configurable constants, modify as needed!
sort_col_title : "Click to Sort!", // the popup
text for the
sorting link in the header columns
sort_col_asc_title : "Sorted ascending", // the
popup text for the
sorting link in the header column after the column's sorted
in
ascending order
sort_col_desc_title : "Sorted Descending ", //
the popup text for
the sorting link in the header column after the column's
sorted in
ascending order
sort_col_class : "abc", // whichever class you
want the heading to
be
sort_col_style : "text-decoration:none;
font-weight:bold;
color:black", // whichever style you want the link to
look like
sort_col_class_post_sort : "def", // whichever
class you want the
heading for the column that's just sorted
sort_col_style_post_sort : "text-decoration:none;
font-weight:bold;
color:black", // whichever style you want the link to
look like when
the column for the link was sorted
sort_col_mouseover : "this.style.color='blue'",
// what style the
link should use onmouseover?
use_ctrl_alt_click : true, // allow ctrl-alt-click
anywhere in
table to activate sorting?
sort_only_sortable : true, // make all tables sortable by
default
or just make the tables with "sortable" class
sortable?
// speed related constants, modify as needed!
table_content_might_change : false, // table content could
be
changed by other JS on-the-fly? if so, some speed
improvements cannot
be used.
preserve_style : " ", // (row, cell) preserves
style for row or
cell e.g., row is useful when the table highlights rows
alternatively. cell is much slower while no preservation's
the
fastest by far!
tell_me_time_consumption : false, // give stats about time
consumed
during sorting and table update/redrawing.
// anything below this line, modify at your own risk! ;)
smallest_int : -2147483648000, // date parse is in
milliseconds,
hence the 000
set_vars : function(event)
{
var e = (event)? event : window.event;
var element = (event)? ((event.target)? event.target :
event.srcElement) : window.event.srcElement;
var clicked_td = ml_tsort.getParent(element,'TD') ||
ml_tsort.getParent(element,'TH');
var table = ml_tsort.getParent(element,'TABLE');
if(!table || table.rows.length < 1 || !clicked_td)
return;
var column = clicked_td.cellIndex;
if (e.altKey && e.ctrlKey &&
ml_tsort.use_ctrl_alt_click)
ml_tsort.resortTable(table.rows[0].cells[column]);
},
makeSortable: function(table)
{
if (table.rows && table.rows.length > 0) {
var rowidx =
table.getAttribute("ts_linkrow") || 0;
var firstRow = table.rows[rowidx];
}
if (!firstRow) return;
var sortCell;
// We have a first row: assume it's the header (it
works for
<thead> too),
// and make its contents clickable links
for (var i=0;i<firstRow.cells.length;i++) {
var cell = firstRow.cells[i];
if(cell.getAttribute("ts_nosort"))
continue;
var txt = cell.innerHTML;
if(cell.getAttribute("sortdir"))
sortCell = cell;
cell.innerHTML = '<a
style="'+this.sort_col_style+'"
onMouseOver="this.oldstyle=this.style.cssText;'+this.so
rt_col_mouseove
r+'"
onMouseOut="this.style.cssText=this.oldstyle;"
class="'+this.sort_col_class+'" href="#"
title="'+this.sort_col_title+'"
onclick="javascript:ml_tsort.resortTable(this.parentNod
e);return
false">'+txt+'</a>';
}
if(sortCell) this.resortTable(sortCell);
},
sortables_init : function()
{
// Find all tables with class sortable and make them
sortable
if (!document.getElementsByTagName) return;
var tbls =
document.getElementsByTagName("table");
for (var ti=0;ti<tbls.length;ti++) {
thisTbl = tbls[ti];
if(!ml_tsort.sort_only_sortable ||
thisTbl.className.match
(/sortable/i))
ml_tsort.makeSortable(thisTbl);
}
},
getParent : function(el, pTagName)
{
if (el == null) return null;
else if (el.nodeType == 1 &&
el.tagName.toLowerCase() ==
pTagName.toLowerCase()) // Gecko bug, supposed to be
uppercase
return el;
else
return this.getParent(el.parentNode, pTagName);
},
getInnerText : function(el)
{
if (typeof el == "string") return el;
if (typeof el == "undefined") { return el };
if (el.innerText) return el.innerText; //Not needed but
it
is faster
var str = "";
var cs = el.childNodes;
var l = cs.length;
for (var i = 0; i < l; i=i++) {
switch (cs[i].nodeType) {
case 1: //ELEMENT_NODE
str += this.getInnerText(cs[i]);
break;
case 3: //TEXT_NODE
str += cs[i].nodeValue;
break;
}
}
return str;
},
match_date_format : function(value, format)
{
var v =
this.getInnerText(value.cells[this.sort_column_index]);
this.set_date_array(format);
if(format == 'M/D/Y' && !isNaN(Date.parse(v)))
return true;
else if(!isNaN(this.convert_date(v))) return true;
this.set_date_array(format.replace(///g, '-'));
if(!isNaN(this.convert_date(v))) return true;
this.set_date_array(format.replace(///g, '.'));
if(!isNaN(this.convert_date(v))) return true;
this.set_date_array(format.replace(///g, ' '));
if(!isNaN(this.convert_date(v))) return true;
return false;
},
resortTable : function(td)
{
if(td == null) return;
var column = td.cellIndex;
var table = this.getParent(td,'TABLE');
this.sort_column_index = column;
if(table == null || table.rows.length <= 2) return;
var lastSortCell =
table.getAttribute("ts_sortcell") || 0;
lastSortCell--; // the processing is used for IE, which
treats no
attribute as 0, while FF treats 0 as still true.
var lastSortDir;
if(td.getAttribute("ts_forcesort"))
lastSortDir =
(td.getAttribute("ts_forcesort")
== 'desc')? 'asc' : 'desc';
else
lastSortDir = (table == this.last_sorted_table
&& column ==
lastSortCell)? table.getAttribute("ts_sortdir") :
((td.getAttribute
("sortdir") == 'desc')? 'asc' : 'desc');
var newRows = new Array();
var headcount = 1;
for (var i=0,j=1;j<table.rows.length;j++)
{
var t =
table.rows[j].parentNode.tagName.toLowerCase();
if(t == 'tbody' && table.rows[j].cells.length
>= column + 1)
newRows[i++] = table.rows[j];
else if(t == 'thead') headcount++;
}
if(newRows.length == 0) return;
var time2 = new Date();
// check if we really need to sort
if(!td.getAttribute("ts_forcesort") &&
!
this.table_content_might_change && table ==
this.last_sorted_table &&
column == lastSortCell)
newRows.reverse();
else
{
// Work out a type for the column
var sortfn, type =
td.getAttribute("ts_type");
this.replace_pattern = '';
var itm, i;
for(i = 0; i < newRows.length; i++)
{
itm = this.getInnerText(newRows[i].cells[column]);
if(itm.match(/S/)) break;
}
if(i == newRows.length) return;
itm = ml_trim(itm);
if(!type)
{
sortfn = this.sort_caseinsensitive;
if (this.match_date_format(newRows[i], 'M/D/Y'))
sortfn =
this.sort_date;
else if (itm.match(/^[¥£€$]/)) sortfn =
this.sort_currency;
else if (itm.match(/^d{1,3}(.d{1,3})$/))
sortfn =
this.sort_ip;
else if
(itm.match(/^[+-]?s*[0-9]+(?:.[0-9]+)?(?:s*[eE]s*
[+-]?s*d+)?$/))
sortfn = this.sort_numeric;
}
else if(type == 'date' &&
this.match_date_format(newRows
[i], 'M/D/Y')) sortfn = this.sort_date;
else if(type == 'euro_date' &&
this.match_date_format(newRows
[i], 'D/M/Y')) sortfn = this.sort_date;
else if(type == 'other_date' &&
this.match_date_format(newRows
[i], td.getAttribute("ts_date_format"))) sortfn =
this.sort_date;
else if(type == 'number') sortfn = this.sort_numeric;
else if(type == 'ip') sortfn = this.sort_ip;
else if(type == 'money') sortfn = this.sort_currency;
// else if(type == 'custom') sortfn =
function(aa,bb) { a =
this.getInnerText(aa.cells[this.sort_column_index]); b =
this.getInnerText(bb.cells[this.sort_column_index]); eval
(td.getAttribute("ts_sortfn")) }; // the coding
here is shorter but
interestingly it's also slower
else if(type == 'custom') { this.custom_code =
td.getAttribute
("ts_sortfn"); sortfn = this.custom_sortfn }
else { alert("unsupported sorting type or data
not matching
indicated type!"); return; }
table.setAttribute("ts_sortcell",
column+1);
newRows.sort(sortfn);
if (lastSortDir == 'asc') newRows.reverse();
}
// set style of heading
var rowidx = table.getAttribute("ts_linkrow")
|| 0;
if(lastSortCell > -1 &&
table.rows[rowidx].cells
[lastSortCell].firstChild.style)
{
table.rows[rowidx].cells[lastSortCell].firstChild.oldstyle =
this.sort_col_style;
table.rows[rowidx].cells[lastSortCell].firstChild.style.cssT
ext
= this.sort_col_style;
table.rows[rowidx].cells[lastSortCell].firstChild.className
=
this.sort_col_class;
}
if(table.rows[rowidx].cells[column].firstChild.style)
{
table.rows[rowidx].cells[column].firstChild.oldstyle =
this.sort_col_style_post_sort;
table.rows[rowidx].cells[column].firstChild.style.cssText =
this.sort_col_style_post_sort;
table.rows[rowidx].cells[column].firstChild.className
=
this.sort_col_class_post_sort;
}
if (lastSortDir == 'desc')
table.setAttribute('ts_sortdir','asc');
else table.setAttribute('ts_sortdir','desc');
// has to use tagName otherwise IE complains
if(lastSortCell > -1 &&
table.rows[rowidx].cells
[lastSortCell].firstChild.tagName) table.rows[rowidx].cells
[lastSortCell].firstChild.title = this.sort_col_title;
if(table.rows[rowidx].cells[column].firstChild.tagName)
table.rows
[rowidx].cells[column].firstChild.title = ((lastSortDir ==
'desc')?
this.sort_col_asc_title : this.sort_col_desc_title);
this.last_sorted_table = table;
var time3 = new Date();
var ps = table.getAttribute("preserve_style")
||
this.preserve_style;
if(ps == 'row' && !ts_browser_is_ie)
{
var tmp = new Array(newRows.length);
for (var i = 0; i < newRows.length; i++) tmp[i] =
newRows
[i].innerHTML;
for (var i = 0; i < newRows.length; i++)
table.rows
[i+headcount].innerHTML = tmp[i];
}
else if(ps == 'cell' || (ps == 'row' &&
ts_browser_is_ie))
{
var tmp = new Array(newRows.length);
for (var i = 0; i < newRows.length; i++)
for (var j = 0; j < newRows[i].cells.length;
j++)
{
if(!tmp[i]) tmp[i] = new
Array(newRows[i].cells.length);
tmp[i][j] = newRows[i].cells[j].innerHTML;
}
for (var i = 0; i < newRows.length; i++)
for (var j = 0; j < newRows[i].cells.length;
j++)
table.rows[i+headcount].cells[j].innerHTML =
tmp[i][j];
}
else
{
for (var i=0;i<newRows.length;i++) // We
appendChild rows that
already exist to the tbody, so it moves them rather than
creating new
ones
table.tBodies[0].appendChild(newRows[i]);
}
var time4 = new Date();
if(this.tell_me_time_consumption)
{
alert('it took ' + this.diff_time(time3, time2) + '
seconds to
do sorting!');
alert('it took ' + this.diff_time(time4, time3) + '
seconds to
do redrawing!');
}
return false;
},
diff_time : function(time2, time1)
{
return (time2.getTime() - time1.getTime())/1000;
},
set_date_array : function(f)
{
var tmp = [['D', f.indexOf('D')], ['M', f.indexOf('M')],
['Y',
f.indexOf('Y')]];
tmp.sort(function(a,b){ return a[1] - b[1]});
this.date_order_array = new Array(3);
for(var i = 0; i < 3; i++)
this.date_order_array[tmp[i][0]] = '$'
+ (i + 2);
this.replace_pattern =
f.replace(/[DMY]([^DMY]+)[DMY]([^DMY]+)
[DMY]/, '^(.*?)(\d+)\$1(\d+)\$2(\d+)(.*)$');
},
process_year : function(y)
{
var tmp = parseInt(y);
if(tmp < 32) return '20' + y;
else if(tmp < 100) return '19' + y;
else return y;
},
// convert to MM/DD/YYYY (or M/D/YYYY) format
convert_date : function(a)
{
var re =
'RegExp.$1+RegExp.'+this.date_order_array['M']+'+'/
'+RegExp.'+this.date_order_array['D']+'+'/'+this.process_
year
(RegExp.'+this.date_order_array['Y']+')+RegExp.$5';
var code = 'if(a.match(/'+this.replace_pattern+'/)) (' +
re + ')';
return Date.parse(eval(code));
},
sort_date : function(a,b)
{
var atext = ml_tsort.getInnerText(a.cells
[ml_tsort.sort_column_index]);
var btext = ml_tsort.getInnerText(b.cells
[ml_tsort.sort_column_index]);
var aa, bb;
if(atext && atext.match(/S/))
{
aa = ml_tsort.convert_date(atext);
if(isNaN(aa)) aa = Date.parse(atext);
if(isNaN(aa)) aa = 0;
}
else aa = ml_tsort.smallest_int;
if(btext && btext.match(/S/))
{
bb = ml_tsort.convert_date(btext);
if(isNaN(bb)) bb = Date.parse(btext);
if(isNaN(bb)) bb = 0;
}
else bb = ml_tsort.smallest_int;
return aa - bb;
},
sort_currency : function(a,b)
{
return
ml_tsort.sort_num(ml_tsort.getInnerText(a.cells
[ml_tsort.sort_column_index]).replace(/[^-0-9.+]/g,''),
ml_tsort.getInnerText(b.cells
[ml_tsort.sort_column_index]).replace(/[^-0-9.+]/g,''));
},
sort_num : function(a, b)
{
var aa, bb;
if(a && a.match(/S/))
{
if(!isNaN(a)) aa = a;
else if(a &&
a.match(/^[^0-9.+-]*([+-]?s*[0-9]+(?:.[0-9]+)?
(?:s*[eE]s*[+-]?s*d+)?)/))
aa = parseFloat(RegExp.$1.replace(/s+/g, ''));
else aa = 0;
}
else aa = ml_tsort.smallest_int;
if(b && b.match(/S/))
{
if(!isNaN(b)) bb = b;
else if(b &&
b.match(/^[^0-9.+-]*([+-]?s*[0-9]+(?:.[0-9]+)?
(?:s*[eE]s*[+-]?s*d+)?)/))
bb = parseFloat(RegExp.$1.replace(/s+/g, ''));
else bb = 0;
}
else bb = ml_tsort.smallest_int;
return aa - bb;
},
sort_numeric : function(a,b)
{
return
ml_tsort.sort_num(ml_tsort.getInnerText(a.cells
[ml_tsort.sort_column_index]),
ml_tsort.getInnerText(b.cells
[ml_tsort.sort_column_index]));
},
sort_ip : function(a,b)
{
var aa = ml_tsort.getInnerText(a.cells
[ml_tsort.sort_column_index]).split('.');
var bb = ml_tsort.getInnerText(b.cells
[ml_tsort.sort_column_index]).split('.');
return ml_tsort.sort_num(aa[0], bb[0]) ||
ml_tsort.sort_num(aa
[1], bb[1]) ||
ml_tsort.sort_num(aa[2], bb[2]) ||
ml_tsort.sort_num(aa
[3], bb[3]);
},
sort_caseinsensitive : function(a,b)
{
var aa = ml_tsort.getInnerText(a.cells
[ml_tsort.sort_column_index]).toLowerCase();
var bb = ml_tsort.getInnerText(b.cells
[ml_tsort.sort_column_index]).toLowerCase();
if (aa==bb) return 0;
if (aa<bb) return -1;
return 1;
},
custom_sortfn : function(aa,bb)
{
var a = ml_tsort.getInnerText(aa.cells
[ml_tsort.sort_column_index]);
var b = ml_tsort.getInnerText(bb.cells
[ml_tsort.sort_column_index]);
return eval(ml_tsort.custom_code);
}
};
function ml_trim(text)
{
if(!text) return text;
var tmp = text.replace(/^s+/, '');
return tmp.replace(/s+$/, '');
}
function ts_addEvent(elm, evType, fn, useCapture)
// By Scott Andrew
{
if (elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
} else if (elm.attachEvent){
var r = elm.attachEvent("on"+evType, fn);
return r;
} else {
alert("Handler could not be removed");
}
}
ts_addEvent(document, "click",
ml_tsort.set_vars);
ts_addEvent(window, "load",
ml_tsort.sortables_init);
</script>
</head>
<body onclick="outliner(event)">
<table class="sortable" >
<thead >
<tr>
<th class="header"
width="1%" />
<td class="header"> Last
Name:</td>
<td class="header"> First
Name:</td>
<td class="header">
Gender:</td>
</tr>
</thead>
<tr>
<td><A><IMG border="0"
alt="expand/collapse" class="expandable"
height="11"
onclick="changepic(event)"
src="expand.gif" width="9"
child="s1"
p1="p1"></A></td>
<td>Rainbow</td>
<td>Mark</td>
<td>T</td>
</tr>
<tr>
<td colspan="4"
bgcolor="cyan" class="collapsed"
id="s1">
<table>
<tr>
<td > Mark T Rainbow</td>
</tr>
</table>
</td>
</tr>
<tr>
<td><A><IMG border="0"
alt="expand/collapse" class="expandable"
height="11"
onclick="changepic(event)"
src="expand.gif" width="9"
child="s2"
p21="p21" p22="p22"
p22="p23"></A></td>
<td
id="p21">Carlos</td>
<td id="p22">Morris</td>
<td id="p23">N</td>
</tr>
<tr>
<td colspan="4"
bgcolor="cyan" class="collapsed"
id="s2">
<table>
<tr>
<tr>
<td > Carlos N Morris</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
[/code]
------------------------ Yahoo! Groups Sponsor
--------------------~-->
Great things are happening at Yahoo! Groups. See the new
email design.
http://us.click.yahoo.com/lOt0.A/hOaOAA/yQLSAA/edFolB/TM
------------------------------------------------------------
--------~->
Visit http://aiaiai.com for
more groups to join
Yahoo! Groups Links
<*> To visit your group on the web, go to:
ht
tp://groups.yahoo.com/group/JavaScript_Official/
<*> Your email settings:
Individual Email | Traditional
<*> To change settings online go to:
http://groups.yahoo.com/group/JavaScript_Official/join
(Yahoo! ID required)
<*> To change settings via email:
mailto:JavaScript_Official-digest@yahoogroups.com
mailto:JavaScript_Official-fullfeatured@yahoogroups.com
<*> To unsubscribe from this group, send an email to:
JavaScript_Official-unsubscribe@yahoogroups.com
<*> Your use of Yahoo! Groups is subject to:
http://docs.yahoo.c
om/info/terms/
|