@finlexlabs/list
TypeScript icon, indicating that this package has built-in type declarations

0.0.30 • Public • Published

Finlex List View (@finlexlabs/list)

Library Name: finlex-list Package Name: @finlexlabs/list Repo Name: fx-ng-components


Steps to Build & Publish Library

Package Renaming

Go to ./src/finlex-list/package.json Rename package name from @fx-ng-components/finlex-list to @finlexlabs/list

Build

npm run build:list

It will build finlex-confirm-dialog using ng-packagr. The build artifacts will be stored in the dist/ directory.

Publishing

After building your library with ng build:list, go to the dist folder cd dist/finlex-list and run npm publish.

  • Finlex List View is a wrapper component encapsulating the functionality provided by Material Table (mat-table) and Material Paginator (mat-paginator) to produce a standard list design and functionality across all finlex application.
  • If used together with FinlexListAdaptor and a service implementing finlex-api, it can also be used to provide standardized server-side functionality for fetching, sorting, pagination, filtering the list data.
  • There is a seperate finlex-flex-list which inherts Finlex List View logic (and renders the list a bit differently). Please refer to its separate readme if you're interested in that.

NOTE:

We are heavily using Material Table @angular/material/table so in case you're not familiar with them already, following resources will be a good start before you understand finlex-list-view and its functionality:

Background knowledge:

  1. Read here an intro to Angular Table Component
  2. Read here an intro to Angular Paginator Component.
  3. Read here an intro to Angular Sort Component.

(TL;DR) Simple example to use finlex-list for list view with finlexListAdaptor:

module.ts

import { FinlexListModule } from '@finlexlabs/list';

@NgModule({
  imports: [ FinlexListModule ]
})
export class ProductsModule { }

controller.ts

import { FinlexTableDataSourceAdapter, COLUMN_TYPE } from '@finlexlabs/list';

  constructor(
        private listAdaptor: FinlexTableDataSourceAdapter,
  ){}

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // HTTP query param to append to each call
    this.productsList.currentQuery = {
      fields: [ 'name', 'created_at' ]
    };

    // buttons to be displayed for each row (in the last column)
    this.productsList.rowActions.push(
      {
        label: 'open_product',
        tooltipText: 'highlight info',
        icon: 'pageview',
        ngIf: (item) => item.status == 1,
        classes: () => ['accent-fg'],
        onClick: product => console.info('DO SOMETHING WHEN THIS ACTION BUTTON IS CLICKED FOR ITEM', product)
      }
    );

    // Specify column definition for the list
    this.productsList.displayedColumns = [
      {
        columnKey: 'name',
        header: this.translate.instant('NAME_LABEL'),
        type: COLUMN_TYPE.Text
      },
      {
        columnKey: 'created_at',
        header: this.translate.instant('CREATED_AT_LABEL'),
        type: COLUMN_TYPE.Date
      },
      {
        columnKey: 'status',
        header: this.translate.instant('STATUS'),
        type: COLUMN_TYPE.Text,
        icon: {
          onHover: (tender) => this.onNotifyPortfolioManagerForUnderwriterRemarks(tender?._id),
          ngIf: (tender) => tender?.notify_portfolio_manager_on_underwriter_remark,
          name: 'info',
          tooltipText: this.translate.instant('UNDERWRITER_HAS_LEFT_A_COMMENT_FOR_YOU'),
          classes: () => 'warn-fg'
      },
    ];

view.ts

    <finlex-list-view [listAdaptor]="productsList"></finlex-list-view>


(TL;DR) Simple example to use finlex-list for list view without finlexListAdaptor:

module.ts

import { FinlexListModule } from '@finlexlabs/list';
import { MatTableModule } from '@angular/material/table';

@NgModule({
  imports: [ FinlexListModule, MatTableModule ]
})
export class ProductsModule { }

controller.ts


  import { MatTableDataSource } from '@angular/material/table';

  ngOnInit(){

    // creating dummy data only
    const listData = [];
    for (let i = 0; i < 200; i++) {
      listData.push( { id: i, name: `sample object ${i}` } )
    }

    // its important to create instance of MatTableDataSource
    this.sampleListConfig.data = new MatTableDataSource(listData);

    // the displayedColumns should be according to the data
    this.sampleListConfig.displayedColumns = [
      {
        header: 'ID',
        columnKey: 'id'
      },
      {
        header: 'Name',
        columnKey: 'name'
      }
    ];

    // only relavent if pagination is desired
    this.sampleListConfig.config = {
      paginate: true
    }    
  }

view.ts

    <finlex-list-view [dataSource]="sampleListConfig.data"
                      [displayedColumns]="sampleListConfig.displayedColumns"
                      [config]="sampleListConfig.config"></finlex-list-view>

Input bindings for component

  • listAdaptor (type: FinlexTableDataSourceAdapter, required: false)

Although recommended for using server-side support, its not required. More details about this bindings will be explained below. If used, this binding alone is sufficient to render a complete list, please read more about FinlexListAdaptor in the section below.

    • displayedColumns (type: Array, required: true)

displayedColumns are used initialize the columns shown for mat-table and provide customization of each column. The schema and functoinlity of each property is described as below:

Array<{
      columnKey: string; --> used to fetch data in dataSource (each row object will use this key to get the value to dislplay)
      header: string; --> Used to display column title in header row
      translate?: Object; --> object with <key, value> to manually translate row values. It can be used for enums translation also.
      classes?: Function, --> Function to be called for each row item to determine if it should display any css class.
      type?: COLUMN_TYPE --> for formatting the data in the column.
      modifyValue?: Function --> Function to be called for each row item to modify the actual value of each cell for that column.
    }>;

We are currently using Angular's CurrencyPipe to format the currency column cells and DatePipe to format the dates column cells. These pipes should normally be provided in the app.module.ts rather than finlex-list-view explicitly.

    • rowActions (type: Array, required: false)

rowActions binding can be used with or without listAdaptor. If used, a new column is added at the end of the table and the cell for each row shows a button for each rowAction provided. The schema and functionality for each rowAction is as follows:

Array<{
        ngIf: Function, --> If provided, its called with each row object and used to decide if the action button is shown for the row or not.
        onClick: Function; --> If clicked, the onClick function is triggered with row object.
        icon?: string; --> If provided, the button is an icon button. The icon should be string with valid material-icon name.
        label?: string, --> If icon is not provided, a label can also be provided to show as text for the button.
}>;
    • config (type: object, required: false)

In case used, the config object is used to allow different configurations of the list-view. Each configuration and its functionality is described below:

    {
        selectable?: boolean, --> If each row should display a checkbox to allow selection of rows (Also see output binding selectionChanged).
        paginate?: any, --> If listAdaptor is not used, paginate boolean can be used to show/hide the pagination info at the bottom of the list.
        emptyValue?: string, --> If provided, this text is shown whenever a row cell is empty.
        highlightCols? : number, --> This will be depricated, it was used to highlight multiple columns at the left.
        highlightRows? : number --> This will be depricated, it was used to highlight multiple rows at the top
        emptyListPlaceholder?: string --> Optional, can be used to display custom placeholder text when list is empty
    }
  • footerRow (type: any[], required: false)

In case we want to display a footer row with a standard layout and design, this input binding can supply the data for the footer row. It should be an array with the value at each index in the same order as displayedColumns binding.

  • *dataSource (type: MatTableDataSource, required: false)

In case not using listAdaptor binding, this will become required. Its basically MatTableDataSource instance that contains the data to be shown in the list. Its basically passed on to dataSource binding of mat-table.

To disable a particular row, the datasource should include the property "excluded: true" for on that particular index

  • *dataReady (type: boolean, default: true, required: false)

Only used when we want to show the list loading UI, usually its necessary when we are loading async data and while its being fetched.

  • *sortColDirection (type: 'asc' | 'desc', default: 'asc')

Passed on to matSortDirection directive of mat-sort to apply client-side sort for the data shown in the browser (does not support server-side data sorting).

  • *sortColName (type: string, required: false)

Passed on to matSortActive directive of mat-sort to identify the column name that is used for sorting the data.

Output bindings for component

  • selectionChanged (type: EventEmitter)

In case the input binding config.selectable = true, this event is emitted anytime a row is selected or deselected. Please note that the event returns entire selected rows without any prasing/formatting.

  • rowClick (type: EventEmitter)

If provided, this event is triggered when user clicks on any of the row. Unlike rowActions input binding, this event is not triggered when the buttons are clicked for each row, this is emitted when the user clicks anywhere on any of the row. The event returns the clicked row in row format.

    • pageChanged (type: EventEmitter)

This event is fired when user changes the page of the list (same time when mat-paginator fires page event). It would only be necessary to use if we want to apply pagination manually (without listAdaptor binding). In contrast to raw page event from mat-paginator, we parse the response to our own following schema:

    // e is the raw event emitted by page
    {
        limit : e.pageSize,
        skip : e.pageIndex * e.pageSize,
        pageIndex : e.pageIndex
    };

* FOOTNOTE: All the bindings identified with * are used internaly by the FinlexListAdaptor to provide server-side functionality. So if we're using listAdaptor binding (which is recommended), we don't need to use these bindings. In fact, using these bindings together with listAdaptor binding might lead to unexpected behavior.


FinlexListAdaptor class

FinlexListAdaptor is our own custom driver for finlex-list-view to use it together with the a service using finlex-api functionality. It calls the endpoint (supplied to the createList method of the class instance) to fetch the data from the service and makes subsequent calls to fetch the data again in case the sorting, pagination or filters for the list have changed.

FinlexListAdaptor Functions

If listAdaptor binding is used for finlex-list-view, we can also call following methods on FinlexListAdaptor instance to manually trigger sort, pagination and filter server-side. Following are the functions/functionality available on instances of FinlexListAdaptor:

  • applySort

params: object with column name and -1 | 1 as value. This is called internally by finlex-list-view if the user clicks on column header. If we wish to update sorting of the list from another user-defined behavior, we can call the function like this:

controller.ts

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // this function will update the list by making server-side call
    this.productsList.applySort({
        'status' : 1
    })

  • getPageSize

Get current pagination size. params: None. we can call the function like this:

controller.ts

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    this.pageSize = this.productsList.getPageSize();

  • applyPagination

params: pageIndex and pageSize. This is called internally by finlex-list-view if the user clicks 'Next Page' or 'Previous Page' or 'Page Number'. If we wish to update pagination of the list from another user-defined behavior, we can call the function like this:

controller.ts

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // this function will update the list by making server-side call
    this.productsList.applyPagination({
        'insurer_id' : '<<INSURER_ID>>'
    })
  • applyFilters

params:

  • filters: object with object property and value.
  • customizedUrlQueryParam(optional) : This method also updates the nrowser url based on the filters argument. For example, if there is a filter based on customer_id, this will be added to the url tree like this---> .../...?customer_id=:customer_id

for complex filters such as matching elements inside an array of objects, the passed 'filters' argument will have nested objects, meaning the browser url will not be verbose, rather be something like---> .../...?some_field=%5Bbject%20Object%5D

'customizedUrlQueryParam' can be passed for a clearer meaningful browser url. For example, check expired-contracts-list filter.

This is not called internally by finlex-list-view. We can call the function like this:

controller.ts

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // this function will update the list by making server-side call
    this.productsList.applyFilters({
        'insurer_id' : 'INSURER_ID'
    })

FinlexListAdaptor Propertis

  • displayedColumns | rowActions | config

If using listAdaptor binding, the above mentioned bindings of finlex-list-view can be passed as properties of the FinlexListDisplayAdaptor isntance. Example code:

    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // set all config options
    this.productsList.config = {
        selectable: false
    };

    // set single config option
    this.productsList.config.selectable = false;

    // rowActions
    this.productsList.rowActions.push(
      {
        label: 'open_product',
        icon: 'pageview',
        ngIf: (product) => product.status == ProductVersion.STATUS.ACTIVE,
        onClick: product => this.router.navigate(['product-configurations', product.product_id])
      }
    );

    // displayedColumns
    this.productsList.displayedColumns = [
      {
        columnKey: 'name',
        header: this.translate.instant('NAME_LABEL'),
        type: COLUMN_TYPE.Text
      },
      {
        columnKey: 'created_at',
        header: this.translate.instant('CREATED_AT_LABEL'),
        type: COLUMN_TYPE.Date
      },
    ];
    
  • currentSort | currentFilters | currentQuery | currentPageIndex | currentPageSize

These properties have two purposes:

  • They can be read to understand the current state of the list.
  • They can be set to update the list state without triggering an immediate server-side call, in this case, the next server-call (made by whatever next user-behavior triggers a server call) will apply this state. This is specially useful when initalizing the a FinlexListDisplay instance (where we want to set a state of the list without triggering call each time we do so). Sample code:
    this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

    // this will not trigger a service call immediately
    this.productsList.currentQuery = {
      fields: [
        'name',
        'created_at',
        'status'
      ]
    };

    // this will not trigger a service call immediately
    this.productsList.currentSort = {
        'created_at' : 1
    };

    this.productsList.currentPageIndex = 0; // open the first page
    this.productsList.currentPageSize = 10; // set page size of list

  • onListUpdated (type: EventEmitter)

This property can be subscribed to be aware in the controller, in case necessary, when the list data is updated through a new service call. We don't have any exisitng use-case for this but it can prove useful in case we're showing the list data or state outside the list. For example:

        this.productsList = this.listAdaptor.createList(this.productService.products_endpoint);

        this.productsList.onListUpdated
          .subscribe(newListData => {
            console.info('controller knows list data is updated to', newListData);
          });


Future Enhancement (suggestive):

  • Improve footerRow binding to accept an object with displayedColumns as keys and values as the value to be shown in the cell. Currently, its using an array so if the displayedColumns are changed, the footerRow might show different values unless it is changed together.
  • Use Material to format dates in finlex-list-view so its the same as finlex-form-control. Currently we're using Angular DatePipe which leads to different formatting string for material and Angular DatePipe.

Readme

Keywords

none

Package Sidebar

Install

npm i @finlexlabs/list

Weekly Downloads

11

Version

0.0.30

License

none

Unpacked Size

1.25 MB

Total Files

84

Last publish

Collaborators

  • miladrezazadeh
  • npm-at-finlex