为了提升性能与用户体验,可以列表界面加载数据时,可以采用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()"> </li>
                <li class="right oarequirement" style="cursor: pointer" onclick="xx"> </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> </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;
}