8 // variables, assigned with default value, changed later
10 colOrder: new Array(), // array of column order
11 colVisib: new Array(), // array of column visibility
12 tableCreateTime: null, // table creation time, only available in "Browse tab"
13 qtip: null, // qtip API
14 reorderHint: '', // string, hint for column reordering
15 sortHint: '', // string, hint for column sorting
16 markHint: '', // string, hint for column marking
17 colVisibHint: '', // string, hint for column visibility drop-down
18 showReorderHint: false,
21 showColVisibHint: false,
22 showAllColText: '', // string, text for "show all" button under column visibility list
23 visibleHeadersCount: 0, // number of visible data headers
26 dragStartRsz: function(e, obj) { // start column resize
27 var n = $(this.cRsz).find('div').index(obj);
32 objLeft: $(obj).position().left,
33 objWidth: $(this.t).find('th.draggable:visible:eq(' + n + ') span').outerWidth()
35 $('body').css('cursor', 'col-resize');
39 dragStartMove: function(e, obj) { // start column move
40 // prepare the cCpy and cPointer from the dragged column
41 $(this.cCpy).text($(obj).text());
42 var objPos = $(obj).position();
46 height: $(obj).height(),
49 $(this.cPointer).css({
53 // get the column index, zero-based
54 var n = this.getHeaderIdx(obj);
66 $('body').css('cursor', 'move');
70 dragMove: function(e) {
72 var dx = e.pageX - this.colRsz.x0;
73 if (this.colRsz.objWidth + dx > this.minColWidth) {
74 $(this.colRsz.obj).css('left', this.colRsz.objLeft + dx + 'px');
76 } else if (this.colMov) {
77 // dragged column animation
78 var dx = e.pageX - this.colMov.x0;
80 .css('left', this.colMov.objLeft + dx)
84 var hoveredCol = this.getHoveredCol(e);
86 var newn = this.getHeaderIdx(hoveredCol);
87 this.colMov.newn = newn;
88 if (newn != this.colMov.n) {
89 // show the column pointer in the right place
90 var colPos = $(hoveredCol).position();
91 var newleft = newn < this.colMov.n ?
93 colPos.left + $(hoveredCol).outerWidth();
100 // no movement to other column, hide the column pointer
101 $(this.cPointer).css('visibility', 'hidden');
107 dragEnd: function(e) {
109 var dx = e.pageX - this.colRsz.x0;
110 var nw = this.colRsz.objWidth + dx;
111 if (nw < this.minColWidth) {
112 nw = this.minColWidth;
114 var n = this.colRsz.n;
118 $('body').css('cursor', 'default');
122 } else if (this.colMov) {
124 if (this.colMov.newn != this.colMov.n) {
125 this.shiftCol(this.colMov.n, this.colMov.newn);
126 // assign new position
127 var objPos = $(this.colMov.obj).position();
128 this.colMov.objTop = objPos.top;
129 this.colMov.objLeft = objPos.left;
130 this.colMov.n = this.colMov.newn;
131 // send request to server to remember the column order
132 if (this.tableCreateTime) {
135 this.refreshRestoreButton();
138 // animate new column position
139 $(this.cCpy).stop(true, true)
141 top: g.colMov.objTop,
142 left: g.colMov.objLeft
145 $(this.cPointer).css('visibility', 'hidden');
149 $('body').css('cursor', 'default');
150 $('body').noSelect(false);
154 * Resize column n to new width "nw"
156 resize: function(n, nw) {
157 $(this.t).find('tr').each(function() {
158 $(this).find('th.draggable:visible:eq(' + n + ') span,' +
159 'td:visible:eq(' + (g.actionSpan + n) + ') span')
165 * Reposition column resize bars.
167 reposRsz: function() {
168 $(this.cRsz).find('div').hide();
169 var $firstRowCols = $(this.t).find('tr:first th.draggable:visible');
170 for (var n = 0; n < $firstRowCols.length; n++) {
171 $this = $($firstRowCols[n]);
172 $cb = $(g.cRsz).find('div:eq(' + n + ')'); // column border
173 $cb.css('left', $this.position().left + $this.outerWidth(true))
176 $(this.cRsz).css('height', $(this.t).height());
180 * Shift column from index oldn to newn.
182 shiftCol: function(oldn, newn) {
183 $(this.t).find('tr').each(function() {
185 $(this).find('th.draggable:eq(' + newn + '),' +
186 'td:eq(' + (g.actionSpan + newn) + ')')
187 .before($(this).find('th.draggable:eq(' + oldn + '),' +
188 'td:eq(' + (g.actionSpan + oldn) + ')'));
190 $(this).find('th.draggable:eq(' + newn + '),' +
191 'td:eq(' + (g.actionSpan + newn) + ')')
192 .after($(this).find('th.draggable:eq(' + oldn + '),' +
193 'td:eq(' + (g.actionSpan + oldn) + ')'));
196 // reposition the column resize bars
199 // adjust the column visibility list
201 $(g.cList).find('.lDiv div:eq(' + newn + ')')
202 .before($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
204 $(g.cList).find('.lDiv div:eq(' + newn + ')')
205 .after($(g.cList).find('.lDiv div:eq(' + oldn + ')'));
207 // adjust the colOrder
208 var tmp = this.colOrder[oldn];
209 this.colOrder.splice(oldn, 1);
210 this.colOrder.splice(newn, 0, tmp);
211 // adjust the colVisib
212 var tmp = this.colVisib[oldn];
213 this.colVisib.splice(oldn, 1);
214 this.colVisib.splice(newn, 0, tmp);
218 * Find currently hovered table column's header (excluding actions column).
219 * @return the hovered column's th object or undefined if no hovered column found.
221 getHoveredCol: function(e) {
223 $headers = $(this.t).find('th.draggable:visible');
224 $headers.each(function() {
225 var left = $(this).offset().left;
226 var right = left + $(this).outerWidth();
227 if (left <= e.pageX && e.pageX <= right) {
235 * Get a zero-based index from a <th class="draggable"> tag in a table.
237 getHeaderIdx: function(obj) {
238 return $(obj).parents('tr').find('th.draggable').index(obj);
242 * Reposition the table back to normal order.
244 restoreColOrder: function() {
245 // use insertion sort, since we already have shiftCol function
246 for (var i = 1; i < this.colOrder.length; i++) {
247 var x = this.colOrder[i];
249 while (j >= 0 && x < this.colOrder[j]) {
253 this.shiftCol(i, j + 1);
256 if (this.tableCreateTime) {
257 // send request to server to remember the column order
260 this.refreshRestoreButton();
264 * Send column preferences (column order and visibility) to the server.
266 sendColPrefs: function() {
269 db: window.parent.db,
270 table: window.parent.table,
271 token: window.parent.token,
272 server: window.parent.server,
274 col_order: this.colOrder.toString(),
275 col_visib: this.colVisib.toString(),
276 table_create_time: this.tableCreateTime
281 * Refresh restore button state.
282 * Make restore button disabled if the table is similar with initial state.
284 refreshRestoreButton: function() {
285 // check if table state is as initial state
286 var isInitial = true;
287 for (var i = 0; i < this.colOrder.length; i++) {
288 if (this.colOrder[i] != i) {
293 // check if only one visible column left
294 var isOneColumn = this.visibleHeadersCount == 1;
295 // enable or disable restore button
296 if (isInitial || isOneColumn) {
297 $('.restore_column').hide();
299 $('.restore_column').show();
304 * Update current hint using the boolean values (showReorderHint, showSortHint, etc.).
305 * It will hide the hint if all the boolean values is false.
307 updateHint: function(e) {
308 if (!this.colRsz && !this.colMov) { // if not resizing or dragging
310 if (this.showReorderHint && this.reorderHint) {
311 text += this.reorderHint;
313 if (this.showSortHint && this.sortHint) {
314 text += text.length > 0 ? '<br />' : '';
315 text += this.sortHint;
317 if (this.showMarkHint && this.markHint &&
318 !this.showSortHint // we do not show mark hint, when sort hint is shown
320 text += text.length > 0 ? '<br />' : '';
321 text += this.markHint;
323 if (this.showColVisibHint && this.colVisibHint) {
324 text += text.length > 0 ? '<br />' : '';
325 text += this.colVisibHint;
328 // hide the hint if no text
329 this.qtip.disable(!text && e.type == 'mouseenter');
331 this.qtip.updateContent(text, false);
333 this.qtip.disable(true);
338 * Toggle column's visibility.
339 * After calling this function and it returns true, afterToggleCol() must be called.
341 * @return boolean True if the column is toggled successfully.
343 toggleCol: function(n) {
344 if (this.colVisib[n]) {
345 // can hide if more than one column is visible
346 if (this.visibleHeadersCount > 1) {
347 $(this.t).find('tr').each(function() {
348 $(this).find('th.draggable:eq(' + n + '),' +
349 'td:eq(' + (g.actionSpan + n) + ')')
352 this.colVisib[n] = 0;
353 $(this.cList).find('.lDiv div:eq(' + n + ') input').removeAttr('checked');
355 // cannot hide, force the checkbox to stay checked
356 $(this.cList).find('.lDiv div:eq(' + n + ') input').attr('checked', 'checked');
359 } else { // column n is not visible
360 $(this.t).find('tr').each(function() {
361 $(this).find('th.draggable:eq(' + n + '),' +
362 'td:eq(' + (g.actionSpan + n) + ')')
365 this.colVisib[n] = 1;
366 $(this.cList).find('.lDiv div:eq(' + n + ') input').attr('checked', 'checked');
372 * This must be called after calling toggleCol() and the return value is true.
374 * This function is separated from toggleCol because, sometimes, we want to toggle
375 * some columns together at one time and do one adjustment after it, e.g. in showAllColumns().
377 afterToggleCol: function() {
378 // some adjustments after hiding column
383 // check visible first row headers count
384 this.visibleHeadersCount = $(this.t).find('tr:first th.draggable:visible').length;
385 this.refreshRestoreButton();
389 * Show columns' visibility list.
391 showColList: function(obj) {
392 // only show when not resizing or reordering
393 if (!this.colRsz && !this.colMov) {
394 var pos = $(obj).position();
395 // check if the list position is too right
396 if (pos.left + $(this.cList).outerWidth(true) > $(document).width()) {
397 pos.left = $(document).width() - $(this.cList).outerWidth(true);
401 top: pos.top + $(obj).outerHeight(true)
404 $(obj).addClass('coldrop-hover');
409 * Hide columns' visibility list.
411 hideColList: function() {
412 $(this.cList).hide();
413 $(g.cDrop).find('.coldrop-hover').removeClass('coldrop-hover');
417 * Reposition the column visibility drop-down arrow.
419 reposDrop: function() {
420 $th = $(t).find('th:not(.draggable)');
421 for (var i = 0; i < $th.length; i++) {
422 var $cd = $(this.cDrop).find('div:eq(' + i + ')'); // column drop-down arrow
423 var pos = $($th[i]).position();
425 left: pos.left + $($th[i]).width() - $cd.width(),
432 * Show all hidden columns.
434 showAllColumns: function() {
435 for (var i = 0; i < this.colVisib.length; i++) {
436 if (!this.colVisib[i]) {
440 this.afterToggleCol();
444 // wrap all data cells, except actions cell, with span
445 $(t).find('th, td:not(:has(span))')
446 .wrapInner('<span />');
448 g.gDiv = document.createElement('div'); // create global div
449 g.cRsz = document.createElement('div'); // column resizer
450 g.cCpy = document.createElement('div'); // column copy, to store copy of dragged column header
451 g.cPointer = document.createElement('div'); // column pointer, used when reordering column
452 g.cDrop = document.createElement('div'); // column drop-down arrows
453 g.cList = document.createElement('div'); // column visibility list
456 g.cCpy.className = 'cCpy';
460 g.cPointer.className = 'cPointer';
461 $(g.cPointer).css('visibility', 'hidden');
464 g.cDrop.className = 'cDrop';
467 g.cList.className = 'cList';
470 // chain table and grid together
474 // get first row data columns
475 var $firstRowCols = $(t).find('tr:first th.draggable');
477 // initialize g.visibleHeadersCount
478 g.visibleHeadersCount = $firstRowCols.filter(':visible').length;
480 // assign first column (actions) span
481 if (! $(t).find('tr:first th:first').hasClass('draggable')) { // action header exist
482 g.actionSpan = $(t).find('tr:first th:first').prop('colspan');
487 // assign table create time
488 // #table_create_time will only available if we are in "Browse" tab
489 g.tableCreateTime = $('#table_create_time').val();
491 // assign column reorder & column sort hint
492 g.reorderHint = $('#col_order_hint').val();
493 g.sortHint = $('#sort_hint').val();
494 g.markHint = $('#col_mark_hint').val();
495 g.colVisibHint = $('#col_visib_hint').val();
496 g.showAllColText = $('#show_all_col_text').val();
498 // initialize column order
499 $col_order = $('#col_order');
500 if ($col_order.length > 0) {
501 g.colOrder = $col_order.val().split(',');
502 for (var i = 0; i < g.colOrder.length; i++) {
503 g.colOrder[i] = parseInt(g.colOrder[i]);
506 g.colOrder = new Array();
507 for (var i = 0; i < $firstRowCols.length; i++) {
512 // initialize column visibility
513 $col_visib = $('#col_visib');
514 if ($col_visib.length > 0) {
515 g.colVisib = $col_visib.val().split(',');
516 for (var i = 0; i < g.colVisib.length; i++) {
517 g.colVisib[i] = parseInt(g.colVisib[i]);
520 g.colVisib = new Array();
521 for (var i = 0; i < $firstRowCols.length; i++) {
526 if ($firstRowCols.length > 1) {
527 // create column drop-down arrow(s)
528 $(t).find('th:not(.draggable)').each(function() {
529 var cd = document.createElement('div'); // column drop-down arrow
530 var pos = $(this).position();
531 $(cd).addClass('coldrop')
533 left: pos.left + $(this).width() - $(cd).width(),
537 if (g.cList.style.display == 'none') {
543 $(g.cDrop).append(cd);
546 // add column visibility control
547 g.cList.innerHTML = '<div class="lDiv"></div>';
548 var $listDiv = $(g.cList).find('div');
549 for (var i = 0; i < $firstRowCols.length; i++) {
550 var currHeader = $firstRowCols[i];
551 var listElmt = document.createElement('div');
552 $(listElmt).text($(currHeader).text())
553 .prepend('<input type="checkbox" ' + (g.colVisib[i] ? 'checked="checked" ' : '') + '/>');
554 $listDiv.append(listElmt);
555 // add event on click
556 $(listElmt).click(function() {
557 if ( g.toggleCol($(this).index()) ) {
562 // add "show all column" button
563 var showAll = document.createElement('div');
564 $(showAll).addClass('showAllColBtn')
565 .text(g.showAllColText);
566 $(g.cList).append(showAll);
567 $(showAll).click(function() {
570 // prepend "show all column" button at top if the list is too long
571 if ($firstRowCols.length > 10) {
572 var clone = showAll.cloneNode(true);
573 $(g.cList).prepend(clone);
574 $(clone).click(function() {
580 // create column borders
581 $firstRowCols.each(function() {
583 var cb = document.createElement('div'); // column border
584 $(cb).addClass('colborder')
585 .mousedown(function(e) {
586 g.dragStartRsz(e, this);
588 $(g.cRsz).append(cb);
592 // bind event to update currently hovered qtip API
593 $(t).find('th').mouseenter(function(e) {
594 g.qtip = $(this).qtip('api');
597 // create qtip for each <th> with draggable class
598 PMA_createqTip($(t).find('th.draggable'));
601 if (g.reorderHint) { // make sure columns is reorderable
602 $(t).find('th.draggable')
603 .mousedown(function(e) {
604 if (g.visibleHeadersCount > 1) {
605 g.dragStartMove(e, this);
608 .mouseenter(function(e) {
609 if (g.visibleHeadersCount > 1) {
610 g.showReorderHint = true;
611 $(this).css('cursor', 'move');
613 $(this).css('cursor', 'inherit');
617 .mouseleave(function(e) {
618 g.showReorderHint = false;
622 if ($firstRowCols.length > 1) {
623 var $colVisibTh = $(t).find('th:not(.draggable)');
625 PMA_createqTip($colVisibTh);
626 $colVisibTh.mouseenter(function(e) {
627 g.showColVisibHint = true;
630 .mouseleave(function(e) {
631 g.showColVisibHint = false;
635 $(t).find('th.draggable a')
636 .attr('title', '') // hide default tooltip for sorting
637 .mouseenter(function(e) {
638 g.showSortHint = true;
641 .mouseleave(function(e) {
642 g.showSortHint = false;
645 $(t).find('th.marker')
646 .mouseenter(function(e) {
647 g.showMarkHint = true;
650 .mouseleave(function(e) {
651 g.showMarkHint = false;
654 $(document).mousemove(function(e) {
657 $(document).mouseup(function(e) {
660 $('.restore_column').click(function() {
663 $(t).find('td, th.draggable').mouseenter(function() {
668 $(t).addClass('pma_table');
673 $(g.gDiv).prepend(g.cRsz);
674 $(g.gDiv).append(g.cPointer);
675 $(g.gDiv).append(g.cDrop);
676 $(g.gDiv).append(g.cList);
677 $(g.gDiv).append(g.cCpy);
680 g.refreshRestoreButton();
681 g.cRsz.className = 'cRsz';
682 $(t).removeClass('data');
683 $(g.gDiv).addClass('data');
684 $(g.cRsz).css('height', $(t).height());
685 $(t).find('th a').bind('dragstart', function() {
690 // document ready checking
691 var docready = false;
692 $(document).ready(function() {
696 // Additional jQuery functions
698 * Make resizable, reorderable grid.
700 $.fn.makegrid = function() {
701 return this.each(function() {
704 $(document).ready(function() {
710 this.grid.reposDrop();
715 * Refresh grid. This must be called after changing the grid's content.
717 $.fn.refreshgrid = function() {
718 return this.each(function() {
721 $(document).ready(function() {
729 this.grid.reposRsz();
730 this.grid.reposDrop();