這幾天改bug中發現的一些問題,小結一下。從簡單到復雜逐個講。
angular datatable實質上是對jquery庫的包裝,但包裝后不太好用,定制功能比較麻煩。
1. 基本用法
最簡單的用法,大致就是template里:
<table datatable [dtOptions]="dtOptions">
component里:
dtOptions: DataTables.Settings;
最基本的就這兩句,其他代碼都不用改,table就自然有了搜索,按列排序等功能。
2. css
如果要用分頁功能,一是dtOptions需配置一下:
dtOptions: DataTables.Settings = {paging: true};
二是需要在angular.json里加上css路徑,不然分頁欄排版錯亂:
"styles": [
"node_modules/datatables.net-dt/css/jquery.dataTables.css",
3. 去掉(不顯示)上方每頁顯示N條記錄的下拉選擇框
dtOptions配置:
dtOptions: DataTables.Settings = {lengthChange: false;}
4.避免和已有的css沖突
原來的table已經定義了一套css,加上datatable屬性后,原有的css被破壞了。我用個笨辦法,手工把上面那個jquery.dataTables.css的內容拷到另外一個文件,然后注釋掉不需要的部分后再引用。但這樣做了之后,發現表頭和表體各列還是對不齊,最后發現還是要配置dtOptions:
dtOptions: DataTables.Settings = {autoWidth: false;}
5. 數據刷新的問題
這個折騰了不少時間。開始用dtTrigger,即template里:
<table datatable [dtOptions]="dtOptions" [dtTrigger]="dtTrigger">
component里:
dtTrigger: Subject<any> = new Subject<any>(); this.someWebservice.someMethod().subscribe(data => { ... this.dtTrigger.next(); });
問題是刷新數據時就會提示datatable不能再次初始,比較簡單的解決方法是配置一下dtOptions:
dtOptions: DataTables.Settings = { destroy: true};
這樣設置后,不再報錯。問題是按列排序時,發現數據沒有刷新,還是用的老數據。試了不少方法都不行,最后只能把table放在ViewChild里,在父部件里傳數據,這樣dtTrigger就不需要了。
6. 排序問題
1)日期列排序不正確,解決辦法就是設置dtOptions:
dtOptions: DataTables.Settings = {"columnDefs": [{ "targets": [5, 6], "type": "date" }]};
2) 有一個列,是金錢類型,本來沒什么,但是它有個特殊的格式,凡是負數,不顯示負號,而是用括號來表示,如($5.00)。這樣一來,缺省的排序就出錯了。花了不少時間,最后發現解決辦法是利用dtOptions的render屬性:
dtOptions: DataTables.Settings = {"columnDefs": [{ "targets": [9, 10], "render": (data, type, row) => { if (type == 'display') { return this.minusSignPipe.transform(this.currencyPipe.transform(data)); } return Number(data); }]};
上面的currencyPipe是angular自帶的,minusSignPipe是我們自己寫的,把負號轉成括號。
3) 學會了上面render的用法,解決了幾個列的排序問題,但最后有兩個列,用這個方法無法解決。這個列的html代碼是:
<td><a href="javascript:void(0)" (click)="foo(row.id)" *ngIf="row?.bar> 0">{{row?.bar | currency | minusSignPipe}}</a></td>
如果用render,排序是解決了,問題是點擊無反應,無法觸發component里的foo()方法。花了不少時間,最后想到利用javascript變通實現:
<td><a href="javascript:void(0)" onclick="document.getElementById('btnFoo').click();" *ngIf="row?.bar > 0">
{{row?.bar | currency | minusSignPipe}}</a></td> <button id="btnFoo" style="display:none" (click)="foo(row.id)">foo</button>
也即利用一個隱藏的button中轉一下,成功觸發foo()方法。因為onclick里不能用{{row?.id}}的方式傳參數,所以只能用變通的方法中轉。之所以用button的click事件,是因為foo()方法里需要將一個事件emit出去,觸發父部件里的方法:
@Output() emitter = new EventEmitter(); foo(id) { this.emitter.emit(id); }
另外一個列也需要傳遞參數,不過不需要emit,稍微變通了一下,這樣解決:
<td><a href="javascript:void(0)" onclick="window.foo(this.nextSibling.value);" *ngIf="row?.bar && row?.bar != 0">
{{row?.bar | currency | minusSignPipe}}</a><input type="hidden" value="{{row?.id}}" style="width:1px" /></td>
component里:
constructor( ) { window['foo'] = (id) => { this.bar(id); }; }
這個方法不能用於有emit的情況,雖然可以觸發方法,但無法emit出去。
7. drawCallback的應用
dtOptions里有個drawCallback屬性,在排序等操作后會觸發。利用這一特性,解決了一個問題,就是將單數行和雙數行用不同的顏色顯示。開始以為用css就可以了,但發現class="odd"和class="even"經常出現錯亂,極不可靠,特別是排序之后。最后只能用drawCallback。
dtOptions: DataTables.Settings = {drawCallback: (settings) => { let nodes = settings.nTBody.childNodes; let count = 0; if (settings.aoData[0] != undefined) { for (let i = 0; i < nodes.length; i++) { if (nodes[i].nodeName == "TR" && nodes[i].hasChildNodes() == true) { if (count % 2 == 0) { nodes[i].firstChild.parentElement.outerHTML = nodes[i].firstChild.parentElement.outerHTML.replace(/style="background-color:white"/gi, 'style="background-color:#9ddbf2"'); } else { nodes[i].firstChild.parentElement.outerHTML = nodes[i].firstChild.parentElement.outerHTML.replace(/style="background-color:#9ddbf2"/gi, 'style="background-color:white"'); } count = count + 1; } } } } }};
后來發現這個方法在IE上無效,Chrome可以,因為需要通過WebBrowser控件訪問,只好另外想了個麻煩的方法。template里:
<td><span style="display:none;width:1px">{{row?.serial}}</span>{{row?.id}}</td>
這個隱藏的serial相當於數據列表的索引項,在component里另外用一個數組來保存數據行的順序:
rows = []; dataList = []; this.someWebService.someMethod().subscribe(data => { let count = 1; dataList = data; for (let i = 0; i < dataList.length; i++) { dataList[i].serial = count; rows.push(count); count++; } });
然后在drawCallback里給rows數組重新賦值:
dtOptions: DataTables.Settings = {drawCallback: (settings) => { let nodes = settings.nTBody.childNodes; let count = 0; if (settings.aoData[0] != undefined) { for (let i = 0; i < nodes.length; i++) { if (nodes[i].nodeName == "TR" && nodes[i].hasChildNodes() == true) { let str = nodes[i].firstChild.parentElement.outerHTML.toString(); this.rows[count] = parseInt(nodes[i].firstChild.firstChild.textContent); count = count + 1; } } } }};
然后在template里綁定一個方法讀取rows數組:
<td [style.background-color]="getRowStyle(row)"><span style="display:none;width:1px">{{row?.serial}}</span>{{row?.id}}</td>
getRowStyle方法:
getRowStyle(row: Foo) { for (let i = 0; i < this.rows.length; i++) { if (this.rows[i] == row.serial) { if (i % 2 == 0) { return "white"; } else { return "#9ddbf2"; } } } }
方法是比較笨,但一時也想不出好辦法。不過從中也體會到一些drawCallback的應用。