JS組件系列——在ABP中封裝BootstrapTable


 前言:關於ABP框架,博主關注差不多有兩年了吧,一直遲遲沒有嘗試。一方面博主覺得像這種復雜的開發框架肯定有它的過人之處,系統的穩定性和健壯性比一般的開源框架肯定強很多,可是另一方面每每想到它繁瑣的封裝和復雜的開發流程就望而卻步,就這樣遲遲沒有行動。最近在項目里面用到了ABP框架,沒辦法,只有硬着頭皮上了。經過這一段時間的熟悉,算是對這個框架有了一個大概的了解。今天來分享下如何在ABP框架的模式里面使用bootstrapTable組件。

本文原創地址:http://www.cnblogs.com/landeanfen/p/7261651.html

一、關於ABP

ABP是“ASP.NET Boilerplate Project (ASP.NET樣板項目)”的簡稱,它是一個成熟的開源框架,基於DDD+Repository模式,自帶Zero權限和認證模塊,避免了從零開始搭建框架的煩惱。關於ABP的框架優勢就此打住,因為這樣說下去要說三天三夜,脫離文本主題。

關於ABP的入門,博主不想說太多,園子里面tkb至簡和陽光銘睿有很多入門級的文章,有興趣的可以了解下,還是給出它的官網和開源地址。

ABP官方網站http://www.aspnetboilerplate.com

ABP開源項目https://github.com/aspnetboilerplate

PS:如果你不願意去看它的源碼,可以直接查看ABP官網上面的演示地址:https://aspnetzero.com/?ref=abptmplpage

點擊CREATE MY DEMO按鈕,系統會自動為你生成演示地址

進入對應的Demo URL

使用演示的用戶名和密碼登陸進去

可以看到Zero模塊的實現效果。

二、jTable在ABP中的運用

如果你下載ABP的源碼,並且選擇的是混合開發模式(ABP提供了兩種開發模式,一種是基於MVVM的Angular.js的模式;另一種就是MVC+jQuery的混合開發模式),如下圖:

當你Down下來源碼之后你就會發現,ABP的源碼里面的UI部分的表格都是使用jTable去實現的。為什么會用jTable?原因很簡單,jTable是ABP的作者kalkan寫的一款開源插件,自己寫的肯定用自己的東西嘍。下面jTable的效果來一發。

來一個jtable的父子表:

如果是不帶父子表的簡單表格,其實jTable的效果其實還行,可是加上一些復雜的功能之后,那一片片藍色的區域不忍直視,並且jTable的api還有待完善,很多需要的功能都需要自己去實現,於是就接到了將所有的表格組件換成BootstrapTable的需求,才有了今天的主題:在ABP中封裝BootstrapTable。

三、Bootstrap Table在ABP中的封裝

接到需求,博主各種百度、各種谷歌,都找不到Bootstrap Table組件在ABP中的封裝,有的只是在ABP的項目里面簡單的用傳統的方式去初始化組件,這並不是博主想要的。說到這里不得不說一下,如果你使用ABP開發的過程中遇到一些難題,你會發現很難從百度里面搜索到相關答案,谷歌里面有時能找到,但大部分都是英文社區,所以如果你英文較弱,在查找資料上面會很吃虧,有時一個簡單的配置問題需要折騰很久。

1、jTable在ABP項目里面的初始化

首先來看看jTable在一般的ABP項目里面是如何初始化的。比如我們在Application里面有一個如下的接口和實現

 public interface IRequisitionAppService : IApplicationService
    {
        Task<PagedResultDto<RequisitionListDto>> GetRequisitionListAsync(GetRequisitionListInput input);
    }
  [AbpAuthorize(OrderAppPermissions.Pages_Order_Requisition)]
    public class RequisitionAppService : AbpZeroTemplateAppServiceBase, IRequisitionAppService
    {
        private readonly IRepository<Requisition, long> _requisitionRepository;
        public RequisitionAppService(IRepository<Requisition, long> requisitionRepository)
        {
            _requisitionRepository = requisitionRepository;
        }

     public async Task<PagedResultDto<RequisitionListDto>> GetRequisitionListAsync(GetRequisitionListInput input)
        {
            var query = _requisitionRepository.GetAll()
                                                    .WhereIf(input.Status != null, w => (int)w.Status == input.Status.Value)
                                                    .WhereIf(
                                                        !input.Filter.IsNullOrWhiteSpace(),
                                                        u =>
                                                            u.No.Contains(input.Filter) ||
                                                            u.Remark.Contains(input.Filter)
                                                    );

            var count = await query.CountAsync();

            var list = await query
            .OrderBy(input.Sorting)
            .PageBy(input)
            .ToListAsync();

            var dtos = list.MapTo<List<RequisitionListDto>>();
            return new PagedResultDto<RequisitionListDto>(
                count,
                dtos
                );
        }


    }

然后我們前端有一個頁面的列表數據從這個接口GetRequisitionListAsync()獲取

<div class="portlet-body">
    <div id="dataListTable"></div>
</div>
(function () {
    $(function () {
        var _$dataListTable = $('#dataListTable');
        var _service = abp.services.app.requisition;
        _$dataListTable.jtable({
            paging: true,
            sorting: true,
            selecting: true,
            actions: {
                listAction: {
                    method: _service.getRequisitionListAsync
                }
            },
            fields: {
                id: {
                    key: true,
                    list: false
                },
                details: {
                    width: '1%',
                    sorting: false,
                    edit: false,
                    create: false,
                    listClass: 'child-opener-image-column',
                    display: function (detailData) {
                        var $img = $('<img class="child-opener-image" src="/Common/Images/list_metro.png" title="申購明細" />');
                        $img.click(function () {
                            _$dataListTable.jtable('openChildTable',
                                $img.closest('tr'),
                                {
                                    title: "申購明細",
                                    showCloseButton: true,
                                    actions: {
                                        listAction: {
                                            method: _service.getRequisitionDetailListByIdAsync
                                        }
                                    },
                                    fields: {
                                        materialClassParentNameAndName: {
                                            title: app.localize('MaterialClassName'),
                                            width: '8%'
                                        },
                                        materialInfoTypeNo: {
                                            title: app.localize('TypeNo'),
                                            width: '5%'
                                        },
                                        materialInfoLengthDisplayName: {
                                            title: app.localize('LengthDisplayName'),
                                            width: '3%'
                                        },
                                        materialInfoWeight: {
                                            title: app.localize('Weight'),
                                            width: '5%',
                                            display: function (data) {
                                                return data.record.materialInfoMinWeight + '-' + data.record.materialInfoMaxWeight;
                                            }
                                        },
                                        materialInfoMouldTypeDisplayName: {
                                            title: app.localize('MouldTypeDisplayName'),
                                            width: '6%'
                                        },
                                        materialInfoProductionRemark: {
                                            title: app.localize('ProductionRemark'),
                                            width: '8%'
                                        },
                                        materialInfoBundleCountDisplayName: {
                                            title: app.localize('BundleCountDisplayName'),
                                            width: '3%'
                                        },
                                        materialInfoUnitDisplayName: {
                                            title: app.localize('UnitDisplayName'),
                                            width: '3%'
                                        },
                                        materialInfoProcessCost: {
                                            title: app.localize('ProcessCost'),
                                            width: '6%'
                                        },
                                        materialInfoProductRemark: {
                                            title: app.localize('ProductRemark'),
                                            width: '6%'
                                        },
                                        materialInfoRemark: {
                                            title: app.localize('Remark'),
                                            width: '6%'
                                        },
                                        count: {
                                            title: app.localize('申購數量'),
                                            width: '6%'
                                        },
                                        remark: {
                                            title: app.localize('申購備注'),
                                            width: '6%'
                                        }
                                    }
                                }, function (data) {
                                    data.childTable.jtable('load',
                                        { requisitionId: detailData.record.id }
                                    );
                                });
                        });
                        return $img;
                    }
                },
                no: {
                    title: "申購單號",
                    width: '20%'
                },
                creatorUserName: {
                    title: "申購人",
                    width: '20%'
                },
                creationTime: {
                    title: "申購時間",
                    width: '10%',
                    display: function (data) {
                        return moment(data.record.creationTime).format('YYYY-MM-DD HH:mm:ss');
                    }
                },
                sumCount: {
                    title: "總數",
                    width: '10%'
                },
                status: {
                    title: "狀態",
                    width: '20%',
                    display: function (data) {
                        if (data.record.status === app.order.requisitionAuditStatus.audit)
                            return '<span class="label label-info">' + app.localize('Autdit') + '</span>'
                        else if (data.record.status === app.order.requisitionAuditStatus.auditPass)
                            return '<span class="label label-success">' + app.localize('Pass') + '</span>'
                        else if (data.record.status === app.order.requisitionAuditStatus.auditReject)
                            return '<span class="label label-danger">' + app.localize('Reject') + '</span>'
                        else if (data.record.status === app.order.requisitionAuditStatus.delete)
                            return '<span class="label label-danger">' + app.localize('Abandon') + '</span>'
                        else
                            return '<span class="label label-danger">' + app.localize('Unknown') + '</span>'
                    }
                }
            }

        });
    });
})();

得到如下效果:

代碼釋疑:

(1) var _service = abp.services.app.requisition; 這一句聲明當前頁面需要使用哪個服務。

(2)  _service.getRequisitionListAsync 這一句對應的是服務調用的方法,你會發現在后台方法名是GetRequisitionListAsync(),而在js里面卻變成了getRequisitionListAsync(),我們暫且稱之為“潛規則”。

2、bootstrapTable在ABP項目里面的封裝

通過上述代碼你會發現,ABP在application層里面定義的方法,最終會生成某一些js對應的function,這里難點來了。我們找遍了bootstrapTable組件的api,都沒有通過某一個function去獲取數據的啊。這可如何是好?為這個問題,博主折騰了兩天。最開始博主想,function最終還不是要換成http請求的,我們只要拿到http請求的url,然后將function轉換為url不就行了么:

 

我們使用bootstrapTable組件初始化的時候聲明  {url:'/api/services/app/requisition/GetRequisitionListAsync'}  這樣不就行了么?呵呵,經過測試,這樣確實能正確取到數據。但是不夠理想,因為這前面的前綴是ABP給我們生成的,是否會變化我們尚且不說,給每一個url加上這么一長串着實看着很不爽,於是進一步想,是否我們的bootstrapTable也可以使用function去初始化呢,組件沒有,難道我們就不能給他擴展一個嗎?我們不用url獲取數據,通過調用這個function取到數據,然后將數據渲染到組件不就行了。思路有了,那么這里有兩個難題:一是如何將原來url的方式變成這里的調用function的方式呢?二是參數的封裝。經過查看組件的源碼發現,如果是服務端分頁,組件最終是進入到initServer()這個方法去獲取數據,然后渲染到頁面上面的,組件原始的initServer()方法如下:

BootstrapTable.prototype.initServer = function (silent, query) {
        var that = this,
            data = {},
            params = {
                pageSize: this.options.pageSize === this.options.formatAllRows() ?
                    this.options.totalRows : this.options.pageSize,
                pageNumber: this.options.pageNumber,
                searchText: this.searchText,
                sortName: this.options.sortName,
                sortOrder: this.options.sortOrder
            },
            request;

        if (!this.options.url && !this.options.ajax) {
            return;
        }

        if (this.options.queryParamsType === 'limit') {
            params = {
                search: params.searchText,
                sort: params.sortName,
                order: params.sortOrder
            };
            if (this.options.pagination) {
                params.limit = this.options.pageSize === this.options.formatAllRows() ?
                    this.options.totalRows : this.options.pageSize;
                params.offset = this.options.pageSize === this.options.formatAllRows() ?
                    0 : this.options.pageSize * (this.options.pageNumber - 1);
            }
        }

        if (!($.isEmptyObject(this.filterColumnsPartial))) {
            params['filter'] = JSON.stringify(this.filterColumnsPartial, null);
        }

        data = calculateObjectValue(this.options, this.options.queryParams, [params], data);

        $.extend(data, query || {});

        // false to stop request
        if (data === false) {
            return;
        }

        if (!silent) {
            this.$tableLoading.show();
        }
        request = $.extend({}, calculateObjectValue(null, this.options.ajaxOptions), {
            type: this.options.method,
            url: this.options.url,
            data: this.options.contentType === 'application/json' && this.options.method === 'post' ?
                JSON.stringify(data) : data,
            cache: this.options.cache,
            contentType: this.options.contentType,
            dataType: this.options.dataType,
            success: function (res) {
                res = calculateObjectValue(that.options, that.options.responseHandler, [res], res);

                that.load(res);
                that.trigger('load-success', res);
            },
            error: function (res) {
                that.trigger('load-error', res.status, res);
            },
            complete: function () {
                if (!silent) {
                    that.$tableLoading.hide();
                }
            }
        });

        if (this.options.ajax) {
            calculateObjectValue(this, this.options.ajax, [request], null);
        } else {
            $.ajax(request);
        }
    };
組件原始initServer()方法

代碼不難讀懂,解析參數,整合參數,得到參數,發送ajax請求,在success事件里面將得到的數據渲染到界面。讀懂了這段代碼,我們再來封裝function就容易多了。

最終我們封裝的代碼如下:

(function ($) {
    'use strict';

    //debugger;
    //通過構造函數獲取到bootstrapTable里面的初始化方法
    var BootstrapTable = $.fn.bootstrapTable.Constructor,
        _initData = BootstrapTable.prototype.initData,
        _initPagination = BootstrapTable.prototype.initPagination,
        _initBody = BootstrapTable.prototype.initBody,
        _initServer = BootstrapTable.prototype.initServer,
        _initContainer = BootstrapTable.prototype.initContainer;

    //重寫
    BootstrapTable.prototype.initData = function () {
        _initData.apply(this, Array.prototype.slice.apply(arguments));
    };

    BootstrapTable.prototype.initPagination = function () {
        _initPagination.apply(this, Array.prototype.slice.apply(arguments));
    };

    BootstrapTable.prototype.initBody = function (fixedScroll) {
        _initBody.apply(this, Array.prototype.slice.apply(arguments));
    };

    BootstrapTable.prototype.initServer = function (silent, query) {
        //構造自定義參數
        for (var key in this.options.methodParams) {
            $.fn.bootstrapTable.defaults.methodParams[key] = this.options.methodParams[key];
        }
        //如果傳了url,則走原來的邏輯
        if (this.options.url) {
            _initServer.apply(this, Array.prototype.slice.apply(arguments));
            return;
        }
        //如果定義了abpMethod,則走abpMethod的邏輯
        if (!this.options.abpMethod) {
            return;
        }
        var that = this,
            data = {},
            params = {
                pageSize: this.options.pageSize === this.options.formatAllRows() ?
                    this.options.totalRows : this.options.pageSize,
                pageNumber: this.options.pageNumber,
                searchText: this.searchText,
                sortName: this.options.sortName,
                sortOrder: this.options.sortOrder
            },
            request;

        
        //debugger;
        if (this.options.queryParamsType === 'limit') {
            params = {
                search: params.searchText,
                sort: params.sortName,
                order: params.sortOrder
            };
            if (this.options.pagination) {
                params.limit = this.options.pageSize === this.options.formatAllRows() ?
                    this.options.totalRows : this.options.pageSize;
                params.offset = this.options.pageSize === this.options.formatAllRows() ?
                    0 : this.options.pageSize * (this.options.pageNumber - 1);
            }
        }

        if (!($.isEmptyObject(this.filterColumnsPartial))) {
            params['filter'] = JSON.stringify(this.filterColumnsPartial, null);
        }

        data = $.fn.bootstrapTable.utils.calculateObjectValue(this.options, this.options.queryParams, [params], data);

        $.extend(data, query || {});

        // false to stop request
        if (data === false) {
            return;
        }

        if (!silent) {
            this.$tableLoading.show();
        }
        
        this.options.abpMethod(data).done(function (result) {
            result = $.fn.bootstrapTable.utils.calculateObjectValue(that.options, that.options.responseHandler, [result], result);
            that.load(result);
            that.trigger('load-success', result);
        });
        request = $.extend({}, $.fn.bootstrapTable.utils.calculateObjectValue(null, this.options.ajaxOptions), {
            type: this.options.method,
            url: this.options.url,
            data: this.options.contentType === 'application/json' && this.options.method === 'post' ?
                JSON.stringify(data) : data,
            cache: this.options.cache,
            contentType: this.options.contentType,
            dataType: this.options.dataType,
            success: function (res) {
                debugger;
                res = $.fn.bootstrapTable.utils.calculateObjectValue(that.options, that.options.responseHandler, [res], res);

                that.load(res);
                that.trigger('load-success', res);
            },
            error: function (res) {
                that.trigger('load-error', res.status, res);
            },
            complete: function () {
                if (!silent) {
                    that.$tableLoading.hide();
                }
            }
        });

        if (this.options.ajax) {
            $.fn.bootstrapTable.utils.calculateObjectValue(this, this.options.ajax, [request], null);
        } else {
            $.ajax(request);
        }
    }

    BootstrapTable.prototype.initContainer = function () {
        _initContainer.apply(this, Array.prototype.slice.apply(arguments));
    };

    abp.bootstrapTableDefaults = {
        striped: false,
        classes: 'table table-striped table-bordered table-advance table-hover',
        pagination: true,
        cache: false,
        sidePagination: 'server',
        uniqueId: 'id',
        showRefresh: false,
        search: false,
        method: 'post',
        //toolbar: '#toolbar',
        pageSize: 10,
        paginationPreText: '上一頁',
        paginationNextText: '下一頁',
        queryParams: function (param) {
            //$.fn.bootstrapTable.defaults.methodParams.propertyIsEnumerable()
            var abpParam = {
                Sorting: param.sort,
                filter: param.search,
                skipCount: param.offset,
                maxResultCount: param.limit
            };
            for (var key in $.fn.bootstrapTable.defaults.methodParams) {
                abpParam[key] = $.fn.bootstrapTable.defaults.methodParams[key];
            }
            return abpParam;
        },
        responseHandler: function (res) {
            if (res.totalCount)
                return { total: res.totalCount, rows: res.items };
            else
                return { total: res.result.totalCount, rows: res.result.items };
        },
        methodParams: {},
        abpMethod: function () { }
    };
    
    $.extend($.fn.bootstrapTable.defaults, abp.bootstrapTableDefaults);
})(jQuery);

代碼釋疑:增加兩個參數 methodParams: {},abpMethod: function () { } 來獲取abp的function和參數,然后獲取數據的時候如果定義了abpMethod,則通過function獲取數據,否則還是走原來的邏輯。

然后我們調用就簡單了

 //選取界面上要先數據的表格
        var _$SendOrdersTable = $('#SendOrdersTable');
        //獲取服務層方法
        var _SendOrderService = abp.services.app.sendOrder;

        _$SendOrdersTable.bootstrapTable({
            abpMethod: _SendOrderService.getSendOrderListAsync,
            detailView: true,
            onExpandRow: function (index, row, $detail) {
                var cur_table = $detail.html('<table></table>').find('table');
                $(cur_table).bootstrapTable({
                    showRefresh: false,
                    search: false,
                    pagination: false,
                    abpMethod: _SendOrderService.getSendOrderDetailListAsync,
                    methodParams: { SendOrderId: row.id },
                    columns: [
                        {
                            field: 'materialClassName',
                            title: app.localize('MaterialClassName'),
                            width: '8%'
                        },
                        {
                            field: 'typeNo',
                            title: app.localize('TypeNo'),
                            width: '8%'
                        }
                    ]
                });
            },
            columns: [{
                field: 'no',
                title: app.localize('SendOrderNO'),
                align: 'center'
            },
            {
                field: 'supplierName',
                title: app.localize('SupplierName'),
                align: 'center'
            },
            {
                title: app.localize('SendOrderTime'),
                align: 'center',
                field: 'createdDate',
                formatter: function (data) {
                    return moment(data).format('YYYY-MM-DD HH:mm:ss');
                }
            },

            {
                field: 'status',
                align: 'center',
                title: app.localize('SendOrderStatus'),
                formatter: function (data) {
                    var value = "";
                    if (data == 1) {
                        value = '<span class="label label-info">' + app.localize('Autdit') + '</span>';
                    }
                    else if (data == 2) {
                        value = '<span class="label label-success">' + app.localize('Pass') + '</span>';
                    }
                    else if (data == 3) {
                        value = '<span class="label label-default">' + app.localize('Reject') + '</span>';
                    }
                    else
                        value = '<span class="label label-default">' + app.localize('Abandon') + '</span>';
                    return value;
                }
            },

            {
                field: 'createName',
                align: 'center',
                title: app.localize('SendOrderCreator'),
            },


            {
                field: 'sumCount',
                align: 'center',
                title: app.localize('SendOrderTotalCount'),
            },
            ]
        });

得到如下效果

四、總結 

通過以上一個簡單的封裝,順利將bootstrapTable融入到了ABP的操作方式里面。是不是很easy!在使用ABP的過程中,博主還做了其他一些封裝,以后有機會再來介紹,關於ABP的技術交流歡迎聯系博主。這一篇就到這里,歡迎交流。如果你覺得本文能夠幫助你,可以右邊隨意 打賞 博主,打賞后可以獲得博主永久免費的技術支持。

本文原創出處:http://www.cnblogs.com/landeanfen/

歡迎各位轉載,但是未經作者本人同意,轉載文章之后必須在文章頁面明顯位置給出作者和原文連接,否則保留追究法律責任的權利


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM