# Column Pinning

## Live example

{% hint style="success" %}
[Column Pinning](https://demos.dikesoft.com/dk-grid/column/pinning) live example.
{% endhint %}

## Lock/Unlock columns

When you define a column as **draggable**, it is possible to **freeze** that column by locking it.

{% hint style="info" %}
To **lock/unlock** a column through the **UI**, you must set the flag <mark style="color:orange;">`allowColumnDragging`</mark> at the grid scope.
{% endhint %}

Open the [Column Context Menu](https://docs.dikesoft.com/fundamentals/grid-structure/column-context-menu#column-pinning) for any column defined as **draggable**. You will see the **lock/unlock** icons.

![Lock/Unlock icons](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FANVmmYjfjPwhdjBW2r3F%2Fcolumn-pinning-lock-unlock-icons.png?alt=media\&token=6db9f1ee-c87f-45b2-ba3d-bb7b4c347baa)

Locking a column is useful when the user moves a column to the **right** or **left** panel and then **freezes** the column to be always **visible**. Even though the user can lock a column in the center panel, it makes more sense to freeze columns in the left or right panels.

{% hint style="info" %}
The user can not move locked columns, and other columns can not occupy their place when dragging operation occurs.
{% endhint %}

## Defining frozen columns

To not allow a user to move a column nor lock/unlock it, define the column as **non-draggable**.

{% hint style="success" %}
Only **draggable** columns are susceptible to being **locked/unlocked**.
{% endhint %}

Consider the following column configuration:

{% code title="column-pinning.component.html" %}

```markup
<dike-grid id="grid-col-pinning" height="650px" #grid="dkgGrid"
    [allowColumnDragging]="gridProperties.allowColumnDragging">

    <dike-grid-column
        fieldName="employeeId"
        headerText="Employee Id"
        dataType="Text"
        width="350"
        panel="leftPanel">
    </dike-grid-column>

    <dike-grid-column
        fieldName="firstName"
        headerText="Name"
        dataType="Text"
        width="150">
    </dike-grid-column>

    <dike-grid-column
        fieldName="lastName"
        headerText="Surname"
        dataType="Text"
        draggable
        groupable>
    </dike-grid-column>

    <dike-grid-column
        fieldName="email"
        headerText="Email"
        dataType="Text"
        width="300"
        draggable
        locked>
    </dike-grid-column>

    <dike-grid-column
        fieldName="personalInfoGroup"
        headerText="Personal Info"
        draggable>

        <dike-grid-column
            fieldName="completeNameGroup"
            headerText="Complete Name"
            draggable>

            <dike-grid-column
                fieldName="firstName"
                headerText="Name"
                dataType="Text"
                width="150"
                draggable
                locked>
            </dike-grid-column>

            <dike-grid-column
                fieldName="lastName"
                headerText="Surname"
                dataType="Text"
                width="150">
            </dike-grid-column>
        </dike-grid-column>

        <dike-grid-column
            fieldName="gender"
            headerText="Gender"
            dataType="Binary"
            width="130"
            draggable
            groupable>
        </dike-grid-column>

        <dike-grid-column
            fieldName="age"
            headerText="Age"
            dataType="Numeric"
            contentAlign="center"
            width="100"
            draggable
            groupable>
        </dike-grid-column>
    </dike-grid-column>

    <dike-grid-column
        fieldName="hireDate"
        headerText="Hire Date"
        dataType="Date"
        width="150"
        draggable
        groupable>
    </dike-grid-column>
</dike-grid>
```

{% endcode %}

With this configuration, we have:

1. All columns are **draggable** except the **Employee Id** column, the **Name** column in the **root** group, and the **Surname** under the **Complete Name** column group.
2. We **locked** the columns **Email** and **Name** under the **Complete Name** group.
3. Finally, we defined the **Employee Id** column in the **left panel**, which is **non-draggable**. The user can not move or lock/unlock this column.

As you can see, the user can not move the Employee Id column and will not lock/unlock it. If you try to move the column, you will see an icon that indicates the user cannot move that column.

![Defining frozen columns](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2F9oJ3D64CUChzvwRpjsMv%2Fcolumn-pinning-frozen-column.gif?alt=media\&token=6910f1a6-d0fe-41ef-9563-07b90ba26964)

## Lock/Unlock column using the API

You can lock/unlock columns using the column API.

{% hint style="info" %}
Remember, to use the **column API**, you must grab the instance of the [<mark style="color:green;">`DikeGridColumnDef`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridcolumndef#methods)service.
{% endhint %}

You use `lockColumn()` and `unlockColumn()` methods for locking and unlocking, respectively.

{% hint style="success" %}
Although you do not set the flag <mark style="color:orange;">`allowColumnDragging`</mark>, you can **lock/unlock** a column using the **API**, but you must define the column as **draggable**.
{% endhint %}

To see the use of the API, we have created the following API:

{% code title="column-pinning.component.html" %}

```markup
<div class="mt-2 flex flex-row flex-wrap items-center justify-around">
    <mat-form-field appearance="fill"
        class="flex-none w-56 m-2">
        <mat-label>Columns</mat-label>
        <mat-select [formControl]="selectedColumn" required
            (selectionChange)="onSelectionChange($event)">
            <mat-option *ngFor="let column of gridColumns" [value]="column" [disabled]="!column.draggable">
                {{ column.headerText }}
            </mat-option>
        </mat-select>
    </mat-form-field>
    
    <button mat-raised-button
        class="flex-none w-56 my-2"
        color="primary"
        [disabled]="!selectedColumn.valid"
        (click)="onChangeLocking()">{{ isColumnLocked ? 'unlock': 'lock' }} column
    </button>
</div>
```

{% endcode %}

The generated output is:

![Lock/Unlock columns using the API](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FTP7WONBjgVZAb7iKcLiJ%2Fcolumn-moving-ui-for-using-api.png?alt=media\&token=bbb9cfbf-4638-4718-b880-5673a6931707)

With the previous UI, you can do the following tasks:

1. You can choose any defined column as **draggable**. It includes column groups.
2. Depending on the locked state, the button next to the dropdown control will display the lock or unlock label.

It is essential to notice that we are **listening** to any change in the **DikeGrid's columns**, attaching a callback to the `columnsChange` output property from the [<mark style="color:green;">`DikeGridComponent`</mark>](https://docs.dikesoft.com/reference/components/dkgridcomponent).

{% tabs %}
{% tab title="column-pinning.component.html" %}

```markup
<dike-grid id="grid-col-pinning" height="650px" #grid="dkgGrid"
    (columnsChange)="onColumnsChange($event)">
</dike-grid>
```

{% endtab %}

{% tab title="column-pinning.component.ts" %}

```typescript
onColumnsChange(columns: DikeColumnDef[]): void {
    this.gridColumns = this.flatColumns(columns);
    // Clear selection:
    this.selectedColumn.patchValue(null);
}
```

{% endtab %}
{% endtabs %}

Every time we receive a new set of columns, we flat them into a single array, invoking the method `flatColumns()`. So indeed, we are **flattening** the **column groups**, traversing them **recursively**.

{% code title="column-pinning.component.ts" %}

```typescript
private flatColumns(columns: DikeColumnDef[]): DikeColumnDef[] {
  return columns.reduce((accum, column) => {
    if (isDikeGroupColumnDef(column) && !!column.children && column.children.length > 0) {
      return [ ...accum, column, ...this.flatColumns(column.children) ];
    }

    return [ ...accum, column ];
  }, [ ]);
}
```

{% endcode %}

If you see the content of the **dropdown** control, you see the columns **Employee Id**, **Name**, **Personal Info** group, **Complete Name** group, then **Name** and **Surname** columns, etc.

![Flattening column groups](https://3888584995-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpDxfe6pgRLqBLMQgZ0kG%2Fuploads%2FnvzxC3nBPxwGYfI4hVfv%2Fcolumn-pinning-flat-columns.png?alt=media\&token=ca8c0f97-6539-413f-9a3d-550e3f1a7f12)

{% hint style="info" %}
The **non-draggable** columns are **disabled**, such as **Employee Id**, **Name** in the **root** group, and **Surname**, under the **Complete Name** group.
{% endhint %}

## Events when Locking/Unlocking columns

When you lock or unlock a column, the DikeGrid instance emits the related event.

{% tabs %}
{% tab title="column-pinning.component.html" %}

```markup
<dike-grid id="grid-col-pinning" height="650px" #grid="dkgGrid"
    (columnLockedChange)="onColumnLockedChange($event)"
    (columnUnLockedChange)="onColumnUnLockedChange($event)">
</dike-grid>
```

{% endtab %}

{% tab title="column-pinning.component.ts" %}

```typescript
onColumnLockedChange(column: DikeColumnDef): void {
  console.log('Column locked: ', column);
}

onColumnUnLockedChange(column: DikeColumnDef): void {
  console.log('Column unlocked: ', column);
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
You can also listen to the `columnLockedChange` and `columnUnLockedChange` events from the [<mark style="color:green;">`DikeGridColumnDef`</mark>](https://docs.dikesoft.com/reference/dkgrid-api/dkgridcolumndef#events)instance.
{% endhint %}

Both events emit the locked/unlocked column. Notice that the sent column is of type <mark style="color:green;">`DikeColumnDef`</mark> because you can lock/unlock column **groups**, not only data columns.

{% hint style="success" %}
Open the **dev console** to see the locked/unlocked column.
{% endhint %}

## Summary

To lock/unlock a column, you must define it as **draggable**. You can lock/unlock columns in any panel, even in the **group** panel, but **freezing** columns in the **left** or **right** panels makes more sense.

### Complete code for this section

{% tabs %}
{% tab title="column-pinning.component.html" %}

```markup
<div class="mt-2 flex flex-row flex-wrap items-center justify-around">
    <mat-form-field appearance="fill"
        class="flex-none w-56 m-2">
        <mat-label>Columns</mat-label>
        <mat-select [formControl]="selectedColumn" required
            (selectionChange)="onSelectionChange($event)">
            <mat-option *ngFor="let column of gridColumns" [value]="column" [disabled]="!column.draggable">
                {{ column.headerText }}
            </mat-option>
        </mat-select>
    </mat-form-field>

    <button mat-raised-button
        class="flex-none w-56 my-2"
        color="primary"
        [disabled]="!selectedColumn.valid"
        (click)="onChangeLocking()">{{ isColumnLocked ? 'unlock': 'lock' }} column
    </button>
</div>

<dike-grid id="grid-col-pinning" height="650px" #grid="dkgGrid"
    [displayRowId]="gridProperties.displayRowId"
    [gridElevation]="gridProperties.matElevation"
    [gridElevationValue]="gridProperties.elevationValue"
    [striped]="gridProperties.stripeRows"
    [verticalRowLines]="gridProperties.verticalRowLines"

    (columnsChange)="onColumnsChange($event)"
    (columnLockedChange)="onColumnLockedChange($event)"
    (columnUnLockedChange)="onColumnUnLockedChange($event)"

    [allowColumnDragging]="gridProperties.allowColumnDragging"
    allowRowGrouping
    [datasource]="dkgDataSource">

    <dike-grid-column
        fieldName="employeeId"
        headerText="Employee Id"
        dataType="Text"
        width="350"
        panel="leftPanel">
    </dike-grid-column>

    <dike-grid-column
        fieldName="firstName"
        headerText="Name"
        dataType="Text"
        width="150">
    </dike-grid-column>

    <dike-grid-column
        fieldName="lastName"
        headerText="Surname"
        dataType="Text"
        draggable
        groupable>
    </dike-grid-column>

    <dike-grid-column
        fieldName="email"
        headerText="Email"
        dataType="Text"
        width="300"
        draggable
        locked>
    </dike-grid-column>

    <dike-grid-column
        fieldName="personalInfoGroup"
        headerText="Personal Info"
        draggable>

        <dike-grid-column
            fieldName="completeNameGroup"
            headerText="Complete Name"
            draggable>

            <dike-grid-column
                fieldName="firstName"
                headerText="Name"
                dataType="Text"
                width="150"
                draggable
                locked>
            </dike-grid-column>
    
            <dike-grid-column
                fieldName="lastName"
                headerText="Surname"
                dataType="Text"
                width="150">
            </dike-grid-column>
        </dike-grid-column>
    
        <dike-grid-column
            fieldName="gender"
            headerText="Gender"
            dataType="Binary"
            width="130"
            draggable
            groupable>
        </dike-grid-column>

        <dike-grid-column
            fieldName="age"
            headerText="Age"
            dataType="Numeric"
            contentAlign="center"
            width="100"
            draggable
            groupable>
        </dike-grid-column>
    </dike-grid-column>

    <dike-grid-column
        fieldName="hireDate"
        headerText="Hire Date"
        dataType="Date"
        width="150"
        draggable
        groupable>
    </dike-grid-column>
</dike-grid>
```

{% endtab %}

{% tab title="column-pinning.component.ts" %}

```typescript
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { MatSelectChange } from '@angular/material/select';

import { Subscription } from 'rxjs';

import { DikeGridDataSourceInput, DikeColumnDef, isDikeGroupColumnDef, DikeGridComponent } from '@dikesoft/angular-data-grid';
import { DikeGridProperties } from 'app/core/config/dike-grid.properties';
import { Employee } from 'app/mock-api/common/employees/data.model';

import { DikeGridConfig } from 'app/services/dike-grid.config.service';
import { SampleData } from 'app/services/sample-data.service';

@Component({
  selector: 'column-pinning',
  templateUrl: './column-pinning.component.html',
  styleUrls: ['./column-pinning.component.scss'],

  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ColumnPinningComponent implements OnInit, OnDestroy {
  // Retrieve the DikeGridComponent<T> instance from the view:
  @ViewChild('grid') dikeGrid: DikeGridComponent<Employee>;

  dkgDataSource: DikeGridDataSourceInput<Employee>;
  gridProperties: DikeGridProperties;

  selectedColumn: FormControl;
  isColumnLocked: boolean;
  gridColumns: DikeColumnDef[];

  private changeGridPropertiesSubscription: Subscription = Subscription.EMPTY;

  constructor(
    private cdr: ChangeDetectorRef,
    private gridConfig: DikeGridConfig,
    private sampleData: SampleData) {

    this.isColumnLocked = false;
  }

  ngOnInit(): void {
    this.dkgDataSource = this.sampleData.getEmployees(1000);
    // Listening to any config property change:
    this.setChangeGridPropertiesSubscription();

    this.selectedColumn = new FormControl(null, Validators.required);
  }

  ngOnDestroy(): void {
    this.changeGridPropertiesSubscription.unsubscribe();
  }

  onSelectionChange(data: MatSelectChange): void {
    this.isColumnLocked = (data.value as DikeColumnDef).locked;
  }

  onChangeLocking(): void {
    if (this.selectedColumn.valid) {
      const column = this.selectedColumn.value as DikeColumnDef;

      if (column.locked) {
        this.dikeGrid.columnDef.unlockColumn(column);

      } else {
        this.dikeGrid.columnDef.lockColumn(column);
      }
    }
  }

  onColumnsChange(columns: DikeColumnDef[]): void {
    this.gridColumns = this.flatColumns(columns);
    // Clear selection:
    this.selectedColumn.patchValue(null);
  }

  onColumnLockedChange(column: DikeColumnDef): void {
    console.log('Column locked: ', column);
  }

  onColumnUnLockedChange(column: DikeColumnDef): void {
    console.log('Column unlocked: ', column);
  }

  private flatColumns(columns: DikeColumnDef[]): DikeColumnDef[] {
    return columns.reduce((accum, column) => {
      if (isDikeGroupColumnDef(column) && !!column.children && column.children.length > 0) {
        return [ ...accum, column, ...this.flatColumns(column.children) ];
      }

      return [ ...accum, column ];
    }, [ ]);
  }

  private setChangeGridPropertiesSubscription(): void {
    this.changeGridPropertiesSubscription.unsubscribe();
    this.changeGridPropertiesSubscription = this.gridConfig.configChange.subscribe((props: DikeGridProperties) => {
      this.gridProperties = props;
      this.cdr.markForCheck();
    });
  }
}

```

{% endtab %}
{% endtabs %}
