LIDOR SYSTEMS

Advanced User Interface Controls and Components

Column with Sorting Menu in Angular Grid

Created: 12 November 2015

Columns in IntegralUI Grid directive for AngularJS by default don't have a menu. Sorting of rows is executed manually from code or by clicking on column header. In following sections of this article, we will show you how to add a custom sorting menu to each column with options to sort rows in ascending or descending order.

Grid directive is part of IntegralUI Studio for Web
a suite of UI Components for development of web apps

In our example above, each column has a drop-down menu with options to sort rows and to change column visibility.

Create Custom Template for Column Header

At first, we need to create the template that will be used as a model to replace the default column header. Because we want to add sorting functionality through a dropdown menu to column header, our template we will contain few span elements:

  • element for showing the column title
  • element for displaying the current sort order
  • element that will act as button showing a dropdown menu when clicked

<div ng-app="appModule" ng-controller="appCtrl">

<script type="text/ng-template" id="column-menu.html">

<div class="column-header">

<span>{{obj.text}}</span>

<span iui-class="column-menu-button icons sort-mark {{obj.icon}}"></span>

<span class="column-menu-button" iui-contextmenu="obj.menu"></span>

</div>

</script>

</div>

.icons-medium

{

margin: 0 7px 0 1px;

}

.sort-ascending

{

background-position: -216px -96px;

}

.sort-descending

{

background-position: -216px -120px;

}

.check-mark

{

background-position: -192px -120px;

}

.column-header

{

margin: 0;

padding: 0 3px;

}

.column-menu-button

{

background-image: url(../../../resources/icons.png);

background-repeat: no-repeat;

border: thin solid #bebebe;

display: inline-block;

position: absolute;

top: 5px;

right: 3px;

margin: 0;

padding: 0;

width: 16px;

height: 16px;

cursor: default;

}

.column-menu-button:last-child

{

background-position: -176px -80px;

}

.column-header:hover > .column-menu-button:last-child

{

background-position: -192px -80px;

border-color: white;

}

.sort-mark

{

border-color: transparent;

right: 24px;

}

.sort-mark-down

{

background-position: -32px -80px;

}

.sort-mark-up

{

background-position: -48px -80px;

}

.sort-mark-down-white

{

background-position: -64px -80px;

}

.sort-mark-up-white

{

background-position: -80px -80px;

}

Related: Create Custom Header and Footer

From our template, you can notice that we are using an object (the obj variable) as our data model. This object is created for each column and it is our link to the template.

Add Sorting Menu to Grid Column

Next, we need to create the structure of the sorting menu. In case of sorting, our menu have only two options:

  • ascending - sorts the grid rows for specified column in ascending order
  • descending - sorts the grid rows for specified column in descending order

// Column menu for sorting grid rows

$scope.menu = {

activate: 'both',

itemIcon: 'icons-medium empty',

items: [

{ icon: 'icons-medium sort-ascending', text: 'Sort Ascending', key: 'SORT_ASCENDING', enabled: $scope.visibleColumns > 0 },

{ icon: 'icons-medium sort-descending', text: 'Sort Descending', key: 'SORT_DESCENDING', enabled: $scope.visibleColumns > 0 }

],

position: 'below'

}

The menu object also specifies how the dropdown menu will open and where it will be positioned. In our case, the menu will open with left and right mouse click, and it will be positioned below the menu button.

Now we have to create a link between columns and the template:

// Objects used by column menu, for each column

$scope.headers = [

{ text: "Title", menu: $scope.menu, icon: 'empty' },

{ text: "Year", menu: $scope.menu, icon: 'empty' },

{ text: "Ratings", menu: $scope.menu, icon: 'empty' }

];

 

// List of grid columns with template specification

$scope.columns = [

{

id: 2,

headerObj: $scope.headers[0],

headerContent: '',

width: 320

},

{

id: 3,

headerObj: $scope.headers[1],

headerContent: '',

contentAlignment: "center",

width: 130

},

{

id: 5,

headerObj: $scope.headers[2],

headerContent: '',

contentAlignment: "center",

width: 110

}

];

Because each column header will have its own dropdown menu, a custom directive is created which will use the column header template. The template object is also created with a link to the menu object.

// Custom Header Directive

angular

.module("appModule", ["integralui"])

.directive('customHeader', function(){

return {

restrict: 'E',

templateUrl: 'column-menu.html',

scope: {

obj: '='

}

};

});

Sort Grid Columns using the DropDown Menu

If we update the grid in current state, each column will display a menu button on the right, which when clicked will show a drop-down menu below. However, still there is no action linked with the menu options.

Whenever a menu option is selected, the itemClick event is fired. We will add a handler to this event and by using the key value for each menu option we can add a different functionality.

var activeColumn = null;

var sortOrder = 'none';

var prevActiveObj = null;

var activeObj = null;

 

// Handler for itemClick event of the sorting menu

$scope.menu.itemClick = function(e){

if (e.item){

// Remove the sorting icon from the previously active column

if (prevActiveObj && prevActiveObj != activeObj){

prevActiveObj.icon = 'empty';

$scope.$apply();

}

 

// Suspend the grid layout to increase performance

$gridService.suspendLayout($scope.gridName);

 

switch (e.item.key){

// In case when Sort Ascending menu option is clicked, sort the grid rows in ascending order

// Also select and update the sorting icon for the active column

case 'SORT_ASCENDING':

if (activeColumn){

sortOrder = 'ascending';

 

$gridService.selectedColumn($scope.gridName, activeColumn);

$gridService.sort($scope.gridName, activeColumn, sortOrder);

 

activeObj.icon = 'sort-mark-up-white';

}

break;

 

// In case when Sort Descending menu option is clicked, sort the grid rows in descending order

// Also select and update the sorting icon for the active column

case 'SORT_DESCENDING':

if (activeColumn){

sortOrder = 'descending';

 

$gridService.selectedColumn($scope.gridName, activeColumn);

$gridService.sort($scope.gridName, activeColumn, sortOrder);

 

activeObj.icon = 'sort-mark-down-white';

}

break;

}

 

// Resume the grid layout and update the grid

$gridService.resumeLayout($scope.gridName);

}

}

In our case, if 'Sort Ascending' option is selected, we will sort the grid rows in ascending order. In similar way, we will implement this for sorting grid rows in descending order.

In code we are using the activeColumn variable to determine which column is active, in other words, from which column the dropdown menu is opened. To know this we are also handling the open event, where we set the value of this variable to point to currently hovered column.

// Handler for open event of the column menu

$scope.menu.open = function(e){

prevActiveObj = activeObj;

 

// Change the reference of the activeColumn to point to currently hovered column

activeColumn = $gridService.getHoverColumn($scope.gridName);

 

// Update the object used by the column template, so we can update the sorting icon

activeObj = activeColumn ? $scope.headers[$scope.columns.indexOf(activeColumn)] : null;

}

Although sorting to the grid is applied, there is no indicator to the user that rows are sorted. To solve this, we are showing a sorting icon which displays whether rows are sorted in ascending or descending order, represented by the up or down arrow.

To update the sorting icon when column is selected (white arrow on dark column header), we will handle the afterSelect event. So whenever the column is selected, the sorting icon will also change from dark to white version of it.

// Handle the afterSelect event of the grid and change the sorting icon

$scope.onAfterSelect = function(e){

if (e.object && activeObj){

switch (sortOrder){

case 'ascending':

activeObj.icon = 'sort-mark-up';

if (activeColumn && activeColumn.selected != false)

activeObj.icon += '-white';

break;

 

case 'descending':

activeObj.icon = 'sort-mark-down';

if (activeColumn && activeColumn.selected != false)

activeObj.icon += '-white';

break;

 

default:

activeObj.icon = 'empty';

break;

}

 

$scope.$apply();

}

}

Show/Hide Column from DropDown Menu

As an addition to our menu, we are also adding options that represent which column are visible. By using these options, we can show or hide a specific column from the dropdown menu.

Because column visibility is changed on demand, the whole menu is updated. For this purpose, we are creating a function that will update the menu structure in whole.

$scope.visibleColumns = 3;

var visibilityMenuItems = [];

 

// Sorting menu options

var defaultMenuItems = [

{ icon: 'icons-medium sort-ascending', text: 'Sort Ascending', key: 'SORT_ASCENDING', enabled: $scope.visibleColumns > 0 },

{ icon: 'icons-medium sort-descending', text: 'Sort Descending', key: 'SORT_DESCENDING', enabled: $scope.visibleColumns > 0 }

];

 

// A function that will update the column menu when column visibility changes

var updateMenu = function(){

$scope.menu.items.length = 0;

$scope.visibleColumns = 0;

 

// Sorting menu options

for (var i = 0; i < defaultMenuItems.length; i++)

$scope.menu.items.push(defaultMenuItems[i]);

 

// Column visibility menu options

visibilityMenuItems.length = 0;

 

var menuItem = { type: "separator" };

if ($scope.columns.length > 0){

$scope.menu.items.push(menuItem);

 

for (var j = 0; j < $scope.columns.length; j++){

menuItem = {

icon: getCheckIcon($scope.columns[j]),

text: $scope.columns[j].headerObj.text

}

 

if ($scope.columns[j].visible != false)

$scope.visibleColumns++;

 

visibilityMenuItems.push(menuItem);

$scope.menu.items.push(menuItem);

}

}

 

$scope.menu.items[1].enabled = $scope.visibleColumns > 0;

$scope.menu.items[2].enabled = $scope.visibleColumns > 0;

 

// Also update the global grid menu

$scope.gridMenu.items.length = 0;

for (var i = 0; i < visibilityMenuItems.length; i++)

$scope.gridMenu.items.push(visibilityMenuItems[i]);

}

In addition, we need to add code to the itemClick event, which will handle the column visibility:

// A modification to the itemClick event handler for the column menu

// Now includes key handlers for column visibility menu options

$scope.menu.itemClick = function(e){

$gridService.suspendLayout($scope.gridName);

 

if (e.item){

switch (e.item.key){

// Previous key handlers here

 

default:

updateColumnVisibility(e.item);

break;

}

 

updateMenu();

 

$gridService.resumeLayout($scope.gridName);

}

}

 

// Returns the check mark icon used for column visibility

var getCheckIcon = function(column){

return column.visible != false ? 'icons-medium check-mark' : null;

}

 

// Updates the check mark in column menu for each column

var updateColumnVisibility = function(item){

var index = visibilityMenuItems.indexOf(item);

if (index >= 0 && index < $scope.columns.length){

$scope.columns[index].visible = $scope.columns[index].visible == undefined ? false : !$scope.columns[index].visible;

item.icon = getCheckIcon($scope.columns[index]);

}

}

Finally, we have a functional dropdown menu, which sorts the grid rows in ascending and descending order, and also shows or hides columns.

Grid Menu for Column Visibility

When all columns are hidden, in order to proceed we need to add a context menu to the grid. This menu will only show the visibility status of columns.

We are using the same menu structure as before, only excluding the sorting options. Our global grid menu looks like this:

// Global menu attached to the grid

// Contains only column visibility menu options

$scope.gridMenu = {

itemIcon: 'icons-medium empty',

items: [],

itemClick: function(e){

if (e.item){

$gridService.suspendLayout($scope.gridName);

 

updateColumnVisibility(e.item);

updateMenu();

 

$gridService.resumeLayout($scope.gridName);

}

}

}

<div ng-app="appModule" ng-controller="appCtrl">

<iui-treegrid name="{{gridName}}" columns="columns" rows="rows" show-footer="false" allow-focus="false" iui-contextmenu="gridMenu" after-select=onAfterSelect(e)"></iui-treegrid>

</div>

This allows us to continue our operations with the grid when all columns are hidden.

Conclusion

Using templates in Angular Grid directive, we can create columns with custom header showing a menu button which when clicked will display a sorting menu. In this way, we can dynamically change the order by which grid rows are sorted, using menu options.

Did you Like this Article?


Enter your e-mail address below and you will receive latest articles as well as news on upcoming events and special offers.