一般动作
登录
Wiki: BROPEN ▼: Document Index
»
Space: FAQ ▼: Document Index
»
Page: 如何开发 AJAX 列表（list）页面、如何开发多页签的列表页面
搜索
zh
页面动作
导出 ▼: 以PDF格式导出 以RTF格式导出 以HTML格式导出
更多动作 ▼: 打印预览 察看源码
欢迎进入Wiki » FAQ » 如何开发 AJAX 列表（list）页面、如何开发多页签的列表页面?

# 如何开发 AJAX 列表（list）页面、如何开发多页签的列表页面?

在2014-11-19 18:04上被李小翔修改
评论 (0) · 附件 (1) · 记录 · 信息

为了提升性能与用户体验，可以列表界面加载数据时，可以采用ajax方式。

在 BroFramework 中，通过查询框架，可以很轻松的实现 ajax 加载的需求，示例如下。

- 修改 list.gsp，给 s:form 标签添加属性 ajax，如：
  <s:form action="${actionName}" ajax="true">
- 将 list.gsp 中的 <div class="list"> 和 <div class="paginateButtons">两端代码，剪切出来粘贴到一个新的 _list.gsp 文件中
- 修改 list.gsp ，在上述两个 div 原本所在位置插入标签 g:render，如：
  <g:render template="/foo/bar/list" plugin="bro-foobar" />
  注：如果是插件开发，则需要 plugin 属性，否则不需要
- 修改控制器中的 list 方法，将最后的 render 修改为：
  if ( isAjax() ) {
  render ( view:"${VIEW_PATH}_list", model:[......] )
  } else {
  render ( view:"${VIEW_PATH}list", model:[......] )
  }
  也就是判断 ajax 请求时，仅渲染 _list.gsp

经过上述改造后，页面加载的时候会一次性渲染完整的第一页，但是点击翻页、表头排序、查询时，都会自动采用 ajax 方式加载列表了。

此外，某些场景下，列表页面中需要加载多个页签，并且每个页签下显示一个独立的列表，但是页签顶部有一个统一的查询栏，效果如下图所示：

这个需求开发略微复杂，以上述截图中的待办列表为例。

改造 list.gsp，添加页签：

<%-- s:form 标签添加 ajax 属性--%>
<s:form action="${actionName}" ajax="true"><s:setup/>
<div class="body">
<h1><g:message code="bropen.workbench.task.Todo"/></h1>
<g:if test="${flash.message}"><div class="message">${flash.message}</div></g:if>
<g:if test="${flash.errors?.hasErrors()}"><div class="errors"><g:renderErrors bean="${flash.errors}" as="list" /></div></g:if>
<%-- 唯一的搜索栏 --%>
<s:filter>
<div class="right">
....
</div>
</s:filter>
<%-- 生成页签 --%>
<script>$j(function(){ $j("#tabs").tabs() })</script>
<div id="tabs">
<ul>
<g:each in="${lists}" var="m" status="i">
<li><a href="#tab-${i}">${m.name}(${m.total})</a></li>
</g:each>
<%-- 创建两个右对齐的按钮型页签 --%>
<li class="right refresh" style="cursor: pointer" onclick="reloadTasks()">&nbsp;</li>
<li class="right oarequirement" style="cursor: pointer" onclick="xx">&nbsp;</li>
</ul>
<%-- 遍历多个页签，生成列表及其页签容器，注意容器设置了一个css class，生成列表时，model中增加了一个参数 tabId 用于标识哪个页签、并用于控制器判断加载更新哪个页签的数据 --%>
<g:each in="${lists}" var="m" status="i">
<div id="tab-${i}" class="tabId${m.tabId}">
<g:render template="/foo/bar/list" plugin="bro-foobar" model="[list: m.list, total: m.total, tabId: m.tabId]" />
</div>
</g:each>
</div>
</div>
</s:form>

子列表页面 _list.gsp 如下：

<div class="list">
<table class="fixedEllipsis">
<%-- 设置表头，注意参数 tabId --%>
<thead>
<tr>
<g:sortableColumn params="[tabId: tabId]" property="security" title="${message(code:'bropen.workbench.task.Task.security')}" width="30" />
......
</tr>
</thead>
<tbody>
<g:each in="${list}" status="i" var="task">
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">
<td><a href="${task.url}" target="_blank">${task.docNumber}</a></td>
......
</tr>
</g:each>
<g:each in="${(total != null && setting('bropen.framework.pagination.default')-list.size()) ? (list.size()..setting('bropen.framework.pagination.default')-1) : null}" var="i">
<tr class="${(i % 2) == 0 ? 'odd' : 'even'}">${"<td>&nbsp;</td>".multiply(10)}</tr>
</g:each>
</tbody>
</table>
</div>
<%-- 设置翻页栏，注意参数 tabId --%>
<g:if test="${total}"><div class="paginateButtons"><g:paginate total="${total}" params="[tabId: tabId]" /></div></g:if>

控制器操作如下：

// ajax 加载一个页签
if ( params.tabId ) {
def list = ...
def total = ...
return render ( view:"${VIEW_PATH}_list", model:[list: list, total: total, tabId: params.tabId] )
}
// 第一次加载或搜索，计算多个页签
else {
def lists = []
for ( x in xx ) {
lists << [tabId: 标签标识, name: 标签名，list: 列表数据, total: N]
}
render ( view:"${VIEW_PATH}list", model:[lists: lists] )
}

这样改造后，任意一个列表翻页、排序时，会覆盖其他列表，因此，每次翻页或排序时，需要指定本次操作的容器（某个页签），这里通过查询框架的事件来实现；此外，由于只有一个查询栏，点搜索按钮时也会自动用 ajax 方式查询，得到的结果也不是想要的，需要改造成搜索时不通过 ajax、直接加载整个页面，此时同样可以通过查询框架的事件来执行。如下面的 js 示例代码，贴到 list.gsp 中：

1、在页签中翻页、排序时（我们在 _list.gsp 中设置了一个 tabId 的额外参数，通过它可以判断），给 s:form 设置一个 ajaxContainer 的表单参数，指向 list.gsp 中的页签 div 容器

2、搜索操作时，删除 s:form 标签的 ajax 属性，实现非 ajax 查询

/**
* 多页签时，用 ajax 方式提交翻页、排序，非ajax方式提交搜索
*/
search.callbackBeforeSubmit = function( actionUrl ) {
var form = $j(search.form());
// 如果是多页签
if ( form.attr("ajax") && $j("#tabs").length ) {
if ( actionUrl.indexOf("tabId") > 0 ) {
// 翻页、排序时，设置列表容器
var container = actionUrl.replace(/.+(tabId=[^&]+)(&.+)?/, "$1").replace("=", "");
form.attr("ajaxContainer", "." + container);
} else {
// 点搜索按钮时，取消ajax
form.removeAttr("ajax");
}
}
}

最后，默认的 jQuery UI 的页签组件显示效果适用于表单，在列表界面中不太好看，因此，需要设置一些 css 样式，上图中的样式示例如下：

/** 页签高宽 */
.ui-tabs .ui-tabs-panel {
padding: 0px;
}
.ui-tabs .ui-tabs-nav {
padding-top: 0px;
}
.ui-tabs .ui-tabs-nav li,
.ui-tabs .ui-tabs-nav li.ui-tabs-active {
cursor: pointer;
border: none;
margin-right: 15px;
margin-bottom: 2px;
padding-bottom: 1px;
text-align: center; /** 文字居中 */
height: 20px; /** 和图片高宽吻合 */
width: 99px;
}
.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
float: none; /** 文字居中 */
}
/** 页签背景 */
.ui-tabs .ui-corner-top {
border-top-right-radius: 0px;
border-top-left-radius: 0px;
}
.ui-tabs .ui-widget-header {
background: url("tab-background.gif") repeat-x;
border: none;
}
.ui-tabs .ui-state-default {
background: url("tab.jpg") repeat-x scroll 50% 50% #ffffff;
}
.ui-tabs .ui-state-active {
background: url("tab-active.jpg") repeat-x scroll 50% 50% #f1f1f1;
}
/** 页签字体 */
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited {
color: #333;
line-height: 2;
}
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited {
color: #06C;
line-height: 2;
font-weight: bold;
}
/** 右侧页签 */
.ui-tabs .ui-tabs-nav li.right {
float: right;
margin-right: 0px;
margin-left: 15px;
}
/** 右侧页签按钮示例 */
.ui-tabs .ui-tabs-nav li.oarequirement {
background: url("sample/oarequirement.gif") no-repeat scroll 50% 50% #ffffff;
width: 63px;
}
.ui-tabs .ui-tabs-nav li.refresh {
background: url("sample/refresh.gif") no-repeat scroll 50% 50% #ffffff;
width: 63px;
}
/** 搜索栏 */
.searchfilter {
border: none;
}
标签： BroFramework search ajax
在2014-11-19 17:53上被李小翔创建

- 评论 （0）
- 附件 （1）
- 记录
- 信息

# 快速链接

- 首页
- 新闻动态
- FAQ

Copyright © 2013 北京博瑞开源软件有限公司
京ICP备12048974号