/* * Resize Table Columns * * Copyright (c) 2005, Mackley F. Pexton. All rights reserved. */ /** These routines allow a user to resize HTML table columns by clicking and dragging the right border of the column header with their mouse, much like a spreadsheet. Double-clicking on the column header's right border returns the column width to the original size. These routines require the following scripts to be loaded before this script file. A table's column widths can be made resizable by calling the following function: Only the first argument is required. It is either the table's id attribute (string) or the table element (object). The second true/false argument determines whether the column widths are changed as the mouse is moved (true) or if they are changed only when the mouse button is released (false). The optional third argument can be set to "TH" or "TD". It restricts the header elements in the table's first row to be only TH or TD elements. This allows a tables "chrome" to be specified with TD tags and the resizable columns specified with TH tags. Three event handlers are called if they are set to functions. They are: resize_col.onstart -- called when column resizing starts (onmousedown) resize_col.onresize -- called when the guideline moves (onmousemove) resize_col.onstop -- called when column is resized (onmouseup) The event handlers are called with three arguments: event -- event object of mousedown,mouseup,or mousemove. The target element of the event is the guideline. header -- the TH or TD header element object col_num -- the column number (0 based) of the column being resized (useful for accessing data arrays). Every table on the page can be made resizable by calling the following function: The function arguments are optional. The second and third arguments are the same as the second and third arguments of make_table_columns_resizable() above, e.g. update widths as the mouse moves and restrict resizable columns to just th or td tags. The show_results argument, if true, shows the column and table widths in the browser status bar. Theory of operation: - One guideline DIV element is created and attached to the document body. - An onmouseover attribute is added to the TD and/or TH elements of the table's first row. - When the mouse hovers over a table's column header, the guideline is positioned over the column's right border. - When the user clicks on the guideline, its height is set to be the table's height, its right border is displayed, and it moves with the mouse until the user lets up. - When the user lets up on the mouse the difference between the starting and ending coordinates are added to the column's width. Note: The resize_col control object has only class methods (as opposed to object methods) as only one guideline is made or is active at any one time. Variables local to a table are stored as attributes assigned to the tables' elements. Note: Its best if the table's table-layout css style is set to "auto", its default state. */ function make_table_columns_resizable(id, update_live, restrict_to_th_or_td) { table_e = getElementRef(id); if (! table_e) return; // error // Save update_live setting in table element. if (update_live == null) update_live = resize_col.update_live; table_e[resize_col.update_live_attr] = update_live ? true : false; // Attach guideline to document resize_col.make_guideline(); // Get table's first row. var tr = table_e.getElementsByTagName('TR')[0]; // Attach mouseover event handler to table column headers. var node, ncol = 0; for (var i = 0; i < tr.childNodes.length; i++) { node = tr.childNodes[i]; // Skip non-elements. if (!node || node.nodeType != 1) continue; // Skip non-table cell nodes. if (! node.nodeName || (node.nodeName != 'TH' && node.nodeName != 'TD')) continue; if (restrict_to_th_or_td && restrict_to_th_or_td.toUpperCase() != node.nodeName) continue; // Attach handler to column header. if (node.onmouseover) node.orig_onmouseover = node.onmouseover; node.onmouseover = resize_col.position_guideline; // Save column number as an element property. if (resize_col.column_number_attr) node[resize_col.column_number_attr] = ncol; ncol++; } } function make_all_table_columns_resizable(show_results, update_live, restrict_to_th_or_td) { if (show_results != null) resize_col.show_results = show_results; // default var tables = document.getElementsByTagName('TABLE'); for (var i=0; i 0 && resize_col.onresize) resize_col.onresize(evt,resize_col.header_e,resize_col.col_num()); } resize_col.start = function(evt,e) { // Guideline's mousedown event handler. evt = evt ? evt : window.event ? window.event : null; // Record starting values of the column and table widths. resize_col.record_starting_values(evt,e); // Set height of guideline to the height of the table. resize_col.guideline_e.style.height = getElementHeight(resize_col.table_e) + 'px'; // Show guideline. resize_col.guideline_e.style.borderRightStyle = resize_col.guideline_style; // Change cursor (not really needed, but it reduces jitters) document.body.style.cursor = resize_col.cursor; // Set activation flag to start moving guideline. resize_col.activated = true; // Trigger custom event handler. if (resize_col.onstart) resize_col.onstart(evt,resize_col.header_e,resize_col.col_num()); // Attach extra document event handlers. resize_col.attach_document_event_handlers(); // Start resizing the column width. resize_col(evt,e); } resize_col.stop = function(evt) { // Guideline's mouseup event handler. if (! resize_col.activated) return; if (! resize_col.header_e) return; resize_col.collapse_text_selection(); evt = evt ? evt : window.event ? window.event : null; // Compute the new column width. resize_col.adjust_column_width(); // Clear flags. resize_col.activated = false; // Trigger custom event handler. if (resize_col.onstop) resize_col.onstop(evt,resize_col.header_e,resize_col.col_num()); // Check again if custom event handler set the activated flag. if (! resize_col.activated) { // Hide guideline. resize_col.guideline_e.style.borderRightStyle = 'none'; // Set (reset) height of guideline to be the same as the column header. resize_col.guideline_e.style.height = getElementHeight(resize_col.header_e) + 'px'; // Reset cursor document.body.style.cursor='default'; // Remove document event handlers. resize_col.unattach_document_event_handlers(); } } resize_col.position_guideline = function(evt) { // Table column header's mouseover event handler. // Position guideline over the column header's right border. if (! resize_col.guideline_e) return; // guideline has not been made if (resize_col.activated) return; // guideline is already active evt = evt ? evt : window.event ? window.event : null; var e = evt.target ? evt.target : evt.srcElement ? evt.srcElement : null; while (e && ! (e.nodeName == 'TH' || e.nodeName == 'TD')) { e = e.parentNode; } if (!e) return; // Save column header element. resize_col.header_e = e; // Save original column width. var col_width = getElementWidth(e); if (! e[resize_col.original_width_attr]) e[resize_col.original_width_attr] = col_width; // Position guideline over activated column's right border. var x = getEventElementLeft(evt,e) + col_width - 1; var y = getEventElementTop(evt,e); resize_col.move_guideline(x,y); // Set (reset) height of guideline to be the same as the column header. resize_col.guideline_e.style.height = getElementHeight(e) + 'px'; // Show "invisible" guideline to provide a mouse hot spot. resize_col.guideline_e.style.display = 'block'; // Execute original mouseover event (if one exists) if (resize_col.header_e.orig_onmouseover) resize_col.header_e.orig_onmouseover(); } resize_col.move_guideline = function(x,y) { if (x == null || x < 0) x = 0; x--; // keep guideline under cursor if (bv.isIE6) x-= resize_col.guideline_width + 1; if (bv.isIE5) x -= 2; // hack for IE positioning bug resize_col.guideline_e.style.left = x + 'px'; if (y != null) { if (bv.isIE5) y -= 2; // hack for IE positioning bug resize_col.guideline_e.style.top = y + 'px'; } } resize_col.collapse_text_selection = function() { // Un-select text that might have been selected as the mouse moved. if (window.getSelection) { var sel = window.getSelection(); if (sel.removeAllRanges) sel.removeAllRanges(); // Moz else if (sel.getRangeAt) sel.getRangeAt(0).collapse(true); // DOM } else if (document.selection && document.selection.empty) { document.selection.empty(); // IE } } resize_col.record_starting_values = function(evt,e) { // Save starting x coordinate var x = (evt.pageX ? evt.pageX : evt.clientX); if (bv.isIE5) x += document.body.scrollLeft; if (bv.isIE6) x += document.body.parentNode.scrollLeft; resize_col.xstart = x; resize_col.ystart = null; // Clear accumulators. resize_col.prev_xdiff = 0; resize_col.xdiff = 0; resize_col.ydiff = 0; // Save the column's starting width. resize_col.header_width = getElementWidth(resize_col.header_e); resize_col.prev_header_width = null; // Save the column's starting CSS width. // Note: the starting width read from the DOM includes padding // and border dimensions, which the CSS width attribute doesn't. resize_col.col_wstart = resize_col.css_width(resize_col.header_e,resize_col.header_width); // Get table element. resize_col.table_e = resize_col.header_e.parentNode; while (resize_col.table_e && resize_col.table_e.nodeName != 'TABLE') { resize_col.table_e = resize_col.table_e.parentNode; } // Save the table's starting width. resize_col.table_width = getElementWidth(resize_col.table_e); // Save the table's starting CSS width. // Note: tables are different from normal elements. The borders and // padding do not have to be subtracted from the DOM dimensions. resize_col.table_wstart = resize_col.table_width; // Note: Firefox adds the table's margins into the computed width of the // table if it does not "fill" the available width of the enclosing tag. // Because of this, it is best if the table does not specify any left or // right CSS margins. if (bv.isMoz || bv.isNS) { // Firefox 1.0 // Subtract table margins (a gecko bug?) var table_margin_left = getMargin(resize_col.table_e,'left'); var table_margin_right = getMargin(resize_col.table_e,'right'); resize_col.table_wstart -= table_margin_left; resize_col.table_wstart -= table_margin_right; // Set margin style attribute of table element to make the next // lookup faster. A Firefox bug(?) requires a laborious search // of style sheets for the margin setting as it is always 0 // when retrieved though document.defaultView.getComputedStyle(). resize_col.table_e.style.marginLeft = table_margin_left + 'px'; resize_col.table_e.style.marginRight = table_margin_right + 'px'; } } resize_col.adjust_column_width = function() { // Adjust width of table column (and width of entire table). // If width is not positive, then set to 1 and let the browser resize // the column to the column's minimum size. var col_w = resize_col.col_wstart + resize_col.xdiff; if (col_w < 1) { col_w = 1; resize_col.xdiff = col_w - resize_col.col_wstart; } // Compute the new table width. var table_w = resize_col.table_wstart + resize_col.xdiff; // Initialize this function's return value. var column_altered = true; // assume true unless proven false if (resize_col.xdiff > resize_col.prev_xdiff) { // Expanding columns. // Set new table width. resize_col.table_e.style.width = table_w + 'px'; // Set new column width. resize_col.header_e.style.width = col_w + 'px'; resize_col.prev_header_width = null; } else { // Shrinking columns. if (resize_col.xdiff >= 0) { // No need to check if browser will actually shrink column. // The column width is still larger than beginning width. // Set new table width. resize_col.table_e.style.width = table_w + 'px'; // Set new column width. resize_col.header_e.style.width = col_w + 'px'; resize_col.prev_header_width = null; } else { // Check if browser actually changed the column width before // shrinking the table width. // Save previous width. if (! resize_col.prev_header_width) resize_col.prev_header_width = getElementWidth(resize_col.header_e); // Set new column width. resize_col.header_e.style.width = col_w + 'px'; // Check if width actually changed. var curr_header_width = getElementWidth(resize_col.header_e); var wdiff = curr_header_width - resize_col.prev_header_width; if (wdiff == 0) { // No change to column width resize_col.xdiff = resize_col.prev_xdiff; // Reset table width. table_w = resize_col.table_wstart + resize_col.xdiff; column_altered = false; } else if (wdiff > (resize_col.xdiff - resize_col.prev_xdiff)) { // Browser did not shrink the column as specified. // Set coordinates to the amount of actual change. resize_col.xdiff = resize_col.prev_xdiff + wdiff; var new_col_w = resize_col.css_width(resize_col.header_e,curr_header_width); table_w += col_w - new_col_w; col_w = new_col_w; } resize_col.table_e.style.width = table_w + 'px'; resize_col.prev_header_width = curr_header_width; } } if (resize_col.show_results && column_altered) { window.status = "Column "+resize_col.col_num()+": "+col_w+"px" + ", Table: "+table_w+"px"; } resize_col.prev_xdiff = resize_col.xdiff; resize_col.prev_column_altered = column_altered; return column_altered; } resize_col.col_num = function() { // Return the column number saved in the header when the table was setup. return resize_col.header_e[resize_col.column_number_attr]; } resize_col.css_width = function(e,dom_width) { return dom_width - getPadding(e,'left') - getPadding(e,'right') - getBorderWidth(e,'left') - getBorderWidth(e,'right'); } resize_col.reset_width = function(evt,e) { // Guideline's dblclick event handler. // Reset the column width to the original width. var col_width = getElementWidth(resize_col.header_e); var wdiff = col_width - resize_col.header_e[resize_col.original_width_attr]; var css_col_width = resize_col.css_width(resize_col.header_e,resize_col.header_e[resize_col.original_width_attr]); var table_width = getElementWidth(resize_col.table_e) - wdiff; var css_table_width = table_width; resize_col.table_e.style.width = css_table_width + 'px'; resize_col.header_e.style.width = css_col_width + 'px'; // Trigger custom event handler. if (resize_col.onstop) resize_col.onstop(evt,resize_col.header_e,resize_col.col_num()); resize_col.guideline_e.style.display = 'none'; // remove hot spot } /* * Extra event handlers * (needed only for IE and Safari) */ resize_col.attach_document_event_handlers = function() { resize_col.saved_onmousemove = document.onmousemove; document.onmousemove = resize_col; resize_col.saved_onmouseup = document.onmouseup; document.onmouseup = resize_col.stop; } resize_col.unattach_document_event_handlers = function() { document.onmouseup = resize_col.saved_onmouseup ? resize_col.saved_onmouseup : null; document.onmousemove = resize_col.saved_onmousemove ? resize_col.saved_onmousemove : null; } /* * Guideline */ resize_col.make_guideline = function() { resize_col.guideline_node = document.createElement("DIV"); resize_col.guideline_node.innerHTML = resize_col.guideline_html(); resize_col.guideline_e = resize_col.guideline_node.firstChild; document.body.insertBefore(resize_col.guideline_e,document.body.firstChild); } resize_col.guideline_html = function() { // Make vertical guideline element that moves with the mouse when resizing. return '' + '
' + ' ' + '
'; }