DataTable column re-order

// install colreorder

  datatables.net-colreorder (v.2.1.0)
 
		
├── datatables.net-colreorder-dt@2.1.0
├── datatables.net-colreorder@2.1.0
├── datatables.net-dt@2.3.1
├── datatables.net-staterestore-dt@1.4.1
├── datatables.net@2.3.1		
						
npm i datatables.net-colreorder@2.1.0 datatables.net@2.3.1 
npm i datatables.net-colreorder-dt@2.1.0 datatables.net-dt@2.3.1

angular.json
   "node_modules/datatables.net-colreorder/js/dataTables.colReorder.js"
// Some code

import { CurrentCustomer } from 'src/app/models/current-customer';
import { DataTableConfigService } from 'src/app/services/data-table-config.service';

1.   
  disableOrderColumn = [4];  // Disable ordering for the last column
  isForCustomer = true;
  enableEdit = false;
  constructor(
     private dtConfigService: DataTableConfigService
  ) { }
 
2. 
  ngOnInit(): void {
    let isForCustomer = CurrentCustomer.IsCustomer()
    this.enableEdit = this.dtConfigService.hasAdminOrSupervisorAccess();
    if (this.enableEdit) {
      this.disableOrderColumn = [5];
    }
    else {
      this.disableOrderColumn = [4];
    }

    if (!isForCustomer) {
	  this.getRfidList();
    } else {
     // this.getRfidListByCustomerId(CurrentCustomer.CustomerId());
    }
  }

  
3. 
  buildDtOptions() {
    this.dtOptions = this.dtConfigService.buildDtOptions(
      {
        // pageLength: 25
		// order: [[1, 'asc']],
	   // serverSide: true,   ///<<<<<<< Enable server-side processing for paged list
      },
      this.disableOrderColumn,
      {

 //---------------------------------------------------
        data: this.CarList,    //<--- custom property
        columns: [
          { data: 'id', name: 'id', },
          { data: 'rfidNo', name: 'rfidNo' },
          { data: 'serialNo', name: 'serialNo' },
          {
            data: 'isAdmin', name: 'isAdmin',
	   // orderable: false,    ///<<<<< disable orderable
            render: (data: any, type: any, row: any, meta: any) => {
              if (data) {
                return '<div class="text-center text-success"> Yes </div>';
              }
              else {
                return '<div class="text-center text-danger"> No </div>';
              }
            }
          },
          { data: 'rego', name: 'rego' },

  //---------------------------------------------------
          {
            data: null,
            render: (data: any, type: any, row: any, meta: any) => {
             // console.log('✅changed', this.RfidList.size);
              if (this.enableEdit) {
                return (
                  '<div class="text-center operation-3">' +
                  '<button mat-button class="btn btn-outline-warning btn-sm edit-btn">' +
                  '<i class="fa fa-edit" title="Edit"></i>' +
                  '</button><span class="pe-1"></span>' +
                  '<button mat-button class="btn btn-outline-danger btn-sm delete-btn">' +
                  '<i class="fa fa-trash" title="Delete"></i>' +
                  '</button>' +
                  '</div>'
                );
              } else return '<div></div>';
            },
            createdCell: (td, cellData, rowData, row, col) => {
              const self = this;
              $(td)
                .off('click')
                .on('click', '.edit-btn', (e) => {
                  e.stopPropagation(); // Optional: stop event bubbling
                  self.editDialog('Edit', rowData);
                })
                .on('click', '.delete-btn', function (e) {
                  self.deleteDialog(rowData.id, rowData.name);
                });
            },
			   className: 'sticky-col-right',
          },
        ],
     /// below code is only for paged list
	 extraButtons: this.enableEdit ? [
          'copy',
          {
            text: 'CSV',
            extend: 'csvHtml5',
            className: 'btn-success',
            exportOptions: {
              columns: ':visible:not(.noExport)'
            }
          }, 'pdf', 'excel', 'print',
        ] : [], // Add extra buttons only if the user has edit permissions
      }, // <--- custom property
    );
  }

4. 
  getCarList() {
    this.isLoading = true;
    return this.carrierService.getAllCarriers().subscribe({
      next: (res) => {
        this.CarList = res;
        this.buildDtOptions();   ///<<<<<<<<
        this.dtTrigger.next(this.dtOptions);
        this.isLoading = false;
      },
      error: (error) => {
        console.log('Error: retrive Carrier list error - ' + error);
        this.toastr.warning(error.status + '- retrive data error', 'Fail');

      },
    });
  }
// Service

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr';


@Injectable({
  providedIn: 'root'
})
export class DataTableConfigService {
  constructor(
    private toastr: ToastrService,
    private router: Router
  ) { }

  buildDtOptions(config?: Partial<any>, disableOrderColumn: number[] = [], customProps?: any): any {
    let component = this.getCurrentComponent();
    if (!component) {
      console.error('Component name not found. Please ensure the component is properly configured.');
      return {};
    }
    let component_name = component.name.toLowerCase() + '_table_state'; // Ensure the name is in lowercase and ends with '_table_state'

    return {
      scrollY: 720,
      scrollCollapse: true,
      autoWidth: true,
      columnDefs: [
        { orderable: false, targets: disableOrderColumn, className: 'text-center' },
        { targets: '_all', className: 'text-center' } // Center align all columns
      ],
      pagingType: 'full_numbers',
      pageLength: 15,
      lengthMenu: [10, 15, 20, 25, 50, 100],
      colReorder: true,
      responsive: true,
      stateSave: true,
      fixedHeader: true,
      fixedColumns: true,
      ordering: true,
      processing: true,  ///<<<<<<< Enable processing indicator
      ...config,       // Allow override
      ...customProps,  // Add custom props like datatable_name

      stateSaveCallback: (settings: any, data: any) => {
        const { time, ...dataWithoutTime } = data;
        const newState = JSON.stringify(dataWithoutTime);
        const parsedState = localStorage.getItem(component_name);

        if (parsedState) {
          const existingState = JSON.parse(parsedState);
          const { time, ...existingWithoutTime } = existingState;
          const oldState = JSON.stringify(existingWithoutTime);
          // Compare the new state with the existing state

          if (oldState !== newState) {
            localStorage.setItem(component_name, JSON.stringify(data));
            // console.log('✅changed', newState);
          }
          else {
            // console.log('🔄 no changed');
          }
        } else {
          localStorage.setItem(component_name, JSON.stringify(data));
        }
      },
      stateLoadCallback: (settings: any) => {
        const state = localStorage.getItem(component_name);
        return state ? JSON.parse(state) : null;
      },
      stateDuration: 0, // 1:no time limit for localStorage,  (1day)= 60 * 60 * 24, -1:for sessionStorage,

      dom:
        "<'row'<'col-sm-3'l><'col-sm-6 text-center'B><'col-sm-3 pull-right'f>>" +
        "<'row'<'col-sm-12'tr>>" +
        "<'row'<'col-sm-5'i><'col-sm-7 text-right'p>>",
      buttons:  [
        {
          extend: 'createState',
          text: 'Create State', config: {
            creationModal: true,
            toggle: {
              columns: {
                name: true,
                search: true,
                visible: true,
              },
              length: true,
              order: true,
              paging: true,
              scroller: true,
              search: true,
              searchBuilder: true,
              searchPanes: true,
              select: true,
            },
          }
        },
        'savedStates',
        {
          extend: 'removeAllStates',
          text: 'Remove All States',
          action: (e, dt: any, node, config) => {
            this.toastr.toastrConfig.positionClass = 'toast-top-center';
            this.toastr.clear();
            this.toastr.warning('Reset all statuses to default.', 'Warning');
            dt.colReorder.reset();
            localStorage.removeItem(component_name);
            // Remove all saved states
            const states = dt.stateRestore.states();
            if (states && typeof states === 'object') {
              for (const stateName in states) {
                if (states[stateName]?.remove instanceof Function) {
                  states[stateName].remove();
                }
              }
            }

            // Reset the initial columns visibility
            dt.columns().every((index) => {
              dt.column(index).visible(true);
            });

            // Reset the column reordering
            dt.colReorder.reset();
            localStorage.removeItem(component_name);
            dt.draw(); // Redraw after modification
            this.toastr.toastrConfig.positionClass = 'toast-top-right';
          },
        },
        //'colvis',   // Column visibility button
        {
          extend: 'colvis',
          text: 'Column Visibility',
          className: 'buttons-colvis',
          columns: ':not(.noVis)' // Exclude columns with the class 'noVis'
        },
         ...(customProps?.extraButtons || [])  // Allow additional buttons to be added dynamically
      ]


    };
  };

  getCurrentComponent(): any {
    let route = this.router.routerState.root;
    while (route.firstChild) {
      route = route.firstChild;
    }
    return route.routeConfig?.component ?? null;
  }

  getCurrentComponentName(): string {
    const component = this.getCurrentComponent();
    return component ? component.name.toLowerCase() : '';
  }

  hasAdminOrSupervisorAccess(): boolean {
    let currentRole = sessionStorage.getItem('Role');
    if (currentRole === 'Administrator' || currentRole === 'Supervisor') {
      return true
    }
    else {
      return false;
    }
  }

}

Last updated

Was this helpful?