victor_armin
作者victor_armin·2012-09-10 23:59
其它·BJ-FANUC

【实战】xGrid,来自jQuery的神器(四)简单的客户信息系统

字数 44072阅读 7842评论 3赞 4
非常悲剧,刚才写了一点后,点删除键,结果后退了...悲剧啊...只能重头写了...

作为实战大结局,一上来我们就做一下弊,看看这节内容最后做出的这个简单的客户信息记录系统最终什么样子的?

和xGrid相比,我们的客户信息记录系统,左下角了3个icon,它们分别代表添加,编辑和删除,我们用一连贯的图片向大家展示一下,这些按钮如何工作的:

2.JPG
3.JPG
4.JPG
5.jpg
6.jpg
7.jpg

OK,就像我说的一样,这个系统真的很简单的,但是看起来也还不错,这是因为它基于一个不错的CC,如果要说xGrid是否很简单的话,我觉得这个问题就 不是那么肯定了,而如果再追问,xGrid的支持框架jqGrid是否简单的话,我觉得可能答案就是,不是那么简单了,所以我们这个简单的应用很简单,但 它的后面的支持却很强大,另外它是一个实实在在地在Victor的公司部 署实施的一个简单应用,尽管使用这个应用的只是一个单独的部门(这个部门的客户信息和SAP里的不一样,而他们又不想让其他部门看到他们的客户信息,因此 提出了要做一个共享的access,Victor了解到之后,通过简单地解释,对方就欣然接受使用一个可以浏览器直接访问,并且能够提供多种查询功能的小 系统,这就是我们这个Demo的由来),尽管这个应用看起来实在是太简单了,甚至有点简陋,但其实业务需求本身就是很简单,我们不得不承认,有时候业务部 门想要的就是一个这么简单,但却好用的小系统。

至于这个小系统有多简单,呵呵呵,看看下面的这些设计元素的截图吧:
8.jpg
9.jpg
10.jpg

去掉图片和JS,整个NSF的Lotus设计元素是7个,1个forms,1个views,1个我们的主角ccxGrid,4个XPages,4个XPages中其实xDelete和xEdit还可以合成1个,而它们俩和xJsonCustom共3个XPages其实是XAgent,因此实际上只有1张XPages,而有3个XAgent(其实,可以简化为2个,而至于什么是XAgent请温习上一节的内容)

forms和views都是简单至极的设计,没有任何花哨的东西,非常常规的设计方式,我们直接跳过,反正最后会给大家一个Demo的NSF,而 ccxGrid和之前我们说的xGrid.nsf里的ccxGrid稍有不同,Victor基于jqGrid做了一些扩展,我们现在放出代码,看看到底多 出来的3个操作按钮是哪里来的?
$().ready(function(){
                                        var lastsel;
                                        jQuery("#listxGrid").jqGrid({
                                                          url:'#{javascript:compositeData.url}',
                                                        datatype: "json",
                                                           colNames:#{javascript:compositeData.colNames},
                                                           colModel:#{javascript:compositeData.colModel},
                                                        jsonReader: {
                                                            repeatitems: false,
                                                            id: '#{javascript:compositeData.colID}',
                                                            root: function (obj) {
                                                                if ($.isArray(obj)) return obj;
                                                                if ($.isArray(obj.items)) return obj.items;
                                                                return [];
                                                            },
                                                            page: function () { return 1; },
                                                            total: function () { return 1; },
                                                            records: function (obj) {
                                                                if ($.isArray(obj))        return obj.length;
                                                                if ($.isArray(obj.items))        return obj.items.length;
                                                                return 0;
                                                            }
                                                        },
                                                        gridview: true,
                                        loadonce: #{javascript:compositeData.loadOnce},
                                        ignoreCase: #{javascript:compositeData.ignoreCase},
                                                        rowNum: #{javascript:compositeData.rowNum},
                                        rowList: #{javascript:compositeData.rowList},
                                        rownumbers: #{javascript:compositeData.showRowNumbers},
                                        height: #{javascript:compositeData.height},
                                        caption: '#{javascript:compositeData.caption}',
                                        pager: '#pagerxGrid',
                                        viewrecords: true,
                                        emptyrecords: '#{javascript:compositeData.emptyRecords}',
                                        sortable:#{javascript:compositeData.allowReorder},
                                        sortname: '@position'
                                        
                         
                                                         
                                        });
                                        if (#{javascript:compositeData.showFilterToolbar} == true){
                                                jQuery("#listxGrid").filterToolbar({stringResult: false, defaultSearch: 'cn', searchOnEnter: false});
                                                $("#listxGrid")[0].toggleToolbar();
                                        }
                                        jQuery("#listxGrid").jqGrid('navGrid','#pagerxGrid',{edit:true, add:true, del:true,refresh:true,refreshstate:'current',
                                       
                                        beforeRefresh: function() {
                                           $("#listxGrid").jqGrid('setGridParam',{datatype:'json'}).trigger('reloadGrid');
                                        }},                       
                                                {url: '#{javascript:compositeData.editUrl}'}, // default settings for edit
                                                   {url: '#{javascript:compositeData.addUrl}'}, // default settings for add
                                                   {url: '#{javascript:compositeData.deleteUrl}'}, // delete
                                                   {closeOnEscape: true, multipleSearch: true, closeAfterSearch: true, showQuery: false, refreshstate:'current'} // search options                                                  
                                           );
                                          
                                           jQuery.extend(jQuery.jgrid.edit, {
                                             // some other settings
                                            closeAfterAdd: true,
                                            closeAfterEdit: true,
                                            recreateForm: true,
                                            position: "last",
                                            reloadAfterSubmit: true,
                                            afterSubmit: function(response) {
                                                    if (response.responseText == "") {
                                                            $("#listxGrid").jqGrid('setGridParam',{datatype:'json'}).trigger('reloadGrid');
                                                                return [true, response.responseText, false]
                                                        }else {
                                                                return [false, "提交失败,请您重新登录系统!"]
                                                        }
                                                          
                                                }
                                        });
                                          
                                           if (#{javascript:compositeData.showFilterToolbar} == true){
                                                jQuery("#listxGrid").filterToolbar({stringResult: false, defaultSearch: 'cn', searchOnEnter: false});
                                                jQuery("#listxGrid").jqGrid('navButtonAdd', "#pagerxGrid",{
                                                           caption: "",
                                                           title: "快速查询",
                                                           buttonicon: 'ui-icon-pin-s',
                                                      onClickButton: function () {
                                                              $("#listxGrid")[0].toggleToolbar()
                                                      }
                                              });
                                        }
                                           jQuery("#listxGrid").jqGrid('navButtonAdd','#pagerxGrid',{
                                                   caption: "",
                                                   title: "筛选列",
                                                   onClickButton : function (){
                                                           jQuery("#listxGrid").jqGrid('columnChooser');
                                                   }
                                           });
                                        function editLinkFmatter(cellvalue, options, rowObject) {
                                             return "<a target='#{javascript:compositeData.target}' href='./#{javascript:compositeData.xpName}?documentId=" + rowObject["@unid"] +
                                                             "&action=#{javascript:compositeData.action}' class='doclink'>" + cellvalue + "";
                                        }

而Victor做的扩展部分主要是这些,其他的和xGrid.nsf里的没有太大区别,正如大家看到的,Victor在第一行使用edit:true, add:true, del:true开启了添加、编辑和删除按钮,这个是jqGrid的标准API,更多的API内容请参考:http://www.trirand.com/blog/jqgrid/jqgrid.html,另外请注意,Victor下载了最新的jqGrid.js的代码,并替换了自己应用中的该js文件,和xGrid相比,更新后的jqGrid有更强大的功能,比如多级分类,相关的内容同样和可以查询jqGrid的API。
在第二行中,Victor在beforeRefresh事件中中定义了一个简单那的函数,就是reload一下当前数据,这个作用在于当你开启数据的加载方式loadonce的值为true时,仍保证刷新按钮时好用的,且可以从数据库中获取最新的数据,为什么把loadonce设置为ture?理由有很多,比如性能啊,特别是查询性能啊,但最关键的是,xGrid或者说是jqGrid提供了前台的查询搜索功能,这个功能已经非常强大了(远远比Notes的搜索强大),基本上可以不用自己再开发搜索的代码,而且由于是一次加载完之后不用再和服务器交 互,所以查询的速度非常快(远远比SQL快),因此这个功能在我们这个简单的系统中,实在没有放弃的理由,但不放弃查询的功能,就必须设置 loadonce为true(如果哪位能设置为false,还保留前台查询功能的话,请一定联系我,谢谢),而这样一来首当其冲的问题就是,除非你在浏览 器里重新打开或者刷新.xsp页面,否则任何按钮都不会讲后台的数据重新取到前台来,包括刷新按钮,表现是按下去后根本没有反应,而我们在 beforeRefresh中的代码就是为了解决这个问题,让数据强行reload。
接下来的三行代码非常重要,它们分别是一个url,这3个url对应的分别是编辑、添加、删除会执行什么操作,在前一节中,Victor已经说明了如何结 合XAgent这个设计理念来完成你想要执行的操作,不明白如何让url执行XAgent操作的同学可以温习一下上一节,或者你有一些开发经验的话,也应 该了解agent其实也是可以通过url呼叫的的,道理差不多,只不过XAgent强大和方便了更多。
jQuery("#listxGrid").jqGrid('navGrid','#pagerxGrid',{edit:true, add:true, del:true,refresh:true,refreshstate:'current',
                                       
                                        beforeRefresh: function() {
                                           $("#listxGrid").jqGrid('setGridParam',{datatype:'json'}).trigger('reloadGrid');
                                        }},                       
                                                {url: '#{javascript:compositeData.editUrl}'}, // default settings for edit
                                                   {url: '#{javascript:compositeData.addUrl}'}, // default settings for add
                                                   {url: '#{javascript:compositeData.deleteUrl}'}, // delete
                                                   {closeOnEscape: true, multipleSearch: true, closeAfterSearch: true, showQuery: false, refreshstate:'current'} // search options                                                  
                                           );
                                          
                                           jQuery.extend(jQuery.jgrid.edit, {
                                             // some other settings
                                            closeAfterAdd: true,
                                            closeAfterEdit: true,
                                            recreateForm: true,
                                            position: "last",
                                            reloadAfterSubmit: true,
                                            afterSubmit: function(response) {
                                                    if (response.responseText == "") {
                                                            $("#listxGrid").jqGrid('setGridParam',{datatype:'json'}).trigger('reloadGrid');
                                                                return [true, response.responseText, false]
                                                        }else {
                                                                return [false, "提交失败,请您重新登录系统!"]
                                                        }
                                                          
                                                }
                                        });
                                          
                                           if (#{javascript:compositeData.showFilterToolbar} == true){
                                                jQuery("#listxGrid").filterToolbar({stringResult: false, defaultSearch: 'cn', searchOnEnter: false});
                                                jQuery("#listxGrid").jqGrid('navButtonAdd', "#pagerxGrid",{
                                                           caption: "",
                                                           title: "快速查询",
                                                           buttonicon: 'ui-icon-pin-s',
                                                      onClickButton: function () {
                                                              $("#listxGrid")[0].toggleToolbar()
                                                      }
                                              });
                                        }
                                           jQuery("#listxGrid").jqGrid('navButtonAdd','#pagerxGrid',{
                                                   caption: "",
                                                   title: "筛选列",
                                                   onClickButton : function (){
                                                           jQuery("#listxGrid").jqGrid('columnChooser');
                                                   }
                                           });

在这段代码里还有一个重点是这部分

                           jQuery.extend(jQuery.jgrid.edit, {
                                             // some other settings
                                            closeAfterAdd: true,
                                            closeAfterEdit: true,
                                            recreateForm: true,
                                            position: "last",
                                            reloadAfterSubmit: true,
                                            afterSubmit: function(response) {
                                                    if (response.responseText == "") {
                                                            $("#listxGrid").jqGrid('setGridParam',{datatype:'json'}).trigger('reloadGrid');
                                                                return [true, response.responseText, false]
                                                        }else {
                                                                return [false, "提交失败,请您重新登录系统!"]
                                                        }
                                                          
                                                }
                                        });这是jqGrid的一种扩展方式,稍微设计到一些jQuery的语法问题,不过不是重点,还是那句话,在你没有功夫研究jQuery或者 jqGrid之前,你知道可以这么用就可以了,你可以看到Victor对多个方法进行了扩展的设置,但重点是最后那个afterSubmit。

在做完这个应用之后,Victor遇到了一个麻烦的问题,就如我们前面说的,前台查询功能导致我必须开启loadonce为true,这不仅导致原生的刷新按钮不好使了(稍靠前的位置里Victor介绍了如何解决这个问题),最麻烦还是,当用户提 交了一个操作后(添加,编辑,删除中的任何一种),没法即可刷新当前的数据,这个问题最严重的一种后果是,一个人录了100条记录,然后其实他已经登录超 时了,但因为没有重新获取后台数据,所以除非他用浏览器刷新一下url,否则他自己还是接着录入,那第二天他肯定是拿着板砖来砸Victor的,因为他前 一天的工作等于全白做了,这种事情绝对是不能容忍的,所以在用户提交后,即刻reload数据的这个功能必须有!同理可以推出类似于一堆数据已经删除,但 有人因为没有刷新而继续在上面修改等等...

因此在afterSubmit之后,我们获取一下返回的response里是否有内容,一般有内容的话通常就是我们熟悉的404,405等等,不写额外的 代码的话,那返回的就是什么都没有,接下来Victor会立马告诉大家,如何通过XAgent操作request和response的,不用急,现在我们 还是关注afterSubmti这块,我们可以看到里面是一个分支,如果response为空,则reload当前页面,并且return [true, response.responseText, false] ,否则就return [false, "提交失败,请您重新登录系统!"] ,在afterSubmit里return什么值是jqGrid的API的事,你可以查询一下API,会比我说的详细,在这里的话,说得白点就是一个是成 功提交并reload数据,另一个是返回错误提示"提交失败,请您重新登录系统!"。实际效果的话,大家下载了Demo库之后,可以试一下,比较方便的方 式是吧登录超时时间设短点,然后等用户超时后,添加或修改一条数据试试就能看到报错效果的提示了。

Victor不准备讲更多的如何在XPages中设置ccxGrid的属性,从而满足自己的业务的问题,也不再讲一遍如何从视图里获取值并生成一个 JSON数据返回,这些是上节和上上节的内容,请大家已经在看完之前的内容后再来看本节的内容,这样才是最节省时间的办法。(由于Victor的这个业务 不可能出现大量数据条目,所以Victor发布JSON数据的时候,并没有采取分页的方式,有兴趣或有需求的同学可以试试,并发布到论坛上 来,Victor会给与金币奖励的,算是留给大家的一个小实验吧,如果确实没有人做的话,Victor过一段时间也会再来完善这块内容)

我们接下来以xEditCustomer.xsp为例,来讲讲如何在XAgent中操作response和request,以及如何结合domino的方 法来实现添加或者编辑的效果,我们先看看xEditCustomer.xsp的核心代码,这些代码是在这个XPages的 beforeRenderResponse事件中的,为什么加在这里同样不重要,知道XAgent的代码可以加在这里就可以了...(Victor发现自 己老是说这种句式,希望大家没有烦,但事实情况就是这样,在初始阶段如果你花大力气去弄明白每一件事情的话,可能会严重影响进度,而且没有一定的经验积累,一些内容你并不可能真正的理解)
try {

        var exCon:javax.faces.context.ExternalContext = facesContext.getExternalContext();
        var response:com.ibm.xsp.webapp.XspHttpServletResponse = exCon.getResponse();
        var request:com.sun.faces.context.MyHttpServletRequestWrapper = exCon.getRequest();
        var map:java.util.Map = request.getParameterMap();
        //print(map);这句在Debug的时候可以不注释掉
        var it:java.util.Iterator = map.entrySet().iterator();
        if(map.get("@unid").toString().length>0){
                var doc:NotesDocument = database.getDocumentByUNID(map.get("@unid").toString());
        }else{
                var doc:NotesDocument = database.createDocument();
                doc.replaceItemValue("form","fmCustom");
        }
       
        var $CustomName = map.get("$CustomName").toString();
    var $CustomType = map.get("$CustomType").toString();
    var $CustomArea = map.get("$CustomArea").toString();
    var $EndUser  = map.get("$EndUser").toString();
    var $ContactName = map.get("$ContactName").toString();
    var $ContactPhone = map.get("$ContactPhone").toString();
    var $ContactFax = map.get("$ContactFax").toString();
    var $Remark = map.get("$Remark").toString();

    doc.replaceItemValue("M_CustomName",$CustomName);
    doc.replaceItemValue("M_CustomType",$CustomType);
    doc.replaceItemValue("M_CustomArea",$CustomArea);
    doc.replaceItemValue("M_EndUser",$EndUser);
    doc.replaceItemValue("M_ContactName",$ContactName);
    doc.replaceItemValue("M_ContactPhone",$ContactPhone);
    doc.replaceItemValue("M_ContactFax",$ContactFax);
    doc.replaceItemValue("M_Remark",$Remark);
   
        doc.computeWithForm(false,false);
        doc.save(true,false);
       
        facesContext.responseComplete()
       
} catch (e) {
        print ("error: " + e.toString() );
}
先说说前4句:
        var exCon:javax.faces.context.ExternalContext = facesContext.getExternalContext();
        var response:com.ibm.xsp.webapp.XspHttpServletResponse = exCon.getResponse();
        var request:com.sun.faces.context.MyHttpServletRequestWrapper = exCon.getRequest();
        var map:java.util.Map = request.getParameterMap();
        //print(map);这句在Debug的时候可以不注释掉
第3节的内容中我们说了如何取得exCon和response,并保证在第4节中会告诉大家如何使用request,好了,现在Victor兑现了,我们 可以request是想http服务器发出的申请,而response是http服务器进行的反馈,而 request.getParameterMap()里都有什么?你不如print一下试试?保证会让你满意,关于这四句的底层理论,你可以通过Java Servlet或JSF学习到,有兴趣的同学可以了解一下,但就一般的XPages开发中的XAgent来说,有这4句作为开头,基本大多数的任务你都能 完成了,总之print一下map,然后到domino的控制台看一下print出来的内容,这样你自己都能写出接下来的代码,并且可能写得更好:
var it:java.util.Iterator = map.entrySet().iterator();
        if(map.get("@unid").toString().length>0){
                var doc:NotesDocument = database.getDocumentByUNID(map.get("@unid").toString());
        }else{
                var doc:NotesDocument = database.createDocument();
                doc.replaceItemValue("form","fmCustom");
        }
       
        var $CustomName = map.get("$CustomName").toString();
    var $CustomType = map.get("$CustomType").toString();
    var $CustomArea = map.get("$CustomArea").toString();
    var $EndUser  = map.get("$EndUser").toString();
    var $ContactName = map.get("$ContactName").toString();
    var $ContactPhone = map.get("$ContactPhone").toString();
    var $ContactFax = map.get("$ContactFax").toString();
    var $Remark = map.get("$Remark").toString();

    doc.replaceItemValue("M_CustomName",$CustomName);
    doc.replaceItemValue("M_CustomType",$CustomType);
    doc.replaceItemValue("M_CustomArea",$CustomArea);
    doc.replaceItemValue("M_EndUser",$EndUser);
    doc.replaceItemValue("M_ContactName",$ContactName);
    doc.replaceItemValue("M_ContactPhone",$ContactPhone);
    doc.replaceItemValue("M_ContactFax",$ContactFax);
    doc.replaceItemValue("M_Remark",$Remark);
   
        doc.computeWithForm(false,false);
        doc.save(true,false);
       
        facesContext.responseComplete()
这段代码里我们对map进行了遍历,并根据遍历的结果判断了是一个添加操作还是一个编辑操作(判断方法并不是最好的,在print了一下map后 Victor发现有更好的方式,但是当时系统已经上线了,所以就没改了,但是不知道你发现了这个更好的方式了吗?提示1,通过该方法可以把delete操 作也合并进来,提示2,注意map里的一个值...),之后我们获取request传递过来的所有值,并对应到我们客户信息表单上去,按域录入,然后compute一下,然后保存,最后告诉facesContext,response完成了,OK,代码就这么多,一点都不难对不对?删除操作更简单,这个地方Victor不讲了。

最后,在Victor准备上传实战demo的NSF时发现一个很严肃的问题,由于当前这个NSF里面的签名会泄露公司的信息,因此Victor必须明天到 公司用另外一台机器重新签名后,才能给大家传上来,大家安心,Victor说了会共享那肯定就会共享,明天一上班就给大家传上来。

好了,到此为止,随着这个简单的客户信息录入系统的开发和介绍完毕,我们的实战内容《xGrid,来自jQuery的神器》就到此为止了,Victor的 文字功底不是很好,所以可能无法将自己所想讲的清晰完整地全部说给大家听,但通过这个系列4节的内容,Victor相信一方面能够为大家介绍到一些 XPages以及相关Web技术的 概念,特别是如何在XPages中结合使用这些Web技术,另一方面,既然是实战,最后也通过一个共享的demo给大家抛一块砖,用一个实际业务问题的解 决来说明xGrid这个控件的确很有用处,如果是以前的传统开发方式,不使用jqGrid或者说是xGrid的话,完成同样的功能将是一个非常耗时的问 题,搜索的功能无法这样强大,性能无法这样出色,界面无法做到如此顺眼,无法兼容所有的浏览器,最关键的是,无法让这个这么费力开发出来的应用,被快速地 使用到另外一个业务上,而这些正是一个简单的demo和一个可以发布的应用之间的无法跨越的那种区别!这种区别不是靠1-2个人的付出就可以解决的,因此 Victor提出了这种解决思路:使用现有的Web技术,特别是现有插件来改为自己的CC,从而解决这一系列问题,从而使得开发人员更关注于业务的实现, 而不是一些功能的实现。性能的提升和兼容性的解决,这些Lotuser们可以做一部分,但不能都是自己做,这点在Mobile支持方面显得特别突出!

最后,不得不说jqGrid以及由此产生的xGrid都是非常值得我们敬佩的设计,像两位作者致敬,并且xGrid的适用范围远远比Victor已经提到 的还要广阔,所有有兴趣的同学都可以将自己的对xGrid的扩展共享出来,Victor虽然论坛币不多,但绝对以积分形式给予鼓励!

最后的最后,谢谢每一个关注本系列的同学们,你们的支持是Victor最大的动力,让我们开始下一阶段更精彩的内容吧!

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

4

添加新评论3 条评论

dreamseaKIKdreamseaKIK其它IT无国界
2012-09-18 15:29
0.0我这些问题都解决了,原来定制控件的一开始的搭建就和普通页面的一样,甚至可以直接将普通页面做的功能组件复制过来用就行。至于jquery的引用我也从xgrid例子中找到了
victor_arminvictor_armin其它BJ-FANUC
2012-09-12 21:55
dreamseaKIK: 看完了您的4篇文章,感觉对定制控件有了初步的感觉。我看这个的目的不是为了,了解xgrid的本身,我只是想知道怎么具体的做一个定制的控件。可以从文章中借鉴很多设计思
如果是jquery资源如何引入的话,第二节主要讲的是这部分,后续的实战中主要还是根据jquery设计cc,所以具体哪里没有明白的话,可以先提出来,我后面的实战中再注意说清楚你的问题。
至于一开始控件的搭建是怎么样的这个问题,我没有太明白你说的“一开始控件的搭建”指的是什么?
dreamseaKIKdreamseaKIK其它IT无国界
2012-09-12 16:26
看完了您的4篇文章,感觉对定制控件有了初步的感觉。我看这个的目的不是为了,了解xgrid的本身,我只是想知道怎么具体的做一个定制的控件。可以从文章中借鉴很多设计思路。虽还有些不懂的地方,如如果要使用jquery的话,需要引用资源文件,但是不知道具体引用哪些。还有一开始控件的搭建是怎么样的。
Ctrl+Enter 发表

作者其他文章

相关问题

相关资料

X社区推广