diff --git a/js/makegrid.js b/js/makegrid.js index 6e16db9bee..407772ddde 100644 --- a/js/makegrid.js +++ b/js/makegrid.js @@ -3,12 +3,13 @@ // prepare the grid var g = { // constant - minColWidth: 5, + minColWidth: 15, // variables, assigned with default value, changed later alignment: 'horizontal', // 3 possibilities: vertical, horizontal, horizontalflipped actionSpan: 5, colOrder: new Array(), // array of column order + colVisib: new Array(), // array of column visibility tableCreateTime: null, // table creation time, only available in "Browse tab" hintShown: false, // true if hint balloon is shown, used by updateHint() method reorderHint: '', // string, hint for column reordering @@ -18,6 +19,8 @@ showSortHint: false, // boolean, used by showHint() method showMarkHint: false, hintIsHiding: false, // true when hint is still shown, but hide() already called + nHide: -1, // zero-based index of column to hide + visibleHeadersCount: 0, // number of visible data headers // functions dragStartRsz: function(e, obj) { // start column resize @@ -28,11 +31,12 @@ obj: obj, objLeft: $(obj).position().left, objWidth: this.alignment != 'vertical' ? - $(this.t).find('th.draggable:eq(' + n + ') span').outerWidth() : + $(this.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth() : $(this.t).find('tr:first td:eq(' + n + ') span').outerWidth() }; $('body').css('cursor', 'col-resize'); $('body').noSelect(); + $(g.cHide).hide(); }, dragStartMove: function(e, obj) { // start column move @@ -76,6 +80,7 @@ $('body').css('cursor', 'move'); this.hideHint(); $('body').noSelect(); + $(g.cHide).hide(); }, dragMove: function(e) { @@ -141,20 +146,11 @@ } var n = this.colRsz.n; // do the resizing - if (this.alignment != 'vertical') { - $(this.t).find('tr').each(function() { - $(this).find('th.draggable:eq(' + n + ') span,' + - 'td:eq(' + (g.actionSpan + n) + ') span') - .css('width', nw); - }); - } else { // vertical alignment - $(this.t).find('tr').each(function() { - $(this).find('td:eq(' + n + ') span') - .css('width', nw); - }); - } + this.resize(n, nw); + $('body').css('cursor', 'default'); this.reposRsz(); + this.reposDrop(); this.colRsz = false; } else if (this.colMov) { // shift columns @@ -167,7 +163,7 @@ this.colMov.n = this.colMov.newn; // send request to server to remember the column order if (this.tableCreateTime) { - this.sendColOrder(); + this.sendColPrefs(); } this.refreshRestoreButton(); } @@ -187,19 +183,36 @@ $('body').noSelect(false); }, + /** + * Resize column n to new width "nw" + */ + resize: function(n, nw) { + if (this.alignment != 'vertical') { + $(this.t).find('tr').each(function() { + $(this).find('th.draggable:visible:eq(' + n + ') span,' + + 'td:visible:eq(' + (g.actionSpan + n) + ') span') + .css('width', nw); + }); + } else { // vertical alignment + $(this.t).find('tr').each(function() { + $(this).find('td:eq(' + n + ') span') + .css('width', nw); + }); + } + }, + /** * Reposition column resize bars. */ reposRsz: function() { $(this.cRsz).find('div').hide(); $firstRowCols = this.alignment != 'vertical' ? - $(this.t).find('tr:first th.draggable') : + $(this.t).find('tr:first th.draggable:visible') : $(this.t).find('tr:first td'); for (var n = 0; n < $firstRowCols.length; n++) { $this = $($firstRowCols[n]); $cb = $(g.cRsz).find('div:eq(' + n + ')'); // column border - var pad = parseInt($this.css('padding-right')); - $cb.css('left', Math.floor($this.position().left + $this.width() + pad)) + $cb.css('left', $this.position().left + $this.outerWidth(true)) .show(); } }, @@ -229,16 +242,30 @@ // shift rows if (newn < oldn) { $(this.t).find('tr:eq(' + (g.actionSpan + newn) + ')') - .before($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')')); + .before($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')')); } else { $(this.t).find('tr:eq(' + (g.actionSpan + newn) + ')') - .after($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')')); + .after($(this.t).find('tr:eq(' + (g.actionSpan + oldn) + ')')); } } + // adjust the column visibility list + if (newn < oldn) { + $(g.cList).find('tr:eq(' + newn + ')') + .before($(g.cList).find('tr:eq(' + oldn + ')')); + } else { + $(g.cList).find('tr:eq(' + newn + ')') + .after($(g.cList).find('tr:eq(' + oldn + ')')); + } // adjust the colOrder var tmp = this.colOrder[oldn]; this.colOrder.splice(oldn, 1); this.colOrder.splice(newn, 0, tmp); + // adjust the colVisib + var tmp = this.colVisib[oldn]; + this.colVisib.splice(oldn, 1); + this.colVisib.splice(newn, 0, tmp); + + $(g.cHide).hide(); }, /** @@ -247,10 +274,10 @@ */ getHoveredCol: function(e) { var hoveredCol; - $headers = $(this.t).find('th.draggable'); + $headers = $(this.t).find('th.draggable:visible'); if (this.alignment != 'vertical') { $headers.each(function() { - var left = $(this).position().left; + var left = $(this).offset().left; var right = left + $(this).outerWidth(); if (left <= e.pageX && e.pageX <= right) { hoveredCol = this; @@ -258,7 +285,7 @@ }); } else { // vertical alignment $headers.each(function() { - var top = $(this).position().top; + var top = $(this).offset().top; var bottom = top + $(this).height(); if (top <= e.pageY && e.pageY <= bottom) { hoveredCol = this; @@ -286,7 +313,7 @@ /** * Reposition the table back to normal order. */ - restore: function() { + restoreColOrder: function() { // use insertion sort, since we already have shiftCol function for (var i = 1; i < this.colOrder.length; i++) { var x = this.colOrder[i]; @@ -300,23 +327,24 @@ } if (this.tableCreateTime) { // send request to server to remember the column order - this.sendColOrder(); + this.sendColPrefs(); } this.refreshRestoreButton(); }, /** - * Send column order to the server. + * Send column preferences (column order and visibility) to the server. */ - sendColOrder: function() { + sendColPrefs: function() { $.post('sql.php', { ajax_request: true, db: window.parent.db, table: window.parent.table, token: window.parent.token, server: window.parent.server, - set_col_order: true, + set_col_prefs: true, col_order: this.colOrder.toString(), + col_visib: this.colVisib.toString(), table_create_time: this.tableCreateTime }); }, @@ -334,8 +362,10 @@ break; } } + // check if only one visible column left + var isOneColumn = this.visibleHeadersCount == 1; // enable or disable restore button - if (isInitial) { + if (isInitial || isOneColumn) { $('.restore_column').hide(); } else { $('.restore_column').show(); @@ -373,8 +403,8 @@ $(this.dHint) .stop(true, true) .css({ - top: e.pageY, - left: e.pageX + 15 + top: e.clientY, + left: e.clientX + 15 }) .show('fast'); this.hintShown = true; @@ -404,18 +434,167 @@ updateHint: function(e) { if (this.hintShown) { $(this.dHint).css({ - top: e.pageY, - left: e.pageX + 15 + top: e.clientY, + left: e.clientX + 15 }); } + }, + + /** + * Show button to hide column. + */ + showHideBtn: function(obj) { + if (!this.colRsz && ! this.colMov) { + // check if visible headers is more than one + if (this.visibleHeadersCount > 1) { + pos = $(obj).position(); + $(g.cHide).css({ + left: pos.left + $(obj).width(), + top: pos.top + }) + .show(); + this.nHide = this.getHeaderIdx(obj); + } + } + }, + + /** + * Toggle column's visibility. + * After calling this function and it returns true, afterToggleCol() must be called. + * + * @return boolean True if the column is toggled successfully. + */ + toggleCol: function(n) { + if (this.colVisib[n]) { + // can hide if more than one column is visible + if (this.visibleHeadersCount > 1) { + if (this.alignment != 'vertical') { + $(this.t).find('tr').each(function() { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .hide(); + }); + } else { // vertical alignment + $(this.t).find('tr:eq(' + (g.actionSpan + n) + ')') + .hide(); + } + this.colVisib[n] = 0; + $(this.cList).find('tr:eq(' + n + ') input').removeAttr('checked'); + } else { + // cannot hide, force the checkbox to stay checked + $(this.cList).find('tr:eq(' + n + ') input').attr('checked', 'checked'); + return false; + } + } else { // column n is not visible + if (this.alignment != 'vertical') { + $(this.t).find('tr').each(function() { + $(this).find('th.draggable:eq(' + n + '),' + + 'td:eq(' + (g.actionSpan + n) + ')') + .show(); + }); + } else { // vertical alignment + $(this.t).find('tr:eq(' + (g.actionSpan + n) + ')') + .show(); + } + this.colVisib[n] = 1; + $(this.cList).find('tr:eq(' + n + ') input').attr('checked', 'checked'); + } + return true; + }, + + /** + * This must be called after calling toggleCol() and the return value is true. + * + * This function is separated from toggleCol because, sometimes, we want to toggle + * some columns together at one time and do one adjustment after it, e.g. in showAllColumns(). + */ + afterToggleCol: function() { + // some adjustments after hiding column + this.reposRsz(); + this.reposDrop(); + $(g.cHide).hide(); + this.sendColPrefs(); + + // check visible first row headers count + this.visibleHeadersCount = this.alignment != 'vertical' ? + $(this.t).find('tr:first th.draggable:visible').length : + $(this.t).find('th.draggable:nth-child(1):visible').length; + this.refreshRestoreButton(); + this.refreshShowAllButton(); + }, + + /** + * Show columns' visibility list. + */ + showColList: function(obj) { + // only show when not resizing or reordering + if (!this.colRsz && !this.colMov) { + var pos = $(obj).position(); + // check if the list position is too right + if (pos.left + $(this.cList).outerWidth(true) > $(document).width()) { + pos.left = $(document).width() - $(this.cList).outerWidth(true); + } + $(this.cList).css({ + left: pos.left, + top: pos.top + $(obj).outerHeight(true) + }) + .show(); + } + }, + + /** + * Reposition the column visibility drop-down arrow. + */ + reposDrop: function() { + $th = $(t).find('th:not(.draggable)'); + for (var i = 0; i < $th.length; i++) { + var $cd = $(this.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow + var pos = $($th[i]).position(); + $cd.css({ + left: pos.left + $($th[i]).width() - $cd.width(), + top: pos.top + }); + } + }, + + /** + * Refresh "show all columns" button state. + * Make the button disabled if the table's columns are all shown. + */ + refreshShowAllButton: function() { + if (this.visibleHeadersCount < this.colVisib.length) { + $('.show_all_column').show(); + } else { + $('.show_all_column').hide(); + } + }, + + /** + * Show all hidden columns. + */ + showAllColumns: function() { + for (var i = 0; i < this.colVisib.length; i++) { + if (!this.colVisib[i]) { + this.toggleCol(i); + } + } + this.afterToggleCol(); + this.refreshShowAllButton(); } } + // wrap all data cells, except actions cell, with span + $(t).find('th, td:not(:has(span))') + .wrapInner(''); + g.gDiv = document.createElement('div'); // create global div g.cRsz = document.createElement('div'); // column resizer g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header g.cPointer = document.createElement('div'); // column pointer, used when reordering column g.dHint = document.createElement('div'); // draggable hint + g.cHide = document.createElement('div'); // column hide button + g.cDrop = document.createElement('div'); // column drop-down arrows + g.cList = document.createElement('div'); // column visibility list // assign the table alignment g.alignment = $("#top_direction_dropdown").val(); @@ -432,6 +611,17 @@ g.dHint.className = 'dHint'; $(g.dHint).hide(); + // adjust g.cHide + g.cHide.className = 'cHide'; + $(g.cHide).hide(); + + // adjust g.cDrop + g.cDrop.className = 'cDrop'; + + // adjust g.cList + g.cList.className = 'cList'; + $(g.cList).hide(); + // chain table and grid together t.grid = g; g.t = t; @@ -441,6 +631,14 @@ $(t).find('tr:first th.draggable') : $(t).find('tr:first td'); + // get first row of data headers (first column of data headers, in vertical mode) + var $firstRowHeaders = g.alignment != 'vertical' ? + $(t).find('tr:first th.draggable') : + $(t).find('th.draggable:nth-child(1)'); + + // initialize g.visibleHeadersCount + g.visibleHeadersCount = $firstRowHeaders.filter(':visible').length; + // assign first column (actions) span if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist g.actionSpan = g.alignment != 'vertical' ? @@ -468,37 +666,91 @@ } } else { g.colOrder = new Array(); - for (var i = 0; i < $firstRowCols.length; i++) { + for (var i = 0; i < $firstRowHeaders.length; i++) { g.colOrder.push(i); } } + // initialize column visibility + $col_visib = $('#col_visib'); + if ($col_visib.length > 0) { + g.colVisib = $col_visib.val().split(','); + for (var i = 0; i < g.colVisib.length; i++) { + g.colVisib[i] = parseInt(g.colVisib[i]); + } + } else { + g.colVisib = new Array(); + for (var i = 0; i < $firstRowHeaders.length; i++) { + g.colVisib.push(1); + } + } + + if ($firstRowHeaders.length > 1) { + // create column drop-down arrow(s) + $(t).find('th:not(.draggable)').each(function() { + var cd = document.createElement('div'); // column drop-down arrow + var pos = $(this).position(); + $(cd).addClass('coldrop') + .css({ + left: pos.left + $(this).width() - $(cd).width(), + top: pos.top + }) + .click(function() { + if (g.cList.style.display == 'none') { + g.showColList(this); + } else { + $(g.cList).hide(); + } + }); + $(g.cDrop).append(cd); + }); + + // add column visibility control + g.cList.innerHTML = '