/************************************************************************ * DYNAMIC COLUMNS extension for jTable * * (Show/hide/resize columns) * *************************************************************************/ (function ($) { //Reference to base object members var base = { _create: $.hik.jtable.prototype._create, _normalizeFieldOptions: $.hik.jtable.prototype._normalizeFieldOptions, _createHeaderCellForField: $.hik.jtable.prototype._createHeaderCellForField, _createCellForRecordField: $.hik.jtable.prototype._createCellForRecordField }; //extension members $.extend(true, $.hik.jtable.prototype, { /************************************************************************ * DEFAULT OPTIONS / EVENTS * *************************************************************************/ options: { tableId: undefined, columnResizable: true, columnSelectable: true }, /************************************************************************ * PRIVATE FIELDS * *************************************************************************/ _$columnSelectionDiv: null, _$columnResizeBar: null, _cookieKeyPrefix: null, _currentResizeArgs: null, /************************************************************************ * OVERRIDED METHODS * *************************************************************************/ /* Overrides _addRowToTableHead method. *************************************************************************/ _create: function () { base._create.apply(this, arguments); this._createColumnResizeBar(); this._createColumnSelection(); if (this.options.saveUserPreferences) { this._loadColumnSettings(); } this._normalizeColumnWidths(); }, /* Normalizes some options for a field (sets default values). *************************************************************************/ _normalizeFieldOptions: function (fieldName, props) { base._normalizeFieldOptions.apply(this, arguments); //columnResizable if (this.options.columnResizable) { props.columnResizable = (props.columnResizable != false); } //visibility if (!props.visibility) { props.visibility = 'visible'; } }, /* Overrides _createHeaderCellForField to make columns dynamic. *************************************************************************/ _createHeaderCellForField: function (fieldName, field) { var $headerCell = base._createHeaderCellForField.apply(this, arguments); //Make data columns resizable except the last one if (this.options.columnResizable && field.columnResizable && (fieldName != this._columnList[this._columnList.length - 1])) { this._makeColumnResizable($headerCell); } //Hide column if needed if (field.visibility == 'hidden') { $headerCell.hide(); } return $headerCell; }, /* Overrides _createHeaderCellForField to decide show or hide a column. *************************************************************************/ _createCellForRecordField: function (record, fieldName) { var $column = base._createCellForRecordField.apply(this, arguments); var field = this.options.fields[fieldName]; if (field.visibility == 'hidden') { $column.hide(); } return $column; }, /************************************************************************ * PUBLIC METHODS * *************************************************************************/ /* Changes visibility of a column. *************************************************************************/ changeColumnVisibility: function (columnName, visibility) { this._changeColumnVisibilityInternal(columnName, visibility); this._normalizeColumnWidths(); if (this.options.saveUserPreferences) { this._saveColumnSettings(); } }, /************************************************************************ * PRIVATE METHODS * *************************************************************************/ /* Changes visibility of a column. *************************************************************************/ _changeColumnVisibilityInternal: function (columnName, visibility) { //Check if there is a column with given name var columnIndex = this._columnList.indexOf(columnName); if (columnIndex < 0) { this._logWarn('Column "' + columnName + '" does not exist in fields!'); return; } //Check if visibility value is valid if (['visible', 'hidden', 'fixed'].indexOf(visibility) < 0) { this._logWarn('Visibility value is not valid: "' + visibility + '"! Options are: visible, hidden, fixed.'); return; } //Get the field var field = this.options.fields[columnName]; if (field.visibility == visibility) { return; //No action if new value is same as old one. } //Hide or show the column if needed var columnIndexInTable = this._firstDataColumnOffset + columnIndex + 1; if (field.visibility != 'hidden' && visibility == 'hidden') { this._$table .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')') .hide(); } else if (field.visibility == 'hidden' && visibility != 'hidden') { this._$table .find('>thead >tr >th:nth-child(' + columnIndexInTable + '),>tbody >tr >td:nth-child(' + columnIndexInTable + ')') .show() .css('display', 'table-cell'); } field.visibility = visibility; }, /* Prepares dialog to change settings. *************************************************************************/ _createColumnSelection: function () { var self = this; //Create a div for dialog and add to container element this._$columnSelectionDiv = $('
') .addClass('jtable-column-selection-container') .appendTo(self._$mainContainer); this._$table.children('thead').bind('contextmenu', function (e) { if (!self.options.columnSelectable) { return; } e.preventDefault(); //Make an overlay div to disable page clicks $('
') .addClass('jtable-contextmenu-overlay') .click(function () { $(this).remove(); self._$columnSelectionDiv.hide(); }) .bind('contextmenu', function () { return false; }) .appendTo(document.body); self._fillColumnSelection(); //Calculate position of column selection list and show it var containerOffset = self._$mainContainer.offset(); var selectionDivTop = e.pageY - containerOffset.top; var selectionDivLeft = e.pageX - containerOffset.left; var selectionDivMinWidth = 100; //in pixels var containerWidth = self._$mainContainer.width(); //If user clicks right area of header of the table, show list at a little left if ((containerWidth > selectionDivMinWidth) && (selectionDivLeft > (containerWidth - selectionDivMinWidth))) { selectionDivLeft = containerWidth - selectionDivMinWidth; } self._$columnSelectionDiv.css({ left: selectionDivLeft, top: selectionDivTop, 'min-width': selectionDivMinWidth + 'px' }).show(); }); }, /* Prepares content of settings dialog. *************************************************************************/ _fillColumnSelection: function () { var self = this; var $columnsUl = $('') .addClass('jtable-column-select-list'); for (var i = 0; i < this._columnList.length; i++) { var columnName = this._columnList[i]; var field = this.options.fields[columnName]; //Crete li element var $columnLi = $('
  • ').appendTo($columnsUl); //Create label for the checkbox var $label = $('') .append($('' + (field.title || columnName) + '')) .appendTo($columnLi); //Create checkbox var $checkbox = $('') .prependTo($label) .click(function () { var $clickedCheckbox = $(this); var clickedColumnName = $clickedCheckbox.attr('name'); var clickedField = self.options.fields[clickedColumnName]; if (clickedField.visibility == 'fixed') { return; } self.changeColumnVisibility(clickedColumnName, $clickedCheckbox.is(':checked') ? 'visible' : 'hidden'); }); //Check, if column if shown if (field.visibility != 'hidden') { $checkbox.attr('checked', 'checked'); } //Disable, if column is fixed if (field.visibility == 'fixed') { $checkbox.attr('disabled', 'disabled'); } } this._$columnSelectionDiv.html($columnsUl); }, /* creates a vertical bar that is shown while resizing columns. *************************************************************************/ _createColumnResizeBar: function () { this._$columnResizeBar = $('
    ') .addClass('jtable-column-resize-bar') .appendTo(this._$mainContainer) .hide(); }, /* Makes a column sortable. *************************************************************************/ _makeColumnResizable: function ($columnHeader) { var self = this; //Create a handler to handle mouse click event $('
    ') .addClass('jtable-column-resize-handler') .appendTo($columnHeader.find('.jtable-column-header-container')) //Append the handler to the column .mousedown(function (downevent) { //handle mousedown event for the handler downevent.preventDefault(); downevent.stopPropagation(); var mainContainerOffset = self._$mainContainer.offset(); //Get a reference to the next column var $nextColumnHeader = $columnHeader.nextAll('th.jtable-column-header:visible:first'); if (!$nextColumnHeader.length) { return; } //Store some information to be used on resizing var minimumColumnWidth = 10; //A column's width can not be smaller than 10 pixel. self._currentResizeArgs = { currentColumnStartWidth: $columnHeader.outerWidth(), minWidth: minimumColumnWidth, maxWidth: $columnHeader.outerWidth() + $nextColumnHeader.outerWidth() - minimumColumnWidth, mouseStartX: downevent.pageX, minResizeX: function () { return this.mouseStartX - (this.currentColumnStartWidth - this.minWidth); }, maxResizeX: function () { return this.mouseStartX + (this.maxWidth - this.currentColumnStartWidth); } }; //Handle mouse move event to move resizing bar var resizeonmousemove = function (moveevent) { if (!self._currentResizeArgs) { return; } var resizeBarX = self._normalizeNumber(moveevent.pageX, self._currentResizeArgs.minResizeX(), self._currentResizeArgs.maxResizeX()); self._$columnResizeBar.css('left', (resizeBarX - mainContainerOffset.left) + 'px'); }; //Handle mouse up event to finish resizing of the column var resizeonmouseup = function (upevent) { if (!self._currentResizeArgs) { return; } $(document).unbind('mousemove', resizeonmousemove); $(document).unbind('mouseup', resizeonmouseup); self._$columnResizeBar.hide(); //Calculate new widths in pixels var mouseChangeX = upevent.pageX - self._currentResizeArgs.mouseStartX; var currentColumnFinalWidth = self._normalizeNumber(self._currentResizeArgs.currentColumnStartWidth + mouseChangeX, self._currentResizeArgs.minWidth, self._currentResizeArgs.maxWidth); var nextColumnFinalWidth = $nextColumnHeader.outerWidth() + (self._currentResizeArgs.currentColumnStartWidth - currentColumnFinalWidth); //Calculate widths as percent var pixelToPercentRatio = $columnHeader.data('width-in-percent') / self._currentResizeArgs.currentColumnStartWidth; $columnHeader.data('width-in-percent', currentColumnFinalWidth * pixelToPercentRatio); $nextColumnHeader.data('width-in-percent', nextColumnFinalWidth * pixelToPercentRatio); //Set new widths to columns (resize!) $columnHeader.css('width', $columnHeader.data('width-in-percent') + '%'); $nextColumnHeader.css('width', $nextColumnHeader.data('width-in-percent') + '%'); //Normalize all column widths self._normalizeColumnWidths(); //Finish resizing self._currentResizeArgs = null; //Save current preferences if (self.options.saveUserPreferences) { self._saveColumnSettings(); } }; //Show vertical resize bar self._$columnResizeBar .show() .css({ top: ($columnHeader.offset().top - mainContainerOffset.top) + 'px', left: (downevent.pageX - mainContainerOffset.left) + 'px', height: (self._$table.outerHeight()) + 'px' }); //Bind events $(document).bind('mousemove', resizeonmousemove); $(document).bind('mouseup', resizeonmouseup); }); }, /* Normalizes column widths as percent for current view. *************************************************************************/ _normalizeColumnWidths: function () { //Set command column width var commandColumnHeaders = this._$table .find('>thead th.jtable-command-column-header') .data('width-in-percent', 1) .css('width', '1%'); //Find data columns var headerCells = this._$table.find('>thead th.jtable-column-header'); //Calculate total width of data columns var totalWidthInPixel = 0; headerCells.each(function () { var $cell = $(this); if ($cell.is(':visible')) { totalWidthInPixel += $cell.outerWidth(); } }); //Calculate width of each column var columnWidhts = {}; var availableWidthInPercent = 100.0 - commandColumnHeaders.length; headerCells.each(function () { var $cell = $(this); if ($cell.is(':visible')) { var fieldName = $cell.data('fieldName'); var widthInPercent = $cell.outerWidth() * availableWidthInPercent / totalWidthInPixel; columnWidhts[fieldName] = widthInPercent; } }); //Set width of each column headerCells.each(function () { var $cell = $(this); if ($cell.is(':visible')) { var fieldName = $cell.data('fieldName'); $cell.data('width-in-percent', columnWidhts[fieldName]).css('width', columnWidhts[fieldName] + '%'); } }); }, /* Saves field setting to cookie. * Saved setting will be a string like that: * fieldName1=visible;23|fieldName2=hidden;17|... *************************************************************************/ _saveColumnSettings: function () { var self = this; var fieldSettings = ''; this._$table.find('>thead >tr >th.jtable-column-header').each(function () { var $cell = $(this); var fieldName = $cell.data('fieldName'); var columnWidth = $cell.data('width-in-percent'); var fieldVisibility = self.options.fields[fieldName].visibility; var fieldSetting = fieldName + "=" + fieldVisibility + ';' + columnWidth; fieldSettings = fieldSettings + fieldSetting + '|'; }); this._setCookie('column-settings', fieldSettings.substr(0, fieldSettings.length - 1)); }, /* Loads field settings from cookie that is saved by _saveFieldSettings method. *************************************************************************/ _loadColumnSettings: function () { var self = this; var columnSettingsCookie = this._getCookie('column-settings'); if (!columnSettingsCookie) { return; } var columnSettings = {}; $.each(columnSettingsCookie.split('|'), function (inx, fieldSetting) { var splitted = fieldSetting.split('='); var fieldName = splitted[0]; var settings = splitted[1].split(';'); columnSettings[fieldName] = { columnVisibility: settings[0], columnWidth: settings[1] }; }); var headerCells = this._$table.find('>thead >tr >th.jtable-column-header'); headerCells.each(function () { var $cell = $(this); var fieldName = $cell.data('fieldName'); var field = self.options.fields[fieldName]; if (columnSettings[fieldName]) { if (field.visibility != 'fixed') { self._changeColumnVisibilityInternal(fieldName, columnSettings[fieldName].columnVisibility); } $cell.data('width-in-percent', columnSettings[fieldName].columnWidth).css('width', columnSettings[fieldName].columnWidth + '%'); } }); } }); })(jQuery);