接触mvc不久,突然没有了viewstate和服务端控件处处都觉得不顺手,很多在webform时不必要考虑的问题都出现在眼前,这其中分页时查询条件保持的问题又是最让我头疼的事情,权衡再三,决定用ajax局部刷新列表的方式来解决这个问题。网上基于jquery的grid组件很多,jquerygrid,jqgrid等等,分别试用了一下,功能确实非常强大,但感觉上有点重,配置项太多,用起来依然感觉束手束脚,所以想来想去,还是用最笨的办法自己做了一个组件,很简陋,唯一的好处就是灵活,容易修改和控制。
没图没真相,先来个截图看看:
功能:
- 指定url,支持post或get方式加载数据
- 自定义数据查询条件
- 列定义时可以对数据项进行简单的转换处理。如:数据源[{ "name": "张三", "age": 12, "sex": 1 }],渲染时希望把sex的值 1 显示为 男性
- 支持多选
- 支持分页
- 点击行时自动选中,多选时,点击checkbox进行选中和取消选中
- 能够返回选中项目的值,多选时返回选中值的数组
根据个人的习惯和对功能的分解,定义了这样一个setting:
var list = $('#list').GridView({ 'apiUrl': '/Student/List', // 指定数据请求的URL路径 'apiType': 'post', // 请求的方式 'columns': [ // 要显示的列,title对应表头,column对应数据项的属性名称,width指定列的宽度,func指定绑定时调用的函数名称 { title: '姓名', column: 'Name', 160 }, { title: '年龄', column: 'Age' }, { title: '性别', column: 'Sex', 100, 'func': 'convertToSex' } ], 'valueColumn': 'StudentId', // data-value 取值的属性名 'pageSize': 20, // 每页显示的数量 'isMultiy': false, // 是否支持多选 'isTreeView': false, // 是否支持树形 'pager': 'pager', // 指定包含分页的divid,主要是为了能单独控制pager中的一些数据,把pager给拆出来了,后来发现似乎用处不大 'onRowClick': function(id) { }, // 当数据行被点击时执行的回调,参数是tr中的data-value 'convertSource': function (data) { return data.body; }, // 使用数据源之前对数据进行转换。因为我的api返回的都是{ code: 200, body: [] }这种类型,需要在这里直接返回body 'onDataBindComplete': function() {}, // 当数据加载完成,也就是列表渲染完成后的回调。比如说提醒用户加载完成之类的 'getSearchData': function() { return $('#form1').serialize(); }, // 获取查询参数,这个很重要,想了很多办法,最终采用了这种方案,在查询前运行这个函数,将返回值作为ajax的查询参数 'listCssClass': 'table', // 列表table的样式名 'pagerCssClass': 'pager', // 分页最外面div的样式名 'beforeSend': function() { } // ajax请求之前调用的函数,原本是为了提醒一下加载已开始,请稍后之类的,现在没怎么用到 });
OK,接下来就是这个扩展的具体实现了,代码很多,但思路很简单,主要就是代码的拼接。大家自己看吧!
(function ($) { $.fn.GridView = function (settings) { // 系统变量 var self = this; self.selected = []; var pageindex = 1, pageSize = 20; if (settings.pageSize && $.isNumeric(settings.pageSize) && settings.pageSize > 0) { pageSize = settings.pageSize; } var rand = Math.floor(Math.random() * 1000); var table = $("<table" + (settings.listCssClass ? " class='" + settings.listCssClass + "'" : "") + " id='myList" + rand + "'></table>"); var colgroup = colgroup2 = "<colgroup>" $.each(settings.columns, function (idx, item) { colgroup += "<col" + item.width ? " style='" + item.width + "px;'" : "" + ">"; colgroup2 += "<col" + item.width ? " style='" + item.width + "px;'" : "" + ">"; }); colgroup += "<col style='18px;'></col>"; colgroup += "</colgroup>"; colgroup2 += "</colgroup>"; var table = $("<table" + (settings.listCssClass ? " class='" + settings.listCssClass + "'" : "") + " id='myList" + rand + "'></table>"); var pagerHtml = '<div' + (!settings.pagerCssClass ? '' : ' class="' + settings.pagerCssClass + '"') + '>'; //var pagerHtml = ''; pagerHtml += '<a class="disabled" id="first' + rand + '"><i class="fa fa-fast-backward"></i></a>'; pagerHtml += '<a class="disabled" id="prev' + rand + '"><i class="fa fa-backward"></i></a>'; pagerHtml += '<div class="pager-index"><b>第</b><input value="1" type="text" maxlength="4" id="index' + rand + '"><b>页</b></div>'; pagerHtml += '<a class="disabled" id="next' + rand + '"><i class="fa fa-forward"></i></a>'; pagerHtml += '<a class="disabled" id="last' + rand + '"><i class="fa fa-fast-forward"></i></a>'; pagerHtml += '<div class="pager-info2"> 共计 <span class="totalCount" id="total' + rand + '">0</span> 条记录,'; pagerHtml += '每页显示 <span class="pageSize' + rand + '">' + pageSize + '</span> 条,'; pagerHtml += '共 <span class="totalPage" id="page' + rand + '">1</span> 页,'; pagerHtml += '用时 <span class="loadTime" id="time' + rand + '">0</span>毫秒'; pagerHtml += "</div></div>"; var pagerDom = $(pagerHtml); var firstButton, prevButton, nextButton, lastButton, currentSpan, pageSizeSpan, totalCountSpan, totalPagesSpan, loadTimeSpan; // 临时变量 var tbody, pager, cbAll, checkboxes, isPager = false, totalPage = 1; // 创建table self.append(table); // 创建thead addChildrenToTable(); // 为tbody赋值 tbody = $("#tbody" + rand); var colCount = settings.columns.length + 2; tbody.html("<tr class='empty'><td colspan='" + colCount + "'>等待加载数据...</td></tr>"); // 创建分页 if (settings.pager && $("#" + settings.pager)[0]) { pager = $("#" + settings.pager); isPager = true; pager.append(pagerDom); setPagerButtonEvent(); } // 创建渲染函数 self.fun = new Function("data", renderFunString()); // 渲染第一列 self.setFirstCol = function (val) { if (settings.isMulti) { return "<td class='chk'><input type='checkbox' id='cb" + val + "' value='" + val + "'></td>" } else { return "<td class='no'>" + val + "</td>"; } } // 渲染最后一列 self.setLastCol = function () { return "<td></td>"; } // 渲染中间列 self.setCol = function (content, width, cssClass, level) { var html = "<td"; html += width ? " style='" + width + "px'" : ""; html += cssClass && cssClass != 'undefined' ? " class='" + cssClass + "'" : ""; html += ">"; if (settings.isTreeView) { html += level ? "<span class='block' style='" + 24 * level + "px'>" + (level > 0 ? "|—" : "") + "</span>" : ""; } html += content && content != "undefined" ? content : ""; html += "</td>"; return html; } // 显示指定页码的数据 self.show = function (index, type) { self.selected = []; if (!settings.apiUrl) { return; } if (!type || type.toLowerCase() != "post") { $.get(settings.apiUrl, getAjaxData(), function (data) { var source = []; if (settings.convertSource && $.isFunction(settings.convertSource)) { source = settings.convertSource(data); } else { source = data; } addRowsToTbody(source); }); } else { $.post(settings.apiUrl, getAjaxData(), function (data) { var source = []; if (settings.convertSource && $.isFunction(settings.convertSource)) { source = settings.convertSource(data); } else { source = data; } addRowsToTbody(source); }); } }; // 获取选中的ID self.getSelectedId = function () { if (self.selected.length == 0) { return null; } else { return self.selected[0]; } }; self.getSelectedIds = function () { return self.selected; }; self.clear = function () { self.selected = []; $("tr", "#myList" + rand).removeClass("selected"); $(":checkbox", "#myList" + rand).prop("checked", false); }; // 为table内的元素绑定事件 tbody.on("click", "tr", function () { if ($(this).hasClass("empty")) return; $("tr", tbody).removeClass("selected").find(":checkbox").prop("checked", false); $(this).addClass("selected").find(":checkbox").prop("checked", true); $("#cbAll" + rand).prop("checked", false); self.selected = [$(this).data("value")]; }); // 绑定复选框点击事件 if (settings.isMulti) { cbAll = $("#cbAll" + rand); cbAll.on("click", function () { if (!checkboxes) checkboxes = $(":checkbox", tbody); if ($(this).prop("checked")) { self.selected = []; $.each(checkboxes, function (idx, item) { $(this).prop("checked", true); self.selected.push($(this).val()); var tr = $("#tr" + $(item).val()); if (!tr.hasClass("selected")) tr.addClass("selected"); }); } else { checkboxes.prop("checked", false); $("tr", tbody).removeClass("selected"); self.selected = []; } }); tbody.on("click", ":checkbox", function (event) { event.stopPropagation(); if ($(this).prop("checked")) { $("#tr" + $(this).val()).addClass("selected"); } else { $("#tr" + $(this).val()).removeClass("selected"); } if (!checkboxes) { checkboxes = $(":checkbox", tbody); } self.selected = []; var unCheckedCount = 0; $.each(checkboxes, function () { if ($(this).prop("checked")) { self.selected.push($(this).val()); } else { unCheckedCount++; } }); if (unCheckedCount > 0) { cbAll.prop("checked", false); } else { cbAll.prop("checked", true); } }); } // 返回渲染函数的程序体 function renderFunString() { var funString = "var self = this; var html = ''; $.each(data, function(idx, item) { var val = "; funString += (settings.valueColumn ? "item." + settings.valueColumn : "idx"); funString += "; html += '<tr id="tr' + val + '" data-value="' + val + '">'; html += self.setFirstCol(val);"; var level = null; if (settings.levelColumn) { level = settings.levelColumn; } $.each(settings.columns, function (idx, item) { if (item.func) { funString += " html += self.setCol( " + item.func + "(item), " + item.width + ", '" + item.cssClass + "'" + (level ? " , item." + level : "") + " );"; } else { funString += " html += self.setCol( item." + item.column + ", " + item.width + ", '" + item.cssClass + "'" + (level ? " , item." + level : "") + " );"; } }); funString += " html += self.setLastCol(); html += '</tr>'; idx++; }); return html;"; return funString; } // 将数据生成html,并插入到tbody中 function addRowsToTbody(data) { if (data && data.body && data.body.length > 0) { var html = self.fun(data.body); tbody.html(html); if (isPager) { setPagerButton(pageSize, pageindex, data.totalCount, new Date().getTime()); } } else { var colCount = settings.columns.length + 2; tbody.html("<tr class='empty'><td colspan='" + colCount + "'>请求的数据为空</td></tr>"); } } // 创建table function addChildrenToTable() { var body = "<thead>" if (settings.columns) { if (settings.isMulti) { body += "<th class='chk'><input type='checkbox' id='cbAll" + rand + "' /></th>"; } else { body += "<th class='no'></th>"; } $.each(settings.columns, function (idx, col) { body += "<th>" + col.title + "</th>"; }); body += "<th></th>"; body += "</thead><tbody id='tbody" + rand + "'></tbody>"; table.append($(body)); } } // 绑定分页按钮的点击事件 function setPagerButtonEvent() { firstButton = $("#first" + rand); prevButton = $("#prev" + rand); nextButton = $("#next" + rand); lastButton = $("#last" + rand); currentSpan = $("#index" + rand); pageSizeSpan = $("#size" + rand);; totalCountSpan = $("#total" + rand);; totalPagesSpan = $("#page" + rand);; loadTimeSpan = $("#time" + rand);; firstButton.on("click", function () { if (!$(this).hasClass("disabled")) { pageindex = 1; self.show(); } }); prevButton.on("click", function () { if (!$(this).hasClass("disabled")) { pageindex -= 1; pageindex = pageindex <= 0 ? 1 : pageindex; self.show(); } }); nextButton.on("click", function () { if (!$(this).hasClass("disabled")) { pageindex += 1; self.show(); } }); lastButton.on("click", function () { if (!$(this).hasClass("disabled")) { pageindex = totalPage; self.show(); } }); currentSpan.on("change", function () { var nc = Number($(this).val()); if (nc && nc <= totalPage && nc > 0) { pageindex = nc; self.show(); } else { $(this).val(pageindex); } }); } // 配置 Pager 按钮 function setPagerButton(size, index, total, start) { if (total == 0) { pager.hide(); } else { pager.show(); } // 总页数 var pages = Math.ceil(total / size); pages = (pages == 0 ? 1 : pages); totalPage = pages; if (pages == 1) { if (!firstButton.hasClass("disabled")) { firstButton.addClass("disabled"); } if (!prevButton.hasClass("disabled")) { prevButton.addClass("disabled"); } if (!nextButton.hasClass("disabled")) { nextButton.addClass("disabled"); } if (!lastButton.hasClass("disabled")) { lastButton.addClass("disabled"); } } else { if (index == 1) { if (!firstButton.hasClass("disabled")) { firstButton.addClass("disabled"); } if (!prevButton.hasClass("disabled")) { prevButton.addClass("disabled"); } } else { if (firstButton.hasClass("disabled")) { firstButton.removeClass("disabled"); } if (prevButton.hasClass("disabled")) { prevButton.removeClass("disabled"); } } currentSpan.val(index); if (index == pages) { if (!nextButton.hasClass("disabled")) { nextButton.addClass("disabled"); } if (!lastButton.hasClass("disabled")) { lastButton.addClass("disabled"); } } else { if (nextButton.hasClass("disabled")) { nextButton.removeClass("disabled"); } if (lastButton.hasClass("disabled")) { lastButton.removeClass("disabled"); } } } totalCountSpan.text(total); pageSizeSpan.text(this.pageSize); totalPagesSpan.text(pages); loadTimeSpan.text((new Date().getTime() - start)); } // 获取ajax的查询参数 function getAjaxData() { var param; if ($.isFunction(settings.getSearchData)) { param = settings.getSearchData(); } if (isPager) { if ($.isArray(param)) { // $("form").serializationArray() param.push({ "name": "pageSize", "value": pageSize }); param.push({ "name": "pageIndex", "value": pageindex }); } else if ($.isPlainObject(param)) { // 自定义查询对象 $.extend(true, param, { "pageSize": pageSize, "pageIndex": pageindex }); } else { // $("form").serialization() param = (param ? param + "&" : "") + "pageSize=" + pageSize + "&pageIndex=" + pageindex; } } return !!param ? param : {}; } return self; } })(jQuery); /* *使用范例: *==========================数据格式============================ { "code": 200, "describe": "", "totalCount": 3, "body": [ { "no": 1, "name": "王五", "family": { "father": "王老五", "mother": "陈静蓉" } }, { "no": 2, "name": "张三", "family": { "father": "张作霖", "mother": "李培芳" } }, { "no": 3, "name": "李四", "family": { "father": "李宗仁", "mother": "江少芬" } } ] } *==========================页面调用============================ <script src="jquery-1.10.2.js"></script> <script src="myGrid.js"></script> <script> var list = $("#list").myGrid({ apiUrl: "data.json", isMulti: false, isTree: true, cols: [ { col: "no", 120, title: "编号", cssClass: "chk", level: 0 }, { col: "name", 120, title: "姓名", level: 0 }, { col: "family.father", 120, title: "父亲", level: 1, func: "addFix" } ], valueCol: "no", pager: "pager", pageSize: 2, cssClass: "default-list-table", convertSource: function(data) { return data; } }); // 这个是用来转换数据的方法 function addFix(obj) { return "000" + obj; } list.show(); </script>
一个修改的操作: