jqGrid,REST,AJAX和Spring MVC集成

两年多以前,我写了一篇关于两个如何在Struts2中实现优雅的CRUD的文章。 实际上,我必须就该主题写两篇文章,因为该主题如此广泛。 今天,我采用了一套更为流行的,完善的框架和库,采用了更为轻量级的现代方法。 即,我们将在后端使用Spring MVC为资源提供REST接口,为jQuery提供出色的jqGrid插件以呈现表格网格(还有更多!),并且将使用少量JavaScript和AJAX进行所有连接。

后端实际上是该展示柜中最不有趣的部分,它可以使用能够处理RESTful请求的任何服务器端技术来实现,现在应该将JAX-RS视为该领域的标准。 我没有任何理由就选择了Spring MVC,但这对于这个任务来说也不是一个坏选择。 我们将通过REST接口公开CRUD操作; 历史上最畅销的书籍清单将是我们的领域模型(您能猜出谁登上领奖台吗?)

@Controller
@RequestMapping(value = "/book")
public class BookController {private final Map<Integer, Book> books = new ConcurrentSkipListMap<Integer, Book>();@RequestMapping(value = "/{id}", method = GET)public @ResponseBody Book read(@PathVariable("id") int id) {return books.get(id);}@RequestMapping(method = GET)public @ResponseBody Page<Book> listBooks(@RequestParam(value = "page", required = false, defaultValue = "1") int page,@RequestParam(value = "max", required = false, defaultValue = "20") int max) {final ArrayList<Book> booksList = new ArrayList<Book>(books.values());final int startIdx = (page - 1) * max;final int endIdx = Math.min(startIdx + max, books.size());return new Page<Book>(booksList.subList(startIdx, endIdx), page, max, books.size());}}

几乎没有什么需要解释的。 首先,出于这个简单展示的目的,我没有使用任何数据库,所有书籍都存储在控制器内部的内存映射中。 原谅我。 第二个问题更加微妙。 由于在如何使用RESTful Web服务处理分页方面似乎尚未达成共识 ,因此我使用了简单的查询参数。 您可能会发现它很丑陋,但是我发现滥用Accept-Ranges和Range标头以及206 HTTP响应代码更加丑陋。

最后一个值得注意的细节是Page wrapper类:

@XmlRootElement
public class Page<T> {private List<T> rows;private int page;private int max;private int total;//...}

我可以返回原始列表(或更确切地说,返回列表的请求部分),但是我还需要一种方法来向视图层提供方便的元数据(如记录总数),更不用说在编组/解组原始列表时遇到的一些困难。

现在,我们准备启动我们的应用程序并使用curl进行一些测试:

<!-- $ curl -v "http://localhost:8080/books/rest/book?page=1&max=2" --><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<page><total>43</total><page>1</page><max>3</max><rows xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="book"><author>Charles Dickens</author><available>true</available><cover>PAPERBACK</cover><id>1</id><publishedYear>1859</publishedYear><title>A Tale of Two Cities</title></rows><rows xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="book"><author>J. R. R. Tolkien</author><available>true</available><cover>HARDCOVER</cover><id>2</id><publishedYear>1954</publishedYear><title>The Lord of the Rings</title></rows><rows xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="book"><author>J. R. R. Tolkien</author><available>true</available><cover>PAPERBACK</cover><id>3</id><publishedYear>1937</publishedYear><title>The Hobbit</title></rows>
</page>

如果未指定,则响应类型默认为XML,但是如果我们将Jackson库添加到CLASSPATH,Spring会选择它并允许我们使用JSON:

// $ curl -v -H "Accept: application/json" "http://localhost:8080/books/rest/book?page=1&max=3"{"total":43,"max":3,"page":1,"rows":[{"id":1,"available":true,"author":"Charles Dickens","title":"A Tale of Two Cities","publishedYear":1859,"cover":"PAPERBACK","comments":null},{"id":2,"available":true,"author":"J. R. R. Tolkien","title":"The Lord of the Rings","publishedYear":1954,"cover":"HARDCOVER","comments":null},{"id":3,"available":true,"author":"J. R. R. Tolkien","title":"The Hobbit","publishedYear":1937,"cover":"PAPERBACK","comments":null}]
}

很好,现在我们可以在前端工作了,希望不会使我们的手太脏。 关于HTML标记,这就是我们所需要的,认真的是:

<table id="grid"></table>
<div id="pager"></div>

请记住,我们将实现所有CRUD操作,但这仍然是我们所需要的。 没有更多HTML。 多亏了出色的jqGrid库,其余的魔术才得以实现。 这是一个基本设置:

$("#grid").jqGrid({url:'rest/book',colModel:[{name:'id', label: 'ID', formatter:'integer', width: 40},{name:'title', label: 'Title', width: 300},{name:'author', label: 'Author', width: 200},{name:'publishedYear', label: 'Published year', width: 80, align: 'center'},{name:'available', label: 'Available', formatter: 'checkbox', width: 46, align: 'center'}],caption: "Books",pager : '#pager',height: 'auto'}).navGrid('#pager', {edit:false,add:false,del:false, search: false});

从技术上讲,这就是我们所需要的。 用来获取数据的URL,指向我们的控制器(jqGrid将为我们执行所有AJAX魔术)和数据模型(您可能会识别书本字段及其描述)。 但是,由于jqGrid是高度可定制的,因此我进行了一些调整以使网格看起来更好。 另外,我也不喜欢建议使用的元数据名称,例如从服务器返回的字段总数应该是页面总数,而不是记录总数,这很违反直觉。 这是我调整的选项:

$.extend($.jgrid.defaults, {datatype: 'json',jsonReader : {repeatitems:false,total: function(result) {//Total number of pagesreturn Math.ceil(result.total / result.max);},records: function(result) {//Total number of recordsreturn result.total;}},prmNames: {rows: 'max', search: null},height: 'auto',viewrecords: true,rowList: [10, 20, 50, 100],altRows: true,loadError: function(xhr, status, error) {alert(error);}});

渴望看到结果吗? 这是浏览器的屏幕截图:

漂亮的外观,可自定义的分页,轻巧的刷新……而且我们的手还是比较干净的! 但是我答应了CRUD ...如果您小心的话,您可能已经注意到了一些navGrid属性,并且很想打开它:

var URL = 'rest/book';
var options = {url: URL,editurl: URL,colModel:[{name:'id', label: 'ID',formatter:'integer',width: 40,editable: true,editoptions: {disabled: true, size:5}},{name:'title',label: 'Title',width: 300,editable: true,editrules: {required: true}},{name:'author',label: 'Author',width: 200,editable: true,editrules: {required: true}},{name:'cover',label: 'Cover',hidden: true,editable: true,edittype: 'select',editrules: {edithidden:true},editoptions: {value: {'PAPERBACK': 'paperback', 'HARDCOVER': 'hardcover', 'DUST_JACKET': 'dust jacket'}}},{name:'publishedYear',label: 'Published year',width: 80,align: 'center',editable: true,editrules: {required: true, integer: true},editoptions: {size:5, maxlength: 4}},{name:'available',label: 'Available',formatter: 'checkbox',width: 46,align: 'center',editable: true,edittype: 'checkbox',editoptions: {value:"true:false"}},{name:'comments',label: 'Comments',hidden: true,editable: true,edittype: 'textarea',editrules: {edithidden:true}}],caption: "Books",pager : '#pager',height: 'auto'
};
$("#grid").jqGrid(options).navGrid('#pager', {edit:true,add:true,del:true, search: false});

配置变得非常冗长,但并没有什么复杂的事情–对于每个字段,我们都添加了一些其他属性来控制在编辑模式下应如何处理该字段。 这包括应该代表哪种类型HTML输入,验证规则,可见性等。但是说实话,我认为这是值得的:

jqGrid根据上面提到的我们的编辑选项(包括验证逻辑)完全生成了一个外观漂亮的编辑窗口。 我们可以在编辑对话框中使某些字段在网格中隐藏/不活动(如id)可见,反之亦然(封面和注释不存在于网格中,但是您可以对其进行修改)。 另外请注意,在网格的左下角几乎看不到新图标。 添加和删​​除也是可能的-我们还没有编写一行HTML / JSP / JavaScript(jqGrid配置对象除外)。

当然,我们都知道用户界面就是应用程序 ,我们的界面还不错,但是有时候我们真的想要一个漂亮且可以运行的应用程序。 当前,后一个要求是我们的致命弱点。 不是因为后端尚未准备好,而是相当简单的:

@Controller
@RequestMapping(value = "/book")
public class BookController {private final Map<Integer, Book> books = new ConcurrentSkipListMap<Integer, Book>();@RequestMapping(value = "/{id}", method = GET)public @ResponseBody Book read(@PathVariable("id") int id) {//...}@RequestMapping(method = GET)public@ResponseBodyPage<Book> listBooks(@RequestParam(value = "page", required = false, defaultValue = "1") int page,@RequestParam(value = "max", required = false, defaultValue = "20") int max) {//...}@RequestMapping(value = "/{id}", method = PUT)@ResponseStatus(HttpStatus.NO_CONTENT)public void updateBook(@PathVariable("id") int id, @RequestBody Book book) {//...}@RequestMapping(method = POST)public ResponseEntity<String> createBook(HttpServletRequest request, @RequestBody Book book) {//...}@RequestMapping(value = "/{id}", method = DELETE)@ResponseStatus(HttpStatus.NO_CONTENT)public void deleteBook(@PathVariable("id") int id) {//...}}

服务器端已经准备就绪,但是当涉及到客户端的数据操作时,jqGrid会揭示其肮脏的秘密–到服务器的所有流量都使用POST发送,如下所示:

Content-Type: application/x-www-form-urlencoded in the following format:
id=&title=And+Then+There+Were+None&author=Agatha+Christie&cover=PAPERBACK&publishedYear=1939&available=true&comments=&oper=add

最后一个属性(oper = add)是至关重要的。 不是真正的惯用REST,您不觉得吗? 如果我们只能适当地使用POST / PUT / DELETE并使用JSON或XML序列化数据……修改服务器,使其与某些JavaScript库兼容(无论它有多酷),那似乎是不得已的方法。 值得庆幸的是,只需少量的工作就可以自定义所有内容。

$.extend($.jgrid.edit, {ajaxEditOptions: { contentType: "application/json" },mtype: 'PUT',serializeEditData: function(data) {delete data.oper;return JSON.stringify(data);}});
$.extend($.jgrid.del, {mtype: 'DELETE',serializeDelData: function() {return "";}});var URL = 'rest/book';
var options = {url: URL,//...
}var editOptions = {onclickSubmit: function(params, postdata) {params.url = URL + '/' + postdata.id;}
};
var addOptions = {mtype: "POST"};
var delOptions = {onclickSubmit: function(params, postdata) {params.url = URL + '/' + postdata;}
};$("#grid").jqGrid(options).navGrid('#pager',{}, //optionseditOptions,addOptions,delOptions,{} // search options
);

我们为每个操作定制了HTTP方法,使用JSON处理序列化,最后用于编辑和删除操作的URL现在带有/ record_id后缀。 现在它不仅看起来,而且可以工作! 查看浏览器与服务器的交互(注意不同的HTTP方法和URL):

这是在浏览器端创建新资源的示例:

为了尽可能严格地遵循REST原则,我返回201 Created响应代码以及Location标头,该标头指向新创建的资源。 如您所见,数据现在以JSON格式发送到服务器。

总而言之,这种方法具有很多优点:

  • GUI非常敏感,页面会立即显示(它可以是CDN提供的静态资源),而数据是通过AJAX以轻量级JSON格式异步加载的
  • 我们免费获得CRUD操作
  • 其他系统的REST接口也是免费的

将其与任何Web框架进行比较。 我是否在JavaScript结霜方面提到了这个小问题:jqGrid完全符合jQuery UI主题 ,还支持国际化。 这是具有更改的主题和语言的同一应用程序:

完整的源代码可在Tomek的GitHub帐户上获得 。 该应用程序是独立的,只需构建它并将其部署到某个servlet容器中即可。

参考: 穷人的CRUD:jQGrid,REST,AJAX和Spring MVC在我们的JCG合作伙伴 Tomek Nurkiewicz的NoBlogDefFound博客中合而为一 。

相关文章 :
  • Spring3 RESTful Web服务
  • Tomcat 7上具有RESTeasy JAX-RS的RESTful Web服务-Eclipse和Maven项目
  • Spring MVC3 Hibernate CRUD示例应用程序
  • Spring MVC开发–快速教程
  • 带有Spring和Maven教程的JAX–WS

翻译自: https://www.javacodegeeks.com/2011/07/jqgrid-rest-ajax-spring-mvc-integration.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/374933.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java-马士兵设计模式学习笔记-代理模式--动态代理 修改成可以代理任意接口

一、概述 1.目标&#xff1a;把Proxy修改成可以代理任意接口及其任意方法 2.思路&#xff1a; (1)代理任意接口&#xff1a;把接口类型作为参数传给Proxy的newProxyInstance(Class interfze) (2)代理任意方法&#xff1a;用interfze.getMethods()取出所有方法&#xff0c;拼接实…

C语言5-7习题

本题要求实现一个函数&#xff0c;用下列公式求cos(x)的近似值&#xff0c;精确到最后一项的绝对值小于e&#xff1a; #include <stdio.h> #include <math.h>double funcos( double e, double x );int main() { double e, x;scanf("%lf %lf", &…

BC div2补题以及 复习模除 逆元__BestCoder Round #78 (div.2)

第一题没话说 智商欠费 加老柴辅导终于过了 需要在意的是数据范围为2的63次方-1 三个数相加肯定爆了 四边形的定义  任意边小于其余三边之和 换句话说就是  最长边小于其余三边之和 这样的话问题转化为 最长边依次减其余三边的结果是否小于等于0 还有一点是题目出现0边 即最…

习题6-1 分类统计字符个数 (15 分)

本题要求实现一个函数&#xff0c;统计给定字符串中英文字母、空格或回车、数字字符和其他字符的个数。 函数接口定义&#xff1a; void StringCount( char s[] );其中 char s[] 是用户传入的字符串。函数StringCount须在一行内按照 letter 英文字母个数, blank 空格或回车…

Servlet 3.0异步处理可将服务器吞吐量提高十倍

Servlet是Java中处理服务器端逻辑的主要组件&#xff0c;新的3.0规范引入了一些非常有趣的功能&#xff0c;其中异步处理是最重要的功能之一。 可以利用异步处理来开发高度可伸缩的Web应用程序。 使用此功能可以有效地构建Web 2.0站点和AJAX应用程序。 我们的JCG合作伙伴之一To…

使用secureCRT连接VMware-Ubuntukylin虚拟机

使用SecureCRT连接VMware时总是提醒主机拒绝连接。这时可以使用sudo apt-get install openssh-server openssh-client&#xff0c;在主机上安装ssh. 安装成功后&#xff0c;可以连接到主机了。 如果显示远程主机拒绝连接。则可以使用如下方法。 VMware里面装的是Ubuntukylin版本…

国内有哪些好的刷题网站?

http://www.zhihu.com/question/25574458 Luau Lawrence&#xff0c;Data Mining 弱鸡 / PhDNTU 温梦强、石一帆、知乎用户 等人赞同 - Welcome To PKU JudgeOnline 北京大学的Online Judge。POJ上面的题目有点老了&#xff0c;但好处是做的人多&#xff0c;经典算法题多&…

Js 流程控制

流程控制 顺序、分支、循环 顺序结构 代码一行一行从上往下执行并解析 分支结构 if语句 switch语句 if语句 单分支 if(条件表达式){ //语句块 } 含义&#xff1a;当条件表达式为真的时候就执行里面的语句块 示例&#xff1a; 双分支&#xff1a; if(条件表达式){ //语句块1 }el…

SmartGWT入门,提供出色的GWT界面

SmartGWT简介 我最近开始使用SmartGWT &#xff0c;它是一个基于GWT的框架&#xff0c;该框架为您的应用程序UI提供了一个全面的小部件库&#xff0c;并为服务器端的数据管理提供了帮助。 您可以在SmartGWT展示柜上查看其漂亮的功能。 我准备了一个简短的“入门”指南&#xf…

Android OpenGL ES(四)----调整屏幕的宽高比

1.宽高比问题 我们现在相当熟悉这样一个事实&#xff0c;在OpenGL里&#xff0c;我们要渲染的一切物体都要映射到X轴和Y轴上[-1&#xff0c;1]的范围内&#xff0c;对于Z轴也一样。这个范围内的坐标被称为归一化设备坐标&#xff0c;其独立于屏幕实际尺寸或形状。 不幸的是&…

使用Spring Security保护GWT应用程序

在本教程中&#xff0c;我们将看到如何将GWT与Spring的安全模块&#xff08;即Spring Security&#xff09;集成在一起。 我们将看到如何保护GWT入口点&#xff0c;如何检索用户的凭据以及如何记录各种身份验证事件。 此外&#xff0c;我们将实现自定义身份验证提供程序&#x…

Tomcat 7上具有RESTeasy JAX-RS的RESTful Web服务-Eclipse和Maven项目

开发Web服务的RESTful方法不断受到越来越多的关注&#xff0c;并且似乎正在将SOAP淘汰。 我不会讨论哪种方法更好&#xff0c;但是我相信我们都同意REST更轻量级。 在本教程中&#xff0c;我将向您展示如何使用RESTeasy开发RESTful服务以及如何将它们部署在Tomcat服务器上。 在…

数据值、列类型和数据字段属性

数据值&#xff1a;数值型、字符型、日期型和空值等。数据列类型 2.1 数值类的数据列类型2.2 字符串类数据列类型 2.3 日期和时间型数据数据列类型 另外&#xff0c;也可以使用整形列类型存储UNIX时间戳&#xff0c;代替日期和时间列类型&#xff0c;这是基于PHP的web项目中常…

全文搜索Apache Lucene简介

在本教程中&#xff0c;我想谈谈Apache Lucene 。 Lucene是一个开源项目&#xff0c;提供基于Java的索引和搜索技术。 使用其API&#xff0c;很容易实现全文搜索 。 我将处理Lucene Java版本 &#xff0c;但请记住&#xff0c;还有一个名为Lucene.NET的.NET端口&#xff0c;以及…

Java中的低GC:使用原语而不是包装器

总览 有两个很好的理由在可能的地方使用原语而不是包装器。 明晰。 通过使用原语&#xff0c;您可以清楚地知道null值是不合适的。 性能。 使用原语通常更快。 清晰度通常比性能更重要&#xff0c;并且是使用它们的最佳理由。 但是&#xff0c;本文讨论了使用包装程序对性能…

Java Secret:使用枚举构建状态机

总览 Java中的枚举比许多其他语言更强大&#xff0c;这可能导致令人惊讶的用途。 在本文中&#xff0c;我概述了Java 枚举的一些单独功能&#xff0c;并将它们组合在一起形成一个状态机。 单例和实用程序类的枚举 您可以非常简单地将枚举用作Singleton或Utility。 enum Si…

指向函数的指针

指向函数的指针变量的一般形式为&#xff1a;数据类型 &#xff08;*指针变量名&#xff09;&#xff08;函数参数表列&#xff09;&#xff1b;如&#xff1a; int (*p)(int ,int );1、int (*p)(int ,int );表示定义一个指向函数的指针变量p&#xff0c;它不是固定只能指向…

Tomcat中的零停机部署(和回滚); 演练和清单

亲爱的大家&#xff0c; 如果您认为Tomcat不能再进步&#xff0c;那您就错了。 Tomcat 7引入了所谓的并行部署 。 这是由SpringSource / VMWare贡献的。 简而言之&#xff0c;并行部署是一种能够并行部署一个以上版本的Web应用程序的功能&#xff0c;使所有版本都可以在完全相…

HDU 1863 畅通工程(最小生成树,prim)

题意&#xff1a; 给出图的边和点数&#xff0c;要求最小生成树的代价&#xff0c;注&#xff1a;有些点之间是不可达的&#xff0c;也就是可能有多个连通图。比如4个点&#xff0c;2条边:1-2&#xff0c;3-4。 思路&#xff1a; 如果不能连通所有的点&#xff0c;就输出‘?’…

JavaFX 2.0 beta示例应用程序和思考

我有一段时间回过头来玩JavaFX&#xff0c;并且在使用该语言方面有好有坏的经验。 随着JavaFX 2.0 beta的发布&#xff0c;我想尝试一下。 在这里&#xff0c;我开发了一个简单的地址解析应用程序&#xff0c;该应用程序将使用Google地址编码API来获取地址并提供该位置的纬度-经…