npm package: @finlexlabs/list
Description: Library Name: finlex-list Package Name: @finlexlabs/list Repo Name: fx-ng-components
Latest version: 0.0.30

# 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`]((@angular/material/table))) and Material Paginator ([`mat-paginator`](https://material.angular.io/components/paginator/)) to produce a standard list design and functionality across all finlex application.
* If used together with [`FinlexListAdaptor`](https://bitbucket.org/finlex-development-team/fx-ng-components/src/master/src/finlex-list/src/lib/finlex-list-adaptor.ts) and [a service implementing `finlex-api`](https://bitbucket.org/finlex-development-team/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](https://bitbucket.org/finlex-development-team/fx-ng-components/src/master/src/finlex-list/src/lib/list/flex-list/readme.md) if you're interested in that.

### NOTE:
We are heavily using Material Table [@angular/material/table](https://material.angular.io/components/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](@angular/material/table) an intro to Angular Table Component
2. [Read here](https://material.angular.io/components/paginator/) an intro to Angular Paginator Component.
3. [Read here](https://material.angular.io/components/sort) 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 <finlex-form-control> 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`]((@angular/material/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`](https://angular.io/api/common/CurrencyPipe) to format the currency column cells and [`DatePipe`](https://angular.io/api/common/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](https://material.angular.io/components/table/api#MatTableDataSource) instance that contains the data to be shown in the list. Its basically passed on to `dataSource` binding of [`mat-table`]((@angular/material/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`](https://material.angular.io/components/sort/api) 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`](https://material.angular.io/components/sort/api) directive of `mat-sort` to identify the column name that is used for sorting the data.


## Output bindings for <finlex-form-control> 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.