在ABP框架中使用BootstrapTable组件的方法
一、关于abp
abp是“asp.net boilerplate project (asp.net样板项目)”的简称,它是一个成熟的开源框架,基于ddd+repository模式,自带zero权限和认证模块,避免了从零开始搭建框架的烦恼。关于abp的框架优势就此打住,因为这样说下去要说三天三夜,脱离文本主题。
关于abp的入门,博主不想说太多,园子里面tkb至简和阳光铭睿有很多入门级的文章,有兴趣的可以了解下,还是给出它的官网和开源地址。
abp官方网站:
abp开源项目:
ps:如果你不愿意去看它的源码,可以直接查看abp官网上面的演示地址:
点击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() ? : 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); } };
代码不难读懂,解析参数,整合参数,得到参数,发送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'), }, ] });
得到如下效果
总结
以上所述是小编给大家介绍的在abp框架中使用bootstraptable组件的方法,希望对大家有所帮助